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

Python标准库 threading 的 start 和 join 的使用

python 的多线程机制可以的适用场景不适合与计算密集型的,因为 GIL 的存在,多线程在处理计算密集型时,实际上也是串行的,因为每个时刻只有一个线程可以获得 GIL,但是对于 IO 处理来说,不管是网络IO还是文件读写IO还是数据库IO,由于从用户态切换到内核态时,此时线程就陷入等待,线程让出对应 CPU,此时就可以切换到其他线程上继续执行任务,总的来说, python 的多线程机制适用于处理 IO 密集型任务。
这里引申出多线程机制的相关应用,python 的标准库中已经为我们提供了 threading模块,我们可以根据其中的 Thread类 进行线程的相关处理,主要就是创建,运行,阻塞,判断是否存活等操作:

  1. Thread(target=func, args=(), name=“myname”)
  2. Thread.is_alive()
  3. Thread.start()
  4. Thread.join()

但 python 的标准库的线程类仅提供了些简单操作,更多的线程控制,实际上并没有,比如针对超时或者对正在运行的线程停掉等,而且只要子线程 start() 后,其运行就脱离控制了,即使 join(timeout=10) 设置,也只是针对 is_alive() 进行属性的更改,这一点 golang 就在 goroutine 中做得很好,这里不是讨论重点。
接下来,我们就来看看线程类的使用吧。

1.start()后立即join()操作

很多刚使用 python 的人可能在 start() 后就立即 join(),这里会有问题,具体怎样呢,我们看看示例:

import time, datetime
import threading
import sysdef foo(sleep=2):print("当前thread: [{}]".format(threading.current_thread().name))time.sleep(sleep)print("thread: [{}] end.".format(threading.current_thread().name))def multiThread_v1():"""version1: 多个线程start后再join:return:"""print("[{}] [{}] start...".format(datetime.datetime.now(), sys._getframe().f_code.co_name))t1 = threading.Thread(target=foo, name="t1")t2 = threading.Thread(target=foo, name="t2")t3 = threading.Thread(target=foo, name="t3")t4 = threading.Thread(target=foo, name="t4")t5 = threading.Thread(target=foo, name="t5")t1.start()t1.join()t2.start()t2.join()t3.start()t3.join()t4.start()t4.join()t5.start()t5.join()print("[{}] [{}] end...".format(datetime.datetime.now(), sys._getframe().f_code.co_name))if __name__ == "__main__":multiThread_v1()

以下是运行结果:

[2023-04-13 10:40:55.820157] [multiThread_v1] start...
当前thread: [t1]
thread: [t1] end.
当前thread: [t2]
thread: [t2] end.
当前thread: [t3]
thread: [t3] end.
当前thread: [t4]
thread: [t4] end.
当前thread: [t5]
thread: [t5] end.
[2023-04-13 10:41:05.833481] [multiThread_v1] end...

可以看到本来我们创建5个子线程,想着可以并发跑,实际上是串行的,那多线程还有啥意义呢,还不如主线程里串行执行。

这里就要主要到 join() 的作用了,当 start() 后,子线程就开始运行了,我们通过调用 join(),这里就是阻塞主线程,告诉主线程,你得等我子线程运行完才能执行接下来的逻辑,多个子线程都这样,能不是串行执行了吗。

2.常见的应用

下面介绍一种常见的多线程的应用,通过下面的编码实现多线程执行并发的效果:

def multiThread_v3():print("[{}] [{}] start...".format(datetime.datetime.now(), sys._getframe().f_code.co_name))t_list = []for i in range(5):arg = 5 if i % 2 == 1 else 4t = threading.Thread(target=foo, args=(arg,), name="thread_"+str(i))t_list.append(t)for t in t_list:t.start()for t in t_list:t.join()print("[{}] [{}] end...".format(datetime.datetime.now(), sys._getframe().f_code.co_name))if __name__ == "__main__":multiThread_v3()

以下是执行结果:

