Python线程池与GIL全局锁实现抽奖小案例


Posted in Python onApril 13, 2022

线程池

线程池的创建 - concurrent

concurrent 是 Python 的内置包,使用它可以帮助我们完成创建线程池的任务。

方法名 介绍 示例
futures.ThreadPoolExecutor 创建线程池 tpool=ThreadPoolExecutor(max_workers)

通过调用 concurrent 包的 futures 模块的 ThreadPoolExecutor 类,通过实例化 ThreadPoolExecutor 实现创建线程池的对象,它有一个参数来设置 线程池的数量。这和创建进程池设置的数量是完全相同的。

线程池的常用方法

接下里看一下线程池对象中都有哪些常用的方法 :

函数名 介绍 用法
submit 往线程池中添加任务 submit(target, args)
done 确认线程池中的某个线程是否完成了任务 done()
rsult 获取当前线程执行任务的结果 result()
  • submit 函数:通过 submit 函数将参数传入;该函数传入的参数也是传入要执行的函数与该函数的参数,由于它的参数并不用需要通过赋值语句的形式传入,只需要把相应的值传入就可以了(稍后会进行一个练习)。
  • done 函数:判断当前线程是否执行完成;返回值是 bool 类型。
  • result 函数:返回当前线程池中线程任务的执行结果,通过这种方法就可以获取线程池的返回值了。

线程池演示案例

1、定义一个函数实现循环的效果

2、定义一个线程池,设置线程的数量

# coding:utf-8


import time
from concurrent.futures import ThreadPoolExecutor


def work(i):
    print('第 {} 次循环'.format(i))
    time.sleep(1)   # 之所以每次都要使用 sleep 函数,是因为函数执行太快;通过 sleep 尝试模拟一下长时间的执行一个任务


if __name__ == '__main__':
    thread_poor = ThreadPoolExecutor(4)	# 实例化一个线程池,设置线程数量为4

    for i in range(20):
        thread_poor.submit(work, (i,))		# 利用 submit 函数将任务添加至 work 函数

运行效果如下 

Python线程池与GIL全局锁实现抽奖小案例

从运行结果来看,我们的线程任务每次执行4个任务,阻塞一秒后再执行后续的四个线程的任务,是没有问题的。

PS:需要注意的是,运行结果有可能是出现将两个或者多个任务的结果在同一行打印输出,这是因为在同一时间处理了多个线程的任务,这也叫 "并发"。

线程锁

前文的进程池是与进程锁相对应匹配的,同样的线程池也有与之对应的 线程锁 。线程锁的使用方法几乎与进程锁是一样的,只不过线程锁对应的是线程罢了。

1.实例化一个线程锁

2、在 work 函数中调用线程锁

3、并获取 线程 的返回值(线程池也是可以获取返回值的)

代码示例如下:

# coding:utf-8


import os
import time
import threading
from concurrent.futures import ThreadPoolExecutor


lock = threading.Lock()     # 全局定义一个 Lock() 实例

def work(i):
    lock.acquire()          # 区别于 进程锁 只需要在全局实例化一个即可,线程锁需要在线程任务的函数中调用 线程锁 才会生效
    print('当前是第 {} 次循环'.format(i))
    time.sleep(1)           # 之所以每次都要使用 sleep 函数,是因为函数执行太快;通过 sleep 尝试模拟一下长时间的执行一个任务
    lock.release()
    return '第 {} 次循环的进程id为:{}'.format(i, os.getpid())    # 线程也是基于进程实现的


if __name__ == '__main__':
    thread_poor = ThreadPoolExecutor(4)    # 实例化一个线程池,设置线程数量为4

    result = []

    for i in range(20):
        result_thread = thread_poor.submit(work, (i,))      # 利用 submit 函数将任务添加至 work 函数;
                                                            # 需要注意的是这里不像进程池那样使用赋值的形式传入 work 函数
        result.append(result_thread)

    for res in result:
        print(res.result())

运行结果如下:

Python线程池与GIL全局锁实现抽奖小案例

从运行结果可以看到,之前一同执行的4个任务现在变成了一次只执行一个任务;每一个个线程都是在主进程 93215下执行的,说明线程与进程还是有所区别的,虽然我们有多个线程任务在执行,但是依然是在主进程下去完成的;同时我们还获取到了 线程的返回值 第 {} 次循环的进程id为:{}'.format(i, os.getpid() 。

