Python中的并发编程(3)线程池、锁

concurrent.futures 提供的线程池

concurrent.futures模块提供了线程池和进程池简化了多线程/进程操作。

线程池原理是用一个任务队列让多个线程从中获取任务执行,然后返回结果。

常见的用法是创建线程池,提交任务,等待完成并获取结果

with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:futures = [executor.submit(count, item) for item in number_list] # count是一个函数,item是其参数for future in concurrent.futures.as_completed(futures):print(future.result())
  • concurrent.futures.ThreadPoolExecutor(max_workers=5)创建了一个线程池,max_workers指定了线程数量上限。通过线程池可以创建和执行任务。
  • concurrent.futures使用Future类表示(未来的)任务。调用.submit()时会创建并执行一个任务(Future)。
  • .as_completed(futures)是一个迭代器,当futures中有任务完成时会产出该future.

Python最广为使用的并发处理库futures使用入门与内部原理 - 知乎 (zhihu.com)对这个过程做了比较好的说明:
在这里插入图片描述
主线程是通过队列将任务传递给多个子线程的。一旦主线程将任务塞进任务队列,子线程们就会开始争抢,最终只有一个线程能抢到这个任务,并立即进行执行,执行完后将结果放进Future对象就完成了这个任务的完整执行过程。

python-parallel-programming-cookbook-cn 1.0 文档 中的一个例子对使用顺序执行、线程池进程池三种方式进行计算的时间进行了比较:

import concurrent.futures
import time# 一个耗时的计算
def count(number) :for i in range(0, 10000000):i=i+1return i * numberif __name__ == "__main__":number_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]# 顺序执行start_time = time.time()for item in number_list:print(count(item))print("Sequential execution in " + str(time.time() - start_time), "seconds")# 线程池start_time_1 = time.time()with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:futures = [executor.submit(count, item) for item in number_list]for future in concurrent.futures.as_completed(futures):print(future.result())print("Thread pool execution in " + str(time.time() - start_time_1), "seconds")# 进程池start_time_2 = time.time()with concurrent.futures.ProcessPoolExecutor(max_workers=5) as executor:futures = [executor.submit(count, item) for item in number_list]for future in concurrent.futures.as_completed(futures):print(future.result())print("Process pool execution in " + str(time.time() - start_time_2), "seconds")

结果为:

Sequential execution in 7.095552206039429 seconds
Thread pool execution in 7.140377998352051 seconds
Process pool execution in 4.240718126296997 seconds

竞争和锁

由于共享内存,多线程程序容易遇到竞争问题:两个内存对同一个变量进行修改可能导致意想不到的问题。

看下面这个计数的例子:
我们创建了一个全局变量thread_visits,在visit_counter()中修改这个变量值。

from threading import Thread
thread_visits = 0
def visit_counter():global thread_visitsfor _ in range(100_000):  thread_visits +=  1 #  thread_visits = thread_visits + 1if __name__ == "__main__":thread_count = 100threads = [Thread(target=visit_counter)for _ in range(thread_count)]for thread in threads:thread.start()for thread in threads:thread.join()print(f"thread_count={thread_count}, thread_visits={thread_visits}")

执行结果:

第1次 :thread_count=100, thread_visits=7227793
第2次 :thread_count=100, thread_visits=9544020
第3次 :thread_count=100, thread_visits=9851811

执行该程序会发现每次运行thread_visits的值都不一样。
因为在 thread_visits 变量上的读取和写入操作之间有一段时间,另一个线程可以介入并操作结果。这导致了竞争。
在这里插入图片描述

(线程1和线程2对变量thread_visits的竞争。两个线程都对thread_visits执行了+1的操作,但最后thread_visits的是1,而不是2。)

thread_visits += 1 实际包含读写两个操作,它等价于
thread_visits = thread_visits + 1,先读取thread_visits的值并+1,再写入到thread_visits

正确方法是使用保证一次只有一个线程可以处理单个代码块
在这里插入图片描述

from threading import Thread
from threading import Lockthread_visits = 0
thread_visits_lock = Lock()def visit_counter():global thread_visitsfor _ in range(100_000):  with thread_visits_lock:thread_visits +=  1 #  thread_visits = thread_visits + 1

运行结果:

thread_count=100, thread_visits=10000000

这次我们得到了正确的结果,但花费了接近一分钟的时间。因为受保护的块不能并行运行。此外,获取和释放锁是需要一些额外操作。

