聊聊python的标准库 threading 的中 start 和 join 的使用注意事项
python
的多线程机制可以的适用场景不适合与计算密集型的,因为 GIL
的存在,多线程在处理计算密集型时,实际上也是串行的,因为每个时刻只有一个线程可以获得 GIL
,但是对于 IO
处理来说,不管是网络IO
还是文件读写IO
还是数据库IO
,由于从用户态切换到内核态时,此时线程就陷入等待,线程让出对应 CPU
,此时就可以切换到其他线程上继续执行任务,总的来说, python
的多线程机制适用于处理 IO
密集型任务。
这里引申出多线程机制的相关应用,python
的标准库中已经为我们提供了 threading模块
,我们可以根据其中的 Thread类
进行线程的相关处理,主要就是创建
,运行
,阻塞
,判断是否存活
等操作:
- Thread(target=func, args=(), name="myname") - Thread.is_alive() - Thread.start() - Thread.join()
但 python
的标准库的线程类仅提供了些简单操作,更多的线程控制,实际上并没有,比如针对超时或者对正在运行的线程停掉等,而且只要子线程 start()
后,其运行就脱离控制了,即使 join(timeout=10)
设置,也只是针对 is_alive()
进行属性的更改,这一点 golang
就在 goroutine
中做得很好,这里不是讨论重点。
接下来,我们就来看看线程类的使用吧。
1.start()后立即join()操作
很多刚使用 python
的人可能在 start()
后就立即 join()
,这里会有问题,具体怎样呢,我们看看示例:
import time, datetime import threading import sys def 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 4 t = 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()
就更合理点。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
2021-04-12 python 计算均值、方差、标准差 Numpy,Pandas