我有一个疑问。我见过开发人员编写类似以下代码的示例:
import threading
def do_something():
return true
t = threading.Thread(target=do_something)
t.start()
t.join()
我知道
join()
指示解释器等待线程完全执行。但如果我不写t.join()
怎么办?线程会自动关闭吗?以后还会再使用吗?Python 线程只是一个常规的操作系统线程。如果不加入它,它仍然保持与当前线程并发运行。当目标函数完成或引发异常时,它最终会死亡。不存在“线程重用”这样的东西,一旦它死了,它就安息了。
除非线程是“守护线程”(通过构造函数参数
daemon
或分配daemon
属性),否则它将在程序退出之前隐式加入,否则,它会被突然杀死。
在Python中编写多线程程序时要记住的一件事是,由于臭名昭著的全局解释器锁,它们的用途有限。简而言之,使用线程不会使 CPU 密集型程序变得更快。仅当您执行涉及等待的操作时它们才有用(例如,您等待线程中发生某些文件系统事件)。
join
部分表示主程序将等待线程结束后再继续。如果没有join,主程序将结束,线程将继续。
现在如果将
daemon
参数设置为“True”,则表示该线程将依赖于主程序,如果主程序先结束则该线程也会结束。
这里有一个例子可以更好地理解:
import threading
import time
def do_something():
time.sleep(2)
print("do_something")
return True
t = threading.Thread(target=do_something)
t.daemon = True # without the daemon parameter, the function in parallel will continue even your main program ends
t.start()
t.join() # with this, the main program will wait until the thread ends
print("end of main program")
没有守护进程,没有加入:
end of main program
do_something
仅限守护进程:
end of main program
仅加入:
do_something
end of main program
守护进程并加入:
do_something
end of main program
# Note : in this case the daemon parameter is useless
没有
join()
,非守护线程正在运行并与主线程同时完成。
没有
join()
,守护线程与主线程同时运行,当主线程完成时,如果守护线程仍在运行,则守护线程将退出而不完成。
您可以在这篇文章中看到我的回答详细解释它。
使用 Python 3.10.12,如果您不使用
join()
线程,不会发生任何不好的情况。为了测试,我使用了这段代码:
#! /usr/bin/python3
import logging
import threading
import time
def thread_function(name):
logging.info("Thread %2s: starting", name)
time.sleep(20)
logging.info("Thread %2s: finishing", name)
def daemon_thread_function():
logging.info("Daemon : starting")
i = 0
while True:
time.sleep(1)
i = i + 1
logging.info("Daemon : %s seconds have passed", i)
if __name__ == "__main__":
format = "%(asctime)s: %(message)s"
logging.basicConfig(format=format, level=logging.INFO,
datefmt="%H:%M:%S")
for i in range(1, 51):
logging.info("Main : creating and running thread %2s", i)
threading.Thread(target=thread_function, args=(i,)).start()
logging.info("Main : creating and running daemon thread")
threading.Thread(target=daemon_thread_function, args=(), daemon=True).start()
wait = 40
logging.info("Main : sleeping %s seconds", wait)
time.sleep(wait)
logging.info("Main : finished waiting")
在启动此代码后的前 20 秒内,您可以使用
ps -eLf
、ls /proc/MAINPROCESSID/task
和许多其他命令来验证此测试应用程序中实际上有 52 个线程在运行:主线程、50 个前台子线程和守护线程。应用程序启动20秒后,前台子线程全部退出,只剩下两个线程:主线程和守护线程。 ps -eLf
不显示任何具有未读状态或类似状态的“僵尸线程”,因此未加入的线程不会阻塞任何操作系统资源!
当总时间经过 40 秒后,主线程也退出,守护线程也完成,并且 python 应用程序退出。因此,未连接的线程也不会在 Python 内部阻塞任何资源,特别是,它们不会让守护线程不必要地运行。
只有一个建议:如果您打算永远不加入线程,请不要保留对周围创建的线程的引用,以便 python 解释器可以完成其工作并垃圾收集与所有已完成线程相关的所有资源。测试应用程序中的语法
threading.Thread(target=..., args=...).start()
是实现这种“即发即忘”语义的一种可能方法。
当然,请确保使用其他同步方法(如
threading.mutex
或 threading.semaphore
)来跟踪线程的工作。