当前位置: 首页 > news >正文

python中的多线程【threading】

1.多线程

将程序理解为一条河流的主干,主线程是河水(程序)的主要流经途径,但是当河水十分多的时候,只有一条主干无法充分运载河水,此时需要扩展一些分支河道进行扩展。多线程就是这样的意思,我们一个主程序(.py文件)是一个进程,一个进程可以有多个线程,有一个主线程从头贯穿到尾顺序执行,但是当程序十分庞大时并且存在可以合并的情况(例如循环读取)就可以使用多线程进行扩展,用更少的时间完成更多的任务,。

在 Python 中,多线程是通过 threading 模块来实现的。多线程适用于 I/O 密集型任务(例如文件操作、网络请求)。但由于 GIL(全局解释器锁,是一种互斥锁,确保同一时间只有一个线程能够执行代码。在执行I/O任务时解释器会释放GIL(读写任务不需要CPU参与))的存在,对于 CPU 密集型任务,推荐使用多进程

2.定义多线程

python中定义线程需要使用threading库,其中target是创建线程中运行的函数,args是向target函数中传递的参数,下面的命令即可创建一个线程,可以使用for循环创建多个线程。创建线程之后需要使用start()将其激活才会运行该线程。

threading.Thread(target=process_data, args=(data,))

下例是创建多线程的示例代码,创建的线程运行同一个代码,适用于计算数组元素的平方,并且会等待两秒,如果仅仅用循环顺序进行的话需要等待10s,但是使用多线程的话仅需要使用2s多(在join章节会展示结果)。

import threading
import timeif __name__ == "__main__":start_time = time.time()data_list = [1, 2, 3, 4, 5]  # 数据数组def process_data(data):square_data = data ** 2time.sleep(2)  # 模拟处理时间print(f"{threading.current_thread().name} 处理数据 {data} 的平方: {square_data}\n")threads = []# 创建线程并处理数据for data in data_list:thread = threading.Thread(target=process_data, args=(data,))threads.append(thread)thread.start()print("------------")end_time = time.time()print(f"所有数据处理完成: {end_time - start_time} 秒")# ###################
# 执行结果如下:
------------
------------
------------
------------
------------
所有数据处理完成: 0.001992940902709961 秒
Thread-1 (process_data) 处理数据 1 的平方: 1
Thread-5 (process_data) 处理数据 5 的平方: 25Thread-2 (process_data) 处理数据 2 的平方: 4Thread-4 (process_data) 处理数据 4 的平方: 16Thread-3 (process_data) 处理数据 3 的平方: 9

执行for循环添加多线程,添加完成之后,主进程(程序顺序执行就相当于是主进程)继续往下执行,没有考虑到子线程还在运行。所以可以看到执行时间远小于每个子线程(创建的多线程) 等待的两秒。实际使用时,我们往往需要使用子线程的计算结果才能向下继续运行主线程的命令。这时就需要考虑使用join阻塞主线程,等待子线程执行完毕之后才继续执行主线程。

3.多线程中的join

join是用来访问进程是否结束的命令,当join访问的进程未结束时会阻塞主进程,等待进程结束之后,释放阻塞使主进程接着执行。

在上述代码中加入join命令如下:

import threading
import timeif __name__ == "__main__":start_time = time.time()data_list = [1, 2, 3, 4, 5]  # 数据数组def process_data(data):square_data = data ** 2time.sleep(2)  # 模拟处理时间print(f"{threading.current_thread().name} 处理数据 {data} 的平方: {square_data}\n")threads = []# 创建线程并处理数据for data in data_list:thread = threading.Thread(target=process_data, args=(data,))threads.append(thread)thread.start()print("------------")# 等待所有线程完成for thread in threads:thread.join()print("============")end_time = time.time()print(f"所有数据处理完成: {end_time - start_time} 秒")# ##############
# 执行结果如下:
------------
------------
------------
------------
------------
Thread-2 (process_data) 处理数据 2 的平方: 4
Thread-1 (process_data) 处理数据 1 的平方: 1
Thread-3 (process_data) 处理数据 3 的平方: 9============Thread-5 (process_data) 处理数据 5 的平方: 25Thread-4 (process_data) 处理数据 4 的平方: 16============
============
============
============
所有数据处理完成: 2.0078837871551514 秒

从上面的结果可以看出,使用join阻塞主线程之后,主线程会等待子线程执行完毕之后才会进行。

多线程几乎同时被添加并启动,之一旦线程启动,它们的执行就由操作系统调度器管理,是并发的。所以打印出的结果并非是按照顺序的,运行多次结果可能都不尽相同。

4. 多线程任务中的Lock