以上就是线程池的使用和常用方法,我们会发现线程池的使用实际上要比进程池的使用要容易一些。进程池我们需要考虑 join 与 close 等一些问题,但是线程池则不需要那么的严格,并且线程相对于进程要更加的轻量,使用起来也更加的便捷。

利用线程池实现抽奖小案例

案例代码如下:

# coding:utf-8

import threading
import random
from concurrent.futures import ThreadPoolExecutor

lock = threading.Lock()
def luck_draw(arg):
    lock.acquire()
    # 从手机列表中随机选出一个中奖手机,其他手机均未中奖
    phone = random.choice(arg[0])
    # 在从奖池中随机选取一个奖品,视为该手机抽中的奖品
    price = random.choice(arg[1])
    prices.remove(price)
    phones.remove(phone)
    lock.release()
    return '恭喜手机尾号为{}的用户,抽到{}'.format(str(phone)[-5:-1], price)


if __name__ == '__main__':
    t = ThreadPoolExecutor(3)       # 通过创建三个线程从而实现每个线程完成一项抽奖任务
    # 确定抽奖人数
    phone_num = int(input('请输入抽奖的用户人数:'))
    # 模拟产生出相应数量的手机号
    phones = random.sample(range(13300000000, 19999999999), phone_num)      # 这里设置的随机号码仅做演示效果
    prices = ['一等奖:iPhone12 ProMax', '二等奖:ipad2021pro', '三等奖:air wetter']
    result = []
    for i in range(3): # 三个任务,每个线程分配一个
        t_result = t.submit(luck_draw, (phones, prices))
        result.append(t_result)

    for res in result:
        print(res.result())

运行效果如下:

Python线程池与GIL全局锁实现抽奖小案例

GIL全局锁

本章节的开头我们就说过,该部分没有代码的相关练习。仅仅是对 GIL全局锁 做一个概念上的简单启蒙。

其他语言的线程与Python线程的区别

多线程与多进程的使用其实是比较复杂的,目前作为初学者来说涉及的还比较浅。最近的几个章节介绍了 进程与线程在CPU的执行方式,这里再进行拓展一下。

下面我们看一张图:

Python线程池与GIL全局锁实现抽奖小案例

依然是一个CPU 与4个核心(可以认为是4条跑道);

先看左边的两条跑道,是进程1创建的3个线程。这三条线程有一个去了 1core 跑道,另外两条则去了 2core 跑道。线程之间有选择性的进入了不同的跑道,当然进程1的主进程或者说是主线程可能会在 1core 跑道、也可能会在 2core 跑道,这是其他语言进行多线程的样子。

再来看看右边,Python 创建的进程2。当进程创建之后,包含主线程一共产生了3个线程,而这三个线程都跑到了 4core 跑道 上去。它不会像其他语言那样去寻找不同的有空闲资源的跑道去执行,而是仅仅在主进程所处的跑道去执行线程。造成 Python 中的多线程无法在多条跑道执行任务的主要原因就是因为 GIL全局锁 ,这个 GIL 并不是 Python语法中添加上去的,而是 python解释器 在执行的时候自动加了这把 "锁" 。

GIL 的作用

因为 GIL 锁 的关系,使得 Python 的多线程无法在多个CPU跑道上去执行任务,它只能在单一CPU上进行工作。

这也限制了多线程的性能,毕竟 Python 的多线程只能在一条跑道上运行。跑道满了,运行速度依然会慢。而在多个跑道上运行的任务必然是要比单一跑道效率会高很多。

Python创始人 Guido 之所以保留 GIL 锁,其实也是为了线程之间的安全。虽然这个话题一直都在争论,不过我们也有办法去掉这个 GIL全局锁。

默认的解释器是 Python 自带的解释器,这里我们可以选择一个叫做 pypy 的解释器。通过它来执行 Python 脚本是不含有 GIL全局锁 的,但并不太推荐这种做法。

另外一种解决方法就是使用 多进程 + 多线程 的方式 来弥补这一短板上的问题,通过多进程在每个 CPU 跑道上执行任务,并且每个进程的跑道上再去执行多个线程。 ,让它们在各自的时间片上去运行。这些用法会在后续的章节会介绍到,在此只需要了解即可。

