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中的作用域规则详解
Jan 30 Python
微信跳一跳python辅助软件思路及图像识别源码解析
Jan 04 Python
Python堆排序原理与实现方法详解
May 11 Python
flask框架中勾子函数的使用详解
Aug 01 Python
Python对切片命名的实现方法
Oct 16 Python
破解安装Pycharm的方法
Oct 19 Python
PyQtGraph在pyqt中的应用及安装过程
Aug 04 Python
通过Turtle库在Python中绘制一个鼠年福鼠
Feb 03 Python
Python查找不限层级Json数据中某个key或者value的路径方式
Feb 27 Python
python+opencv实现移动侦测(帧差法)
Mar 20 Python
用于ETL的Python数据转换工具详解
Jul 21 Python
python3 删除所有自定义变量的操作
Apr 08 Python
Python之Matplotlib绘制热力图和面积图
Python matplotlib绘制雷达图
Python万能模板案例之matplotlib绘制甘特图
Python万能模板案例之matplotlib绘制直方图的基本配置
python创建字典及相关管理操作
python微信智能AI机器人实现多种支付方式
Python中request的基本使用解决乱码问题
Apr 12 #Python
You might like
ThinkPHP模板中数组循环实例
2014/10/30 PHP
WordPress主题制作中自定义头部的相关PHP函数解析
2016/01/08 PHP
浅谈PHP的数据库接口和技术
2016/12/09 PHP
php上传excel表格并获取数据
2017/04/27 PHP
ThinkPHP框架实现FTP图片上传功能示例
2019/04/08 PHP
php解决crontab定时任务不能写入文件问题的方法分析
2019/09/16 PHP
PHP框架实现WebSocket在线聊天通讯系统
2019/11/21 PHP
php 使用expat方式解析xml文件操作示例
2019/11/26 PHP
div+css布局的图片连续滚动js实现代码
2010/05/04 Javascript
javascript实现删除前弹出确认框
2015/06/04 Javascript
Css3制作变形与动画效果
2015/07/24 Javascript
JavaScritp添加url参数并将参数加入到url中及更改url参数的方法
2015/10/26 Javascript
深入浅析Vue中的slots/scoped slots
2018/04/03 Javascript
4 种滚动吸顶实现方式的比较
2019/04/09 Javascript
vuex实现数据状态持久化
2019/11/11 Javascript
pycharm安装图文教程
2017/05/02 Python
python中numpy.zeros(np.zeros)的使用方法
2017/11/07 Python
疯狂上涨的Python 开发者应从2.x还是3.x着手?
2017/11/16 Python
Python决策树分类算法学习
2017/12/22 Python
Python利用requests模块下载图片实例代码
2019/08/12 Python
弄清Pytorch显存的分配机制
2020/12/10 Python
美体小铺美国官网:The Body Shop美国
2017/11/10 全球购物
Holiday Inn中国官网:IHG旗下假日酒店预订
2018/04/08 全球购物
英国女性时尚品牌:Apricot
2018/12/04 全球购物
Ajax的优点和缺点
2014/11/21 面试题
护士辞职信范文
2014/01/19 职场文书
演讲比赛获奖感言
2014/02/02 职场文书
美容院店长岗位职责
2014/04/08 职场文书
四风问题个人对照检查材料
2014/09/26 职场文书
2014年药店工作总结
2014/11/20 职场文书
党性分析材料格式
2014/12/19 职场文书
在Django中使用MQTT的方法
2021/05/10 Python
MySql学习笔记之事务隔离级别详解
2021/05/12 MySQL
JS + HTML 罗盘式时钟的实现
2021/05/21 Javascript
你知道哪几种MYSQL的连接查询
2021/06/03 MySQL
React 高阶组件HOC用法归纳
2021/06/13 Javascript