python 多线程中的 join 和 daemon
Posted 车子 chezi
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python 多线程中的 join 和 daemon相关的知识,希望对你有一定的参考价值。
第一关:简单的 join()
import threading
import time
def run():
time.sleep(2)
print('当前线程的名字是: ', threading.current_thread().name)
time.sleep(2)
if __name__ == '__main__':
print('这是主线程:', threading.current_thread().name)
thread_list = []
for i in range(5):
t = threading.Thread(target=run)
thread_list.append(t)
for t in thread_list:
t.start()
start_time = time.time()
thread_list[0].join()
print('一共用时:', time.time() - start_time)
print('主线程结束!', threading.current_thread().name)
执行结果是:
这是主线程: MainThread
当前线程的名字是: Thread-1
当前线程的名字是: Thread-5当前线程的名字是: Thread-4当前线程的名字是: 当前线程的名字是:
Thread-2
Thread-3
一共用时: 4.014104843139648
主线程结束! MainThread
为了弄清楚 join() 的作用,我关注代码 23 行的用时。
第 23 行,主线程阻塞,直到 thread_list[0] 退出后才往下执行。
看打印结果第 7 行,用时 4s,这是好理解的,因为 thread_list[0] 大概用 4s。说明主线程确实阻塞了,一直阻塞到 thread_list[0] 执行完毕。
我们把代码修改一下,把 run() 函数的 sleep 时间增加。
def run():
time.sleep(5)
print('当前线程的名字是: ', threading.current_thread().name)
time.sleep(5)
预期结果是主线称大约阻塞 10s
执行结果是:
这是主线程: MainThread
当前线程的名字是: 当前线程的名字是: Thread-5当前线程的名字是: Thread-1当前线程的名字是: Thread-3
当前线程的名字是: Thread-2
Thread-4
一共用时: 10.009864091873169
主线程结束! MainThread
和预期相符。
如果把 run 函数改成死循环,比如
def run():
while(True):
time.sleep(2)
print('当前线程的名字是: ', threading.current_thread().name)
time.sleep(2)
那么主线程就会一直阻塞,根本不会打印 “一共用时:…”,而是一直打印 “当前线程的名字是:…”
这也印证了主线程确实阻塞了,等待子线程退出,如果不退出就一直阻塞。
第二关:join(timeout)
在上面的例子中,如果子线程不返回,父线程就一直阻塞。如果需求是父线程阻塞一段时间,时间到了以后,就算子线程不返回,父线程也可以继续向下执行,那么我们可以给 join() 传入一个时间参数。意思是最多等待这么多,等不到我就继续往下执行了。
实验代码是:
import threading
import time
def run():
time.sleep(2)
print('当前线程的名字是: ', threading.current_thread().name)
time.sleep(2)
if __name__ == '__main__':
print('这是主线程:', threading.current_thread().name)
thread_list = []
for i in range(5):
t = threading.Thread(target=run)
thread_list.append(t)
for t in thread_list:
t.start()
start_time = time.time()
thread_list[0].join(2)
print('一共用时:', time.time() - start_time)
print('主线程结束!', threading.current_thread().name)
第 23 行:父线程最多等待 2s,等不到就继续执行
打印结果是:
这是主线程: MainThread
一共用时: 2.0001044273376465
主线程结束! MainThread
当前线程的名字是: 当前线程的名字是: 当前线程的名字是: Thread-2
Thread-3
Thread-1
当前线程的名字是: Thread-4
当前线程的名字是: Thread-5
可以看到,父线程确实只阻塞了 2s。需要强调的是,主线程阻塞,不会对子线程造成任何影响,子线程依然执行自己的代码,根本不存在什么“主线程等待一段时间然后杀死子线程”,这是谬论。
当然,如果你传入参数是 10,主线程不一定非要等 10s,如果子线程执行了 3s 就返回了,那么主线程会立刻往下执行,而不是继续等待 7s。
例如把代码第 23 行改成
thread_list[0].join(6)
执行结果是:
这是主线程: MainThread
当前线程的名字是: 当前线程的名字是: Thread-2 Thread-3当前线程的名字是: 当前线程的名字是:
当前线程的名字是: Thread-1
Thread-4
Thread-5
一共用时: 4.028920888900757
主线程结束! MainThread
第三关:setDaemon(True)
先上代码:
import threading
import time
def run():
time.sleep(2)
print('当前线程的名字是: ', threading.current_thread().name)
time.sleep(2)
if __name__ == '__main__':
print('这是主线程:', threading.current_thread().name)
thread_list = []
for i in range(5):
t = threading.Thread(target=run)
t.setDaemon(True)
thread_list.append(t)
for t in thread_list:
t.start()
start_time = time.time()
print('一共用时:', time.time() - start_time)
print('主线程结束!', threading.current_thread().name)
和之前不一样的是第 17 行,多加了 t.setDaemon(True)
,这句话的意思是把线程的 daemon 属性设成 True。daemon 有守护神的意思,也可以说把 t 设置为守护线程,守护谁呢?守护父线程吗?咱们后面会分析。
网上充斥着这样的解释:如果一个子线程的 daemon 被设为 True,那么父线程结束了,这个子线程就立刻被干掉。是不是这样呢?我们看执行结果:
这是主线程: MainThread
一共用时: 0.0
主线程结束! MainThread
似乎是这样,主线程一执行完,子线程就被杀死了,连打印都来不及。
我们把 15-18 行代码改一下:
for i in range(5):
t = threading.Thread(target=run)
# t.setDaemon(True)
thread_list.append(t)
thread_list[0].setDaemon(True)
也就是说,仅仅设置 thread_list[0] 的 daemon 为 True。预期结果是主线程一结束,thread_list[0] 就被杀死了,其余的子线程会继续执行。
结果是:
这是主线程: MainThread
一共用时: 0.0
主线程结束! MainThread
当前线程的名字是: Thread-3当前线程的名字是: Thread-2
当前线程的名字是: 当前线程的名字是: Thread-5
当前线程的名字是: Thread-4
Thread-1
这就奇怪了,不是 Thread-1 被杀死了吗,怎么后面还有打印呢?
肯定是我们的理解有问题。实际上,官方是这样解释 daemon 的:
A thread can be flagged as a “daemon thread”. The significance of this flag is that the entire Python program exits when only daemon threads are left.
以上摘自 https://docs.python.org/3/library/threading.html,黑体字是我加的。
也就是说,如果父线程执行完毕,剩下的都是守护线程,那么这些守护线程也不用再执行了,直接强制退出;如果父线程执行完毕,剩下了 3 个守护线程和 1 个非守护线程,那么这 3 个守护线程还会继续执行,当非守护线程结束的时候,这些守护线程才会强制退出。
我们做个实验:
import threading
import time
def run(cnt):
while cnt:
time.sleep(1)
print('当前线程的名字是: ', threading.current_thread().name)
time.sleep(1)
cnt = cnt - 1
print(threading.current_thread().name, ' 退出')
if __name__ == '__main__':
print('这是主线程:', threading.current_thread().name)
thread_list = []
cnt_list = [2, 4, 4, 4, 4]
for i in range(5):
t = threading.Thread(target=run, args=(cnt_list[i],))
t.setDaemon(True)
thread_list.append(t)
thread_list[0].setDaemon(False)
for t in thread_list:
t.start()
start_time = time.time()
print('一共用时:', time.time() - start_time)
print('主线程结束!', threading.current_thread().name)
5-11:每个子线程执行时间不一样,根据传参 cnt 定
第 16 行,增加了cnt_list = [2, 4, 4, 4, 4]
,意图是让 thread_list[0] 执行时间短一些,仅执行 while 循环 2 次,其他子线程执行 4 次
18-23:先把所有子线程设为守护线程,后面再把 thread_list[0] 设为非守护线程(偷懒了)
输出结果:
这是主线程: MainThread
一共用时: 0.0
主线程结束! MainThread
当前线程的名字是: 当前线程的名字是: 当前线程的名字是: Thread-5 当前线程的名字是: Thread-2
Thread-1
Thread-4
当前线程的名字是: Thread-3
当前线程的名字是: Thread-1当前线程的名字是: 当前线程的名字是: 当前线程的名字是: Thread-3
当前线程的名字是: Thread-5
Thread-4
Thread-2
Thread-1 退出
可以发现,父线程退出后,守护线程并没有退出,当非守护线程 Thread-1 退出后,其他守护线程强制退出。
由此可见,守护线程不仅仅守护父线程,也会守护其他非守护线程。
以上就是本文的全部内容。欢迎各位老师批评斧正。
以上是关于python 多线程中的 join 和 daemon的主要内容,如果未能解决你的问题,请参考以下文章