以上就是Python学习之线程池与GIL全局锁详解的详细内容!

Python 相关文章推荐
python logging类库使用例子
Nov 22 Python
Python 实现简单的shell sed替换功能(实例讲解)
Sep 29 Python
Python自然语言处理之词干,词形与最大匹配算法代码详解
Nov 16 Python
Python星号*与**用法分析
Feb 02 Python
Python访问MongoDB,并且转换成Dataframe的方法
Oct 15 Python
10分钟教你用Python实现微信自动回复功能
Nov 28 Python
在VS2017中用C#调用python脚本的实现
Jul 31 Python
python基础 range的用法解析
Aug 23 Python
python实现指定ip端口扫描方式
Dec 17 Python
Tensorflow卷积实现原理+手写python代码实现卷积教程
May 22 Python
Django一小时写出账号密码管理系统
Apr 29 Python
教你用Python matplotlib库制作简单的动画
Jun 11 Python
Python之Matplotlib绘制热力图和面积图
Python matplotlib绘制雷达图
Python万能模板案例之matplotlib绘制甘特图
Python万能模板案例之matplotlib绘制直方图的基本配置
python创建字典及相关管理操作
python微信智能AI机器人实现多种支付方式
Python中request的基本使用解决乱码问题
Apr 12 #Python
You might like
PHP 中的类
2006/10/09 PHP
php面向对象全攻略 (十七) 自动加载类
2009/09/30 PHP
解析PHP高效率写法(详解原因)
2013/06/20 PHP
PHP获取当前所在目录位置的方法
2014/11/26 PHP
PHP获取一段文本显示点阵宽度和高度的方法
2015/03/12 PHP
[原创]PHP global全局变量经典应用与注意事项分析【附$GLOBALS用法对比】
2019/07/12 PHP
Jquery图片滚动与幻灯片的实例代码
2013/04/08 Javascript
Javascript 按位左移运算符使用介绍(
2014/02/04 Javascript
jquery form表单序列化为对象的示例代码
2014/03/05 Javascript
javascript动态控制服务器控件实例
2014/09/05 Javascript
详解js中class的多种函数封装方法
2016/01/03 Javascript
ajax级联菜单实现方法实例分析
2016/11/28 Javascript
Node.js设置CORS跨域请求中多域名白名单的方法
2017/03/28 Javascript
Google 爬虫如何抓取 JavaScript 的内容
2017/04/07 Javascript
jQuery判断网页是否已经滚动到浏览器底部的实现方法
2017/10/27 jQuery
thinkjs 文件上传功能实例代码
2017/11/08 Javascript
js最实用string(字符串)类型的使用及截取与拼接详解
2019/04/26 Javascript
移动端 Vue+Vant 的Uploader 实现上传、压缩、旋转图片功能
2019/06/10 Javascript
jQuery实现的记住帐号密码功能完整示例
2019/08/03 jQuery
Vue图片浏览组件v-viewer用法分析【支持旋转、缩放、翻转等操作】
2019/11/04 Javascript
Vue 中 filter 与 computed 的区别与用法解析
2019/11/21 Javascript
VUE项目axios请求头更改Content-Type操作
2020/07/24 Javascript
Vue 构造选项 - 进阶使用说明
2020/08/14 Javascript
Vue+Element ui 根据后台返回数据设置动态表头操作
2020/09/21 Javascript
对python中的float除法和整除法的实例详解
2019/07/20 Python
django多文件上传,form提交,多对多外键保存的实例
2019/08/06 Python
区分python中的进程与线程
2020/08/13 Python
波兰最大的儿童服装连锁店之一:5.10.15.
2018/02/11 全球购物
央视元宵晚会主持串词
2014/03/25 职场文书
《学会合作》教学反思
2014/04/12 职场文书
产品开发计划书
2014/04/27 职场文书
班子个人四风问题整改措施
2014/10/04 职场文书
数学教师个人工作总结
2015/02/06 职场文书
团日活动总结格式
2015/05/11 职场文书
交通肇事罪辩护词
2015/05/21 职场文书
JavaScript高级程序设计之变量与作用域
2021/11/17 Javascript