[2023-04-13 10:46:28.393077] [multiThread_v3] start...
当前thread: [thread_0]
当前thread: [thread_1]
当前thread: [thread_2]
当前thread: [thread_3]
当前thread: [thread_4]
thread: [thread_4] end.thread: [thread_0] end.thread: [thread_2] end.thread: [thread_1] end.
thread: [thread_3] end.
[2023-04-13 10:46:33.395467] [multiThread_v3] end...

代码中通过设置几个sleep 5秒,几个sleep 4秒,模拟不同的处理耗时,可以看到,从开始到结束,线程单个时间总和应该是 4+5+4+5+4=22秒,实际上只运行5秒就全部结束了,我们还是回到 start() 和 join() 的功能上来分析,start() 后,都在跑子线程,通过 join(), 阻塞主线程,由于子线程都已经在运行,实际上的耗时取决于耗时最长的那个,也就是 sleep 5秒的的线程,所以 thread_0/2/4几乎同时结束,运行4秒,接着是thread_3/5,运行5秒,实际在业务时实现时,我们并不能预知耗时情况,比如涉及到网络抖动、磁盘IO等,所以通过遍历 join() 就更合理点。

补充

thread中join()函数的作用:如果thread是某个子线程,则调用thread.join()的作用是确保thread子线程执行完毕后才能执行下一个线程。下面第一个例子中没有调用join()函数,故没有这个限制,所有线程执行顺序都不定。

第二个例子中在每个子线程启动start()后马上调用了join()函数,这就确保了对于每一个子线程,必须等它执行完毕后才能执行下一个程序,故子线程是按顺序执行的,且主线程中的print()方法是在所有的子线程执行完毕后才执行。

第三个例子中,对于子线程启动start()后没有马上调用join()函数,故子线程的执行顺序是不确定的,但是主线程中的print()前调用了每个子线程的join()函数,故print()要在所有的子线程执行完毕后才能执行。

(1)没有使用join()函数,线程执行顺序不定,主线程可能在所有子线程执行完之前就执行了

(2)修改部分代码如下:每次启动子线程后,调用一次join()函数,可以看出线程按顺序执行,且主线程在所有子线程执行完之 后才执行。

(3)修改部分代码如下:可以看出子线程执行顺序不定,但是主线程是在所有子线程执行完毕之后才执行的。

参考文章:

python标准库threading
聊聊python的标准库 threading 的中 start 和 join 的使用注意事项
Python多线程:Threading中join()函数的理解
Python 多线程

http://www.lryc.cn/news/525991.html

相关文章:

  • 无公网IP 外网访问媒体服务器 Emby
  • 【数据结构】_顺序表
  • [MySQL]数据库表内容的增删查改操作大全
  • 解决双系统引导问题:Ubuntu 启动时不显示 Windows 选项的处理方法
  • Java面试题2025-Spring
  • CentOS7安装使用containerd
  • Redis 集群模式入门
  • WinDBG查找C++句柄泄露
  • Linux查看服务器的内外网地址
  • 深入MapReduce——引入
  • Oracle之开窗函数使用
  • 航空客户价值的数据挖掘与分析(numpy+pandas+matplotlib+scikit-learn)
  • 云原生时代,如何构建高效分布式监控系统
  • 什么是CIDR技术? 它是如何解决路由缩放问题的
  • Unity URP 获取/设置 Light-Indirect Multiplier
  • 用Python和Tkinter标准模块建立密码管理器
  • PyQt5菜单加多页签实现
  • 关注搜索引擎蜘蛛压力
  • Python3 OS模块中的文件/目录方法说明三
  • 2024年终总结:技术成长与突破之路
  • mysql-06.JDBC
  • 使用python调用JIRA6 进行OAuth1认证获取AccessToken
  • HTML5使用favicon.ico图标
  • 黑龙江锅包肉:酸甜香酥的东北经典
  • Unity阿里云OpenAPI 获取 Token的C#【记录】
  • winfrom项目,引用EPPlus.dll实现将DataTable 中的数据保存到Excel文件
  • 【C++基础】多线程并发场景下的同步方法
  • C语言#define TSLP0 (TSLP_Regdef *)TSENSORO BASE ADDR)的含义?
  • 微信小程序wxs实现UTC转北京时间
  • 提示词的艺术 ---- AI Prompt 进阶(提示词框架)