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使用内存zipfile对象在内存中打包文件示例
Apr 30 Python
Python操作SQLite简明教程
Jul 10 Python
Python中无限元素列表的实现方法
Aug 18 Python
python中快速进行多个字符替换的方法小结
Dec 15 Python
解决Python requests 报错方法集锦
Mar 19 Python
Python+Wordpress制作小说站
Apr 14 Python
Python数据分析之双色球统计两个红和蓝球哪组合比例高的方法
Feb 03 Python
解决python中os.listdir()函数读取文件夹下文件的乱序和排序问题
Oct 17 Python
python创造虚拟环境方法总结
Mar 04 Python
Pandas的read_csv函数参数分析详解
Jul 02 Python
python 安装库几种方法之cmd,anaconda,pycharm详解
Apr 08 Python
Python HTMLTestRunner如何下载生成报告
Sep 04 Python
Python之Matplotlib绘制热力图和面积图
Python matplotlib绘制雷达图
Python万能模板案例之matplotlib绘制甘特图
Python万能模板案例之matplotlib绘制直方图的基本配置
python创建字典及相关管理操作
python微信智能AI机器人实现多种支付方式
Python中request的基本使用解决乱码问题
Apr 12 #Python
You might like
关于Appserv无法打开localhost问题的解决方法
2009/10/16 PHP
解析isset与is_null的区别
2013/08/09 PHP
PHP中模拟链表和链表的基本操作示例
2016/02/27 PHP
php注册系统和使用Xajax即时验证用户名是否被占用
2017/08/31 PHP
用javascript实现自定义标签
2007/05/08 Javascript
jquery easyui combox一些实用的小方法
2013/12/25 Javascript
node.js中RPC(远程过程调用)的实现原理介绍
2014/12/05 Javascript
向JavaScript的数组中添加元素的方法小结
2015/10/24 Javascript
浅谈JavaScript for循环 闭包
2016/06/22 Javascript
js微信分享API
2020/10/11 Javascript
Angular-Ui-Router+ocLazyLoad动态加载脚本示例
2017/03/02 Javascript
微信小程序实战之自定义抽屉菜单(7)
2017/04/18 Javascript
JS对象与JSON互转换、New Function()、 forEach()、DOM事件流等js开发基础小结
2017/08/10 Javascript
超出JavaScript安全整数限制的数字计算BigInt详解
2018/06/24 Javascript
深入分析element ScrollBar滚动组件源码
2019/01/22 Javascript
浅析JavaScript异步代码优化
2019/03/18 Javascript
微信小程序实现可拖动悬浮图标(包括按钮角标的实现)
2020/12/29 Javascript
详解React中共享组件逻辑的三种方式
2021/02/02 Javascript
Python cookbook(数据结构与算法)找出序列中出现次数最多的元素算法示例
2018/03/15 Python
Python去除、替换字符串空格的处理方法
2018/04/01 Python
解决pandas .to_excel不覆盖已有sheet的问题
2018/12/10 Python
Django框架之登录后自定义跳转页面的实现方法
2019/07/18 Python
Python之time模块的时间戳,时间字符串格式化与转换方法(13位时间戳)
2019/08/12 Python
python实现音乐播放器 python实现花框音乐盒子
2020/02/25 Python
如何理解Python中包的引入
2020/05/29 Python
Python读写压缩文件的方法
2020/07/30 Python
Python getsizeof()和getsize()区分详解
2020/11/20 Python
TripAdvisor瑞典:全球领先的旅游网站
2017/12/11 全球购物
世界上最好的野生海鲜和有机食品:Vital Choice
2020/01/16 全球购物
管理建议书范文
2014/05/13 职场文书
服装发布会策划方案
2014/05/22 职场文书
学习雷锋标语
2014/06/25 职场文书
财务会计个人原因辞职信
2019/06/21 职场文书
十大最强水系宝可梦,最美宝可梦排第三,榜首大家最熟悉
2022/03/18 日漫
python计算列表元素与乘积详情
2022/08/05 Python
关于MySQL中explain工具的使用
2023/05/08 MySQL