【Python爬虫】多线程之 守护线程 & join()阻塞

1,106 阅读4分钟

「这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战」。

系列精选
Python爬虫速度很慢?并发编程了解一下吧

守护线程

Python 多线程中,主线程的代码运行完后,如果还有其他子线程还未执行完毕,那么主线程会等待子线程执行完毕后再结束;这就会有产生一个问题,如果有一个线程被设置成无限循环,那么意味着整个主线程( Python 程序)就不能结束。举个例子看一下。

import threading
import time

# 非守护线程
def normal_thread():
    for i in range(10000):
        time.sleep(1)
        print(f'normal thread {i}')

print(threading.current_thread().name, '线程开始')
thread1 = threading.Thread(target=normal_thread)
thread1.start()
print(threading.current_thread().name, '线程结束')

上面结果可以看到,主线程( MainThread )虽然已经结束了,但子线程仍然在运行,当子线程运行完后,整个程序才真正的结束。那如果想主线程结束的同时终止其他未运行完的线程,可以将线程设置为守护线程,如果程序当中仅剩下守护线程还在执行并且主程序也结束,那么 Python 程序就能够正常退出。threading 模块提供了两种守护线程的设置方式。

threading.Thread(target=daemon_thread, daemon=True)

thread.setDaemon(True)

import threading
import time

# 守护线程(强制等待1s)
def daemon_thread():
    for i in range(5):
        time.sleep(1)
        print(f'daemon thread {i}')

# 非守护线程(无强制等待)
def normal_thread():
    for i in range(5):
        print(f'normal thread {i}')

print(threading.current_thread().name, '线程开始')
thread1 = threading.Thread(target=daemon_thread, daemon=True)
thread2 = threading.Thread(target=normal_thread)
thread1.start()
# thread1.setDaemon(True)
thread2.start()
print(threading.current_thread().name, '线程结束')

上述将 thread1 设置为守护线程,程序在非守护线程主线程( MainThread )运行完成后,直接结束,因此 daemon_thread() 中的输出语句没有来得及执行。图中的输出结果显示 MainThread 线程结束 之后仍然在输出 normal_thread() 函数中的内容,原因是主线程结束到守护线程强制停止这个过程还需要一段时间

守护线程的继承性

子线程会继承当前线程的 daemon 属性,主线程默认是非守护线程,因此在主线程中新建的线程默认也都是非守护线程,但在守护线程中创建新的线程时,就会继承当前线程的 daemon 属性,子线程也是守护线程

join()阻塞

在多线程爬虫中,一般通过多线程同时爬取不同页面的信息,然后统一进行解析处理,统计存储,这就需要等待所有子线程都执行完毕,才能继续下面的处理,这就需要用到 join() 方法了。

join() 方法的作用就是阻塞(挂起)其他线程(未启动的线程与主线程),等待被调用线程运行结束后再唤醒其他线程的运行。看个例子。

import threading
import time

def block(second):
    print(threading.current_thread().name, '线程正在运行')
    time.sleep(second)
    print(threading.current_thread().name, '线程结束')

print(threading.current_thread().name, '线程正在运行')

thread1 = threading.Thread(target=block, name=f'thread test 1', args=[3])
thread2 = threading.Thread(target=block, name=f'thread test 2', args=[1])

thread1.start()
thread1.join()

thread2.start()

print(threading.current_thread().name, '线程结束')

上面只对 thread1 使用 join() ,注意使用 join() 的位置,它是在 thread2.start() 启动之前执行的,执行后 thread2 与主线程均被挂起,只有 thread1 线程执行完成之后,thread2 与 主线程才会执行,由于这里 thread2 不是守护线程,所以当主线程(MainThread)执行完毕后,thread2 还是会继续运行。

看到这里,是不是有个疑问?如果按照上面代码的执行过程,整个程序完全变成了单线程程序,这就是因为 join() 的使用位置不当造成的。我们稍微改一下上面的代码。

import threading
import time

def block(second):
    print(threading.current_thread().name, '线程正在运行')
    time.sleep(second)
    print(threading.current_thread().name, '线程结束')

print(threading.current_thread().name, '线程正在运行')

thread1 = threading.Thread(target=block, name=f'thread test 1', args=[3])
thread2 = threading.Thread(target=block, name=f'thread test 2', args=[1])

thread1.start()
thread2.start()

thread1.join()
print(threading.current_thread().name, '线程结束')

现在程序就是真正的多线程了,此时使用了 join() 方法时,只有主线程被挂起,当 thread1 执行完毕后,才会执行主线程。

最后需要说明,join() 方法的阻塞是不分对象的,与是否守护线程,是否主线程无关。使用时需要注意,想要真正的多线程运行就要启动所有的子线程后,再调用 join() ,不然就会变成单线程咯!



这就是本文所有的内容了,如果感觉还不错的话。❤ 点个赞再走吧!!!❤


对于刚入门 Python 或是想要入门 Python 的小伙伴,可以通微信搜 【Python新视野】,一起交流学习,都是从新手走过来的,有时候一个简单的问题卡很久,但可能别人的一点拨就会恍然大悟,由衷的希望大家能够共同进步。