python关于守护线程join方法的小细节

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猜想

Created with Raphaël 2.2.0 分线程进入线程池 主线程进入线程池 主线程进入线程池并输出,分线程尚未完成任务

4.2 关于2.3猜想

Created with Raphaël 2.2.0 分线程进入线程池并加锁(与Lock无关) 主线程试图进入 线程池是否打开? 分线程已完成任务并开锁,主线程进入线程池并输出 yes no

4.3 关于3.1的猜想

Created with Raphaël 2.2.0 分线程进入线程池,一进去就上锁 下一个分线程准备进入线程池 线程池是否打开? 分线程进入线程池,分线程是否全部进入线程池 主线程准备进入线程池 线程池是否打开? 主线程进入线程池并输出 yes no yes no yes no

按照我的猜想,只要让花费时间最多的,也就是最晚完成任务的分线程加锁就可以达到和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函数。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值