因为中文网络上充斥着各种关于多线程, 多进程的流言, 这次来当一次流言终结者 (请务必看完”结论”和”注意点”).

结论

  1. 多线程间的切换由 OS 调度, 不可控
  2. 主线程默认不会等待子线程结束, 主线程执行完毕立即结束 (join 方法让主线程等待子线程执行完毕再结束)
  3. 主线程结束后, 子线程不会退出, 直到子线程执行完毕后自行退出 (setDemon 方法让主线程结束后立即杀死子线程)

证明

  1. from threading import Thread
  2. from time import sleep
  3. def func_test():
  4. for i in range(10):
  5. print('child {}'.format(i))
  6. sleep(1)
  7. def main():
  8. t = Thread(target=func_test)
  9. # t.daemon = True
  10. t.start()
  11. for i in range(5):
  12. print('father {}'.format(i))
  13. sleep(1)
  14. # t.join()
  15. error = 1/0 # 这里确保出错让主线程退出
  16. print('father alive!') # father alive! 不会被打印, 证明主线程已退出
  17. main()

输出结果:

  1. child 0
  2. father 0
  3. father 1
  4. child 1
  5. father 2
  6. child 2
  7. father 3
  8. child 3
  9. father 4
  10. child 4
  11. Traceback (most recent call last):
  12. File "D:/Data/code/test.py", line 319, in <module>
  13. main()
  14. File "D:/Data/code/test.py", line 316, in main
  15. error = 1/0 # 这里确保出错让主线程退出
  16. ZeroDivisionError: division by zero
  17. child 5
  18. child 6
  19. child 7
  20. child 8
  21. child 9

可以很清楚的看到主线程遇到错误退出后, 子线程仍保持运行, 直到子线程也执行完毕.

注意点

  1. t.strat() 后子线程开始执行, t.start() 在哪, 子线程就从哪开始执行.
  2. t.join() 方法的位置很重要, 表示主线程在哪里等待子线程, 如果 t.start() 后立即 t.join() 则主线程被阻塞在原地等待子线程执行完毕, 相当于还是单线程, 所以 t.join() 应该放在主线程的最后位置.
  3. t.join(5) 表示主线程在此处会等待子线程 5 秒, 注意不需要任何前提条件都可以使用(不需要设置守护线程).
  4. t.daemon = True 或 t.setDaemon(True) 是将子线程设置为守护线程, 什么是守护线程, 就像是打倒 BOSS 后所有小兵原地自爆, 此选项使主线程结束后立即杀死子线程. 因为原本主线程就不会等待子线程, 主线程结束后, 子线程会变成”孤儿”继续运行, 直到结束, 这是默认情况 (孤儿线程可能会引起各种各样的问题, 应尽力避免).
  5. 经证实, Process 在这些特性上与 Thread 相同.

拓展

多线程中如何获取子线程的返回值 ?

  1. class MyThread(Thread):
  2. def __init__(self, func, args=()):
  3. super(MyThread, self).__init__()
  4. self.func = func
  5. self.args = args
  6. def run(self):
  7. self.result = self.func(*self.args)
  8. def get_result(self):
  9. return self.result

调用 t.get_result() 方法获取返回值.