第一个问题:
import queue
import threading
import time
for i in range(20):
a = threading.Thread(target = worker)
a.setDaemon(True)
a.start()
我想知道,在这个for循环里,这里设置的setDaemon
里所绑定的主线程都是一样的吗?还是说因为for循环把每个子线程的守护线程都绑定为了上一个子线程?
第二个问题:
import queue
import threading
import time
aqueue = queue.Queue()
for i in range(10):
aqueue.put(i)
def worker():
while True:
time.sleep(5)
item = aqueue.get()
print(item)
for i in range(20):
a = threading.Thread(target = worker)
a.setDaemon(True)
a.start()
aqueue.join()
在添加queue
模块后,让我疑惑的是,程序在跑完aqueue
里的所有项后却没有退出,我查了一下官网queue.join()
的定义是Blocks until all items in the queue have been gotten and processed.这里子线程和主线程都执行完了,为什么没有退出呢?
我也刚看,试了好久,初步知道怎么回事了.
先回答你的两个问题:
1、setDaemon所绑定的都是主线程,都是一样的,即运行py文件第一次创建的那个线程(也是主进程),有且只有一个
2、queue.join()如果单独使用会被无限挂起,字面翻译为等待队列为空,再执行别的操作.但是他等待的队列不是我们创建的aqueue,而是一个与aqueue长度相等的一个"需要完成的子线程"队列,他判断的很可能是这个列表,当这个列表为空时queue.join()才成立,所以如楼上的那位所说queue.join()要结合aqueue.task_done()函数来使用,aqueue.task_done()的意思是每task_done一次 就从"需要完成的子线程"队列里删掉一个元素,这样在最后join的时候根据队列长度是否为零来判断队列是否结束,aqueue.task_done()用在子线程函数中,这样queue.join()才知道完成了多少个子线程,才不会无限挂起,也就是为什么你没退出的原因
但是你的程序也有问题:
主要的问题是,守护线程机制:主线程运行完毕去结束子线程时,由于有大量的子线程还在time.sleep(5),结束这些子线程会触发异常:
这是因为:
下面的一些说法是错误的,可以看我后面的补充,这里就不改了,是一种猜测,但是应该确实不是这样
喂不饱的子线程:你aqueue.put(i)了是10个元素,但是却运行了20个子线程来"吃"这10个元素,又因为这20个线程都是守护进程,当10个吃饱了,这个10个卡在了item = aqueue.get()等待接受元素,另外的那些很有可能卡在了time.sleep(5)上,而主线程运行完毕,准备结束所有的守护进程,我判断,结束卡在item = aqueue.get()的守护进程不会报错,而结束其他程序,如time.sleep会触发异常,这也是为啥楼上那位吧你的for i in range(20):改成了for i in range(1):的原因.
还有两种退出的方式:
1、是在子线程里判断队列是否为空:
为空就退出
2、主线程通过queue向子线程发送退出命令
其实这里一般用pipe管道的,Queue队列不好针对特定的线程进行退出操作,大概的伪码为:
主线程:
子线程""
所以多进程,多线程还是要控制好程序逻辑,控制好调度.
我没用过守护进程,我一般用的是普通的用户进程,这样主线程跑完会等待所有子线程运行完毕再结束
其实还没完=_=:
后面的都是我的猜测,算是附加的,选修:
这程序的细节也值得我们关注:
这程序如果真去实践的话很容易炸,以下都是我的猜测
如上所述,我们修改程序,注意注释
10个数据10个线程,time.cleep(5)秒结果会怎么样?运行正常?
很不幸运的炸了23333333
有一定的几率报错,和上面一样:
我电脑上大部分运行正常,只有几次会这样,为什么会这样?
换成10个数据9个线程就不会出错了
我猜测的情况是,初步认为:
直接看完整的输出,注意看有注释的地方:
看主要的几句
先这样吧,可以自己运行下程序看看
看了另一个答主的,使用了a.join()也是一种选择
改成发送10个数据,用9个线程跑,效率是最高的(理论)