因为中文网络上充斥着各种关于多线程, 多进程的流言, 这次来当一次流言终结者 (请务必看完”结论”和”注意点”).
结论
- 多线程间的切换由 OS 调度, 不可控
- 主线程默认不会等待子线程结束, 主线程执行完毕立即结束 (join 方法让主线程等待子线程执行完毕再结束)
- 主线程结束后, 子线程不会退出, 直到子线程执行完毕后自行退出 (setDemon 方法让主线程结束后立即杀死子线程)
证明
from threading import Thread
from time import sleep
def func_test():
for i in range(10):
print('child {}'.format(i))
sleep(1)
def main():
t = Thread(target=func_test)
# t.daemon = True
t.start()
for i in range(5):
print('father {}'.format(i))
sleep(1)
# t.join()
error = 1/0 # 这里确保出错让主线程退出
print('father alive!') # father alive! 不会被打印, 证明主线程已退出
main()
输出结果:
child 0
father 0
father 1
child 1
father 2
child 2
father 3
child 3
father 4
child 4
Traceback (most recent call last):
File "D:/Data/code/test.py", line 319, in <module>
main()
File "D:/Data/code/test.py", line 316, in main
error = 1/0 # 这里确保出错让主线程退出
ZeroDivisionError: division by zero
child 5
child 6
child 7
child 8
child 9
可以很清楚的看到主线程遇到错误退出后, 子线程仍保持运行, 直到子线程也执行完毕.
注意点
- t.strat() 后子线程开始执行, t.start() 在哪, 子线程就从哪开始执行.
- t.join() 方法的位置很重要, 表示主线程在哪里等待子线程, 如果 t.start() 后立即 t.join() 则主线程被阻塞在原地等待子线程执行完毕, 相当于还是单线程, 所以 t.join() 应该放在主线程的最后位置.
- t.join(5) 表示主线程在此处会等待子线程 5 秒, 注意不需要任何前提条件都可以使用(不需要设置守护线程).
- t.daemon = True 或 t.setDaemon(True) 是将子线程设置为守护线程, 什么是守护线程, 就像是打倒 BOSS 后所有小兵原地自爆, 此选项使主线程结束后立即杀死子线程. 因为原本主线程就不会等待子线程, 主线程结束后, 子线程会变成”孤儿”继续运行, 直到结束, 这是默认情况 (孤儿线程可能会引起各种各样的问题, 应尽力避免).
- 经证实, Process 在这些特性上与 Thread 相同.
拓展
多线程中如何获取子线程的返回值 ?
class MyThread(Thread):
def __init__(self, func, args=()):
super(MyThread, self).__init__()
self.func = func
self.args = args
def run(self):
self.result = self.func(*self.args)
def get_result(self):
return self.result
调用 t.get_result() 方法获取返回值.