将锁放在外面的时候,会发现花费的时间减少了很多。因为减少了获取和释放锁的消耗。

	with thread_visits_lock:for _ in range(100_000):  thread_visits +=  1 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/265742.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

内网穿透的应用-如何结合Cpolar内网穿透工具实现在IDEA中远程访问家里或者公司的数据库

文章目录 1. 本地连接测试2. Windows安装Cpolar3. 配置Mysql公网地址4. IDEA远程连接Mysql小结 5. 固定连接公网地址6. 固定地址连接测试 IDEA作为Java开发最主力的工具,在开发过程中需要经常用到数据库,如Mysql数据库,但是在IDEA中只能连接本…

SpringMVC项目出现404

目录 问题讲解: 解决方案: 1、处理器映射器和处理器适配器以及视图解析器没有配置好 2、Controller的包扫描没有加或者包扫描的配置是错误的 3、当然也有说jar包没有 4、请求地址是错误的 5、还有一种解决办法说web.xml配置DispatcherServlet的时…

堪比Postman!实用IDEA插件推荐

Postman是大家最常用的API调试工具,那么有没有一种方法可以不用手动写入接口到Postman,即可进行接口调试操作?今天给大家推荐一款IDEA插件:Apipost Helper,写完代码就可以调试接口并一键生成接口文档!而且还…

【虹科干货】触发器和函数:让代码更接近数据

文章速览: 触发器和函数的基础知识编写语言:从Lua到JavaScript轻松维护应用程序代码数据库事件实时处理如何操作触发器和函数 一般来说,应用程序处理业务的逻辑,是将执行代码发送到数据库。因此每次执行函数时,代码都…

如何使用iPhone15在办公室远程观看家里群晖nas上的4k电影?

文章目录 1.使用环境要求:2.下载群晖Video Station:3.公网访问本地群晖Video Station中的电影:4.公网条件下使用电脑浏览器访问本地群晖video station5.公网条件下使用移动端(搭载安卓,ios,ipados等系统的设…

Server check fail, please check server xxx.xxx.xxx.xxx,port 9848 is available

记录一次服务调用中的错误 背景:我使用了nacos2.x的版本,同时在同一台服务器的三个docker容器中部署了nacos1、2、3,并将它们连接到了同一个docker网络 错误:Server check fail, please check server xxx.xxx.xxx.xxx,port 9848 …

制作蓝牙小车(一)

制作控制蓝牙小车app 想制作一个蓝牙小车,通过手机app程序操控小车运行,制作分2个部分(app制作,蓝牙小车硬件以及程序制作),先完成第一个部分app制作,本次app是通过androidstudio软件来制作安卓…

MySQL - 事务隔离级别

MySQL 事务 本文所说的 MySQL 事务都是指在 InnoDB 引擎下,MyISAM 引擎是不支持事务的。 数据库事务指的是一组数据操作,事务内的操作要么就是全部成功,要么就是全部失败 事务具有原子性(Atomicity)、一致性&#xff0…

Appium python自动化测试系列之移动自动化测试!

1.1 移动自动化测试现状 因为软件行业越来越发达,用户的接受度也在不断提高,所以对软件质量的要求也随之提高,当然这个也要分行业,但这个还是包含了大部分。因为成本、质量的变化现在对自动化测试的重视度越来越高,在…

空中“千里眼” 复亚环保监测无人机助力生态保护

生态环境保护是全球共同关注的重要议题,为了持续改善环境、加强执法效能,复亚智能环保监测无人机在环保领域大显身手。该智能系统为环境执法人员提供了全新的工具,使其能够在无人机的“千里眼”下,及时发现和制止环境违法行为&…

Qt学习笔记

参考文章:(部分内容转载自以下文章) 【Qt】边学边写之Qt教程(零基础)-CSDN博客QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识)-CSDN博客 1.创建Qt项目 1…

自定义类型详解(1)

文章目录 目录1. 结构体1.1 结构的基础知识1.2 结构的声明1.3 特殊的声明1.4 结构的自引用1.5 结构体变量的定义和初始化1.6 结构体内存对齐1.7 修改默认对齐数1.8 结构体传参 2. 位段2.1 什么是位段2.2 位段的内存分配2.3 位段的跨平台问题2.4 位段的应用 3. 枚举3.1 枚举类型…