1.前言
今天写完B站视频爬虫并加入多线程时,我加入了信号量,因此照理来说应该会同时有好几个文件在写入,但是最后却发现程序只是按照写的顺序来进行,就像没有写多线程一样,这是我之前从没有发生过的,而我检查了几遍也没有找出错误,毕竟程序确实没有报错,也成功下载了我需要的视频。
2.join方法
突然我注意到了一个地方
join()
现在写一个例子:
2.1 代码如下:
import time
from threading import Thread, Lock, BoundedSemaphore
def process(name):
# lock.acquire()
print(name + '正在运行-->' + str(time.time()))
time.sleep(1)
print(name + '正在运行-->' + str(time.time()))
time.sleep(1)
print(name + '正在运行-->' + str(time.time()))
time.sleep(1)
print(name + '结束运行-->' + str(time.time()))
# lock.release()
if __name__ == '__main__':
# lock = Lock()
names = ['1号', '2号', '3号']
for name in names:
process(name)
# work = Thread(target=process, args=(name, lock))
# work.start()
print('所有线程结束运行-->' + str(time.time()))
首先我不用多线程运行一遍
2.1结果如下:
这是预料之中的结果。
而当我加入多线程后
2.2 代码如下:
import time
from threading import Thread, Lock, BoundedSemaphore
def process(name):
# lock.acquire()
print(name + '正在运行-->' + str(time.time()))
time.sleep(1)
print(name + '正在运行-->' + str(time.time()))
time.sleep(1)
print(name + '正在运行-->' + str(time.time()))
time.sleep(1)
print(name + '结束运行-->' + str(time.time()))
# lock.release()
if __name__ == '__main__':
# lock = Lock()
names = ['1号', '2号', '3号']
for name in names:
# process(name)
work = Thread(target=process, args=(name, ))
work.start()
print('所有线程结束运行-->' + str(time.time()))
2.2 结果如下:
每个线程几乎是同时开始,甚至后面结果输出都在同一行了,而我们也可以注意到,主线程中我们希望最后输出的“所有线程结束运行”却提前就运行了,这不是我们想要的,因此join方法就起到了这个作用。因此我们将每个线程都写上一个join方法,以前我都是这么写,我会先建一个空列表放置多线程,然后写个循环给每个线程加上join()
2.3 核心部分代码如下:
if __name__ == '__main__':
# lock = Lock()
names = ['1号', '2号', '3号']
works = []
for name in names:
# process(name)
work = Thread(target=process, args=(name, ))
work.start()
works.append(work)
for work in works:
work.join()
print('所有线程结束运行-->' + str(time.time()))
运行一遍
2.3 结果如下:
可以看到,正如我们看到的,目的实现了。
3.被误导
昨天我看了一篇博客,发现作者使用多线程每次都是在start方法后加上join方法,我当时就觉得他那很省事,都不用再建一个空列表,于是我就记住了这种写法,写个例子
3.1 核心代码如下:
if __name__ == '__main__':
# lock = Lock()
names = ['1号', '2号', '3号']
# works = []
for name in names:
# process(name)
work = Thread(target=process, args=(name, ))
work.start()
# works.append(work)
work.join()
print('所有线程结束运行-->' + str(time.time()))
3.1 运行结果
好家伙,我要这多线程有何用,这和单线程有啥区别啊,害我找这个问题半天,最后却发现问题出在这里,我还以为是python库之间不兼容呢。
4.我的猜想
程序会发生我上面写的那样的结果,我猜想是这样的:
一群人(分线程)要进入一个小屋(线程池),因为他们的任务在小屋里面,而主线程是这群人的主管,他把每个人每个人送进去后自己再进去(for循环创建线程),而分线程事情很多很耗时,主管安排完所有人后就进去了并说我进来了(输出“所有线程结束运行”),而此时分线程还没有完成任务,也就是上面的2.2提到的情况。
为了避免这种情况,我们想让主管在门外多待会,但又不能对他做啥,于是分线程们想出了个办法,进去后给门加锁(注意:这个锁与threading.Lock类无关,只是比喻),起初他们在所有人(不包括主管,也就是主线程)都进去后每个人在门上加把锁(for循环添加join方法),只有在所有线程都工作完了,各自吧各自的锁解开后,们才能打开,主管才能进来并说我进来了(输出“所有线程结束运行”)。
而对应的,循环里面start()后直接加入join方法就相当于第一个进去的分线程一进去就把门给锁了,他做完事后再打开门让别人进去,而后面的线程也是如此。也就造成了和单线程一样了。
4.1 关于2.2猜想
4.2 关于2.3猜想
4.3 关于3.1的猜想
按照我的猜想,只要让花费时间最多的,也就是最晚完成任务的分线程加锁就可以达到和2.2中效果,但是我们往往无法知晓哪个分线程最后完成,于是让每个分线程都加上一把锁。按照我的猜想,如果让最先完成任务的分线程加一把锁,其他分线程不加锁,那么主线程应该会在第一个完成任务的分线程后输出
4.4 代码如下:
if __name__ == '__main__':
# lock = Lock()
names = ['1号', '2号', '3号']
works = []
for name in names:
# process(name)
work = Thread(target=process, args=(name, ))
work.start()
works.append(work)
time.sleep(1)
# work.join()
works[0].join()
print('所有线程结束运行-->' + str(time.time()))
4.4结果如下:
1号分线程一结束,主线程就输出了,这验证了我的猜想没错。
5.总结
多线程的join函数还是要在所有线程都start后再运行,并且每个线程都要加上join函数。