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使用函数默认值实现函数静态变量的方法
Aug 18 Python
Python中用Spark模块的使用教程
Apr 13 Python
简单介绍Python中的readline()方法的使用
May 24 Python
从源码解析Python的Flask框架中request对象的用法
Jun 02 Python
Python中的复制操作及copy模块中的浅拷贝与深拷贝方法
Jul 02 Python
Python按行读取文件的实现方法【小文件和大文件读取】
Sep 19 Python
Python基于QRCode实现生成二维码的方法【下载,安装,调用等】
Jul 11 Python
python遍历文件夹,指定遍历深度与忽略目录的方法
Jul 11 Python
Python实现基于socket的udp传输与接收功能详解
Nov 15 Python
python 项目目录结构设置
Feb 14 Python
Python urllib.request对象案例解析
May 11 Python
python 基于DDT实现数据驱动测试
Feb 18 Python
Python之Matplotlib绘制热力图和面积图
Python matplotlib绘制雷达图
Python万能模板案例之matplotlib绘制甘特图
Python万能模板案例之matplotlib绘制直方图的基本配置
python创建字典及相关管理操作
python微信智能AI机器人实现多种支付方式
Python中request的基本使用解决乱码问题
Apr 12 #Python
You might like
世界第一个无线广播电台 KDKA
2021/03/01 无线电
phpmyadmin配置文件现在需要绝密的短密码(blowfish_secret)的2种解决方法
2014/05/07 PHP
Symfony数据校验方法实例分析
2015/01/26 PHP
解决php表单重复提交实现方法
2015/09/29 PHP
PHP 自动加载类原理与用法实例分析
2020/04/14 PHP
一个基于jQuery的树型插件(OrangeTree)使用介绍
2012/05/03 Javascript
解决window.opener=null;window.close(),只支持IE6不支持IE7,IE8的问题
2014/01/14 Javascript
javascript实现左右控制无缝滚动
2014/12/31 Javascript
JavaScript数组和循环详解
2015/04/27 Javascript
JavaScript中this详解
2015/09/01 Javascript
canvas实现流星雨的背景效果
2017/01/13 Javascript
详解vue.js的事件处理器v-on:click
2017/06/27 Javascript
详解AngularJS1.x学习directive 中‘& ’‘=’ ‘@’符号的区别使用
2017/08/23 Javascript
Vue.js 实现微信公众号菜单编辑器功能(一)
2018/05/08 Javascript
在vue.js中使用JSZip实现在前端解压文件的方法
2018/09/05 Javascript
微信小程序中的canvas 文字断行和省略号显示功能的处理方法
2018/11/14 Javascript
nodejs实现用户登录路由功能
2019/05/22 NodeJs
vue-cli 3 全局过滤器的实例代码详解
2019/06/03 Javascript
解决vue v-for src 图片路径问题 404
2019/11/12 Javascript
Jquery $.map使用方法实例详解
2020/09/01 jQuery
python基础入门详解(文件输入/输出 内建类型 字典操作使用方法)
2013/12/08 Python
Flask框架中密码的加盐哈希加密和验证功能的用法详解
2016/06/07 Python
Swift中的协议(protocol)学习教程
2016/07/08 Python
对Python 文件夹遍历和文件查找的实例讲解
2018/04/26 Python
解决pycharm 远程调试 上传 helpers 卡住的问题
2019/06/27 Python
python制作朋友圈九宫格图片
2019/11/03 Python
Python实现打包成库供别的模块调用
2020/07/13 Python
python3处理word文档实例分析
2020/12/01 Python
Python plt 利用subplot 实现在一张画布同时画多张图
2021/02/26 Python
CSS3中background-clip和background-origin的区别示例介绍
2014/03/10 HTML / CSS
深入理解css属性的选择对动画性能的影响
2016/04/20 HTML / CSS
css3边框_动力节点Java学院整理
2017/07/11 HTML / CSS
Perricone MD裴礼康美国官网:抗衰老护肤品
2016/09/26 全球购物
自我评价范文
2013/12/22 职场文书
领导个人查摆剖析材料
2014/10/29 职场文书
2015年小学校长工作总结
2015/05/19 职场文书