当多个线程需要同时访问一个变量时可能回赐您在数据竞争关系。例如,需要账户有1000现在有一个函数能够从账户中取800,有两个进程分别执行该函数,按照正常逻辑讲当第一个线程执行完之后账户只剩200,第二个线程执行的话会报错无法取出800。但是由于是多线程运行,在第二个线程在访问账户余额时,第一个线程可能未将800取出,导致第二个线程进入了取出步骤,所以执行了两次取出操作,导致账户余额成为-600,这显然是不对的。

出现上述的原因就是由于多线程任务访问了公共资源且对公共资源有所修改导致数据出现了不一致现象。threading模块中Lock用于解决该问题,Lock的核心作用是实现多线程间的同步,确保多个线程在访问共享资源时不会出现数据竞争或不一致的问题。

上述问题复现代码如下:

import threading
import timeclass Account:def __init__(self, balance):self.balance = balancedef withdraw(account, amount):if account.balance >= amount:time.sleep(0.2)print(threading.current_thread().name, f"取款{amount}成功")account.balance -= amountprint(threading.current_thread().name, f"余额{account.balance}")else:print(threading.current_thread().name, f"取款{amount}失败,余额不足")if __name__ == '__main__':account = Account(1000)ta = threading.Thread(name="ta", target=withdraw, args=(account, 800))tb = threading.Thread(name="tb", target=withdraw, args=(account, 800))ta.start()tb.start()# ##############
# 执行结果如下:tb 取款800成功
tb 余额200
ta 取款800成功
ta 余额-600

其中,time.sleep的延时导致了线程tb访问时余额还有1000。

使用线程锁解决该问题

import threading
import timeclass Account:def __init__(self, balance):self.balance = balancelock = threading.Lock()def withdraw(account, amount):with lock:if account.balance >= amount:time.sleep(0.2)print(threading.current_thread().name, f"取款{amount}成功")account.balance -= amountprint(threading.current_thread().name, f"余额{account.balance}")else:print(threading.current_thread().name, f"取款{amount}失败,余额不足")if __name__ == '__main__':account = Account(1000)ta = threading.Thread(name="ta", target=withdraw, args=(account, 800))tb = threading.Thread(name="tb", target=withdraw, args=(account, 800))ta.start()tb.start()# ##############
# 执行结果如下:ta 取款800成功
ta 余额200
tb 取款800失败,余额不足

当程序执行线程ta时,运行到with lock:时,会成功获取“锁”,需要等待锁中的内容执行完毕才会释放锁,此时若线程tb也运行到with lock:,由于锁被占用,tb线程会被阻塞进入等待,直到ta执行完毕锁被释放。

或许你有疑问,这不是和单线程一样了吗?程序在线性运行!

但需要注意的是这里锁住的是“共享资源的临界区域”,其余启动、初始化等非临界区代码仍然可以并发执行。上述列举的程序比较简单所以看起来是顺序执行。

http://www.dtcms.com/a/565807.html

相关文章:

  • 做电影小视频在线观看网站谷歌找网站后台
  • 开发常用软件清单
  • 网站设计外文文献免费网站正能量不用下载
  • 结合HOG特征与支持向量机(SVM)的车牌字符识别系统
  • 长沙高端网站制作公司丹灶建网站
  • Nacos配置安全治理:把数据库密码从YAML里请出去
  • RSA 算法数学原理
  • 网站建设应注意哪些事项wordpress 收费 视频
  • 外贸找客户有什么网站平台公司会倒闭吗
  • P5091 【模板】扩展欧拉定理
  • C盘爆满急救指南
  • 天台高端网站建设公司专业网站设计公司价格
  • 温州专业微网站制作公司天猫网页版
  • 免费网络推广网站王烨重生
  • 微气象仪:精准感知微环境气象变化
  • svn使用之创建分支进行开发
  • 拍卖网站建设公司社交类电商平台
  • Elasticsearch 8 安装与配置
  • 邯郸做网站推广找谁盛大游戏优化大师
  • HR1124S/HR1154D:赋能直流电机高效安全运行的核心芯片
  • 化妆品网站网页设计营销培训师
  • 网站积分解决方案标书制作模板
  • 前端直接渲染Markdown
  • Vue 3.5 新特性——useTemplateRef:简化模板引用的革命!
  • 网站开发技术三大件网页设计与制作教程期末考试试题
  • 赣州制作网站企业个人简历自我评价
  • 寄存器与存储器的区别(TLB和Cache,指令流水线分析)
  • 思维链(CoT)× 智能体(Agent)× 提示词(Prompt)讲解
  • MAC-SQL 图1
  • 第一章 WPF概述