文章目录
- 守护线程
- 守护线程的继承性
- join()阻塞
守护线程
在 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()
,不然就会变成单线程咯!