Python 线程中 join() 的用途是啥?
Posted
技术标签:
【中文标题】Python 线程中 join() 的用途是啥?【英文标题】:What is the use of join() in Python threading?Python 线程中 join() 的用途是什么? 【发布时间】:2013-02-11 16:21:23 【问题描述】:我在研究python线程,遇到join()
。
作者告诉如果线程处于守护模式,那么我需要使用join()
,以便线程可以在主线程终止之前完成自己。
但我也看到他使用t.join()
,即使t
不是daemon
示例代码是这样的
import threading
import time
import logging
logging.basicConfig(level=logging.DEBUG,
format='(%(threadName)-10s) %(message)s',
)
def daemon():
logging.debug('Starting')
time.sleep(2)
logging.debug('Exiting')
d = threading.Thread(name='daemon', target=daemon)
d.setDaemon(True)
def non_daemon():
logging.debug('Starting')
logging.debug('Exiting')
t = threading.Thread(name='non-daemon', target=non_daemon)
d.start()
t.start()
d.join()
t.join()
我不知道t.join()
的用途是什么,因为它不是守护进程,即使我删除它也看不到任何变化
【问题讨论】:
标题+1。 “加入”似乎是专门设计用于鼓励性能不佳(通过不断创建/终止/销毁线程)、GUI 锁定(在事件处理程序中等待)和应用程序关闭失败(等待不间断线程终止)。注意 - 不仅仅是 Python,这是一个跨语言的反模式。 很多答案只是给出了 .join() 的作用。但我认为实际的问题是 .join() 的意义何在,它似乎与在没有线程的情况下运行脚本具有相同的效果。 【参考方案1】:此示例演示了.join()
操作:
import threading
import time
def threaded_worker():
for r in range(10):
print('Other: ', r)
time.sleep(2)
thread_ = threading.Timer(1, threaded_worker)
thread_.daemon = True # If the main thread is killed, this thread will be killed as well.
thread_.start()
flag = True
for i in range(10):
print('Main: ', i)
time.sleep(2)
if flag and i > 4:
print(
'''
Threaded_worker() joined to the main thread.
Now we have a sequential behavior instead of concurrency.
''')
thread_.join()
flag = False
输出:
Main: 0
Other: 0
Main: 1
Other: 1
Main: 2
Other: 2
Main: 3
Other: 3
Main: 4
Other: 4
Main: 5
Other: 5
Threaded_worker() joined to the main thread.
Now we have a sequential behavior instead of concurrency.
Other: 6
Other: 7
Other: 8
Other: 9
Main: 6
Main: 7
Main: 8
Main: 9
【讨论】:
【参考方案2】:加入 - 解释器将等到您的流程完成或终止
>>> from threading import Thread
>>> import time
>>> def sam():
... print 'started'
... time.sleep(10)
... print 'waiting for 10sec'
...
>>> t = Thread(target=sam)
>>> t.start()
started
>>> t.join() # with join interpreter will wait until your process get completed or terminated
done? # this line printed after thread execution stopped i.e after 10sec
waiting for 10sec
>>> done?
没有加入 - 解释器不会等到进程终止,
>>> t = Thread(target=sam)
>>> t.start()
started
>>> print 'yes done' #without join interpreter wont wait until process get terminated
yes done
>>> waiting for 10sec
【讨论】:
【参考方案3】:主线程(或任何其他线程)加入其他线程有几个原因
线程可能已创建或持有(锁定)某些资源。加入调用线程可能能够代表它清除资源
join() 是一个自然的阻塞调用,让调用join的线程在被调用线程终止后继续。
如果一个python程序没有加入其他线程,python解释器仍然会代表它加入非守护线程。
【讨论】:
【参考方案4】:在 python 3.x 中,join() 用于将线程与主线程连接,即当 join() 用于特定线程时,主线程将停止执行,直到连接线程的执行完成。
#1 - Without Join():
import threading
import time
def loiter():
print('You are loitering!')
time.sleep(5)
print('You are not loitering anymore!')
t1 = threading.Thread(target = loiter)
t1.start()
print('Hey, I do not want to loiter!')
'''
Output without join()-->
You are loitering!
Hey, I do not want to loiter!
You are not loitering anymore! #After 5 seconds --> This statement will be printed
'''
#2 - With Join():
import threading
import time
def loiter():
print('You are loitering!')
time.sleep(5)
print('You are not loitering anymore!')
t1 = threading.Thread(target = loiter)
t1.start()
t1.join()
print('Hey, I do not want to loiter!')
'''
Output with join() -->
You are loitering!
You are not loitering anymore! #After 5 seconds --> This statement will be printed
Hey, I do not want to loiter!
'''
【讨论】:
【参考方案5】:当为非守护线程和守护线程创建join(t)
函数时,主线程(或主进程)应该等待t
秒,然后可以继续在自己的进程上工作。在t
秒的等待时间内,两个子线程都应该做他们能做的事情,比如打印一些文本。在t
秒之后,如果非守护线程仍然没有完成它的工作,并且在主进程完成它的工作之后它仍然可以完成它,但是对于守护线程,它只是错过了它的机会窗口。但是,它最终会在python程序退出后死掉。如有错误请指正。
【讨论】:
【参考方案6】:一个有点笨拙的 ascii-art 来演示机制:
join()
大概是由主线程调用的。它也可以由另一个线程调用,但会使图表不必要地复杂化。
join
-calling 应该放在主线程的轨道上,但是为了表达线程关系并尽可能简单,我选择放在子线程中。
without join:
+---+---+------------------ main-thread
| |
| +........... child-thread(short)
+.................................. child-thread(long)
with join
+---+---+------------------***********+### main-thread
| | |
| +...........join() | child-thread(short)
+......................join()...... child-thread(long)
with join and daemon thread
+-+--+---+------------------***********+### parent-thread
| | | |
| | +...........join() | child-thread(short)
| +......................join()...... child-thread(long)
+,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, child-thread(long + daemonized)
'-' main-thread/parent-thread/main-program execution
'.' child-thread execution
'#' optional parent-thread execution after join()-blocked parent-thread could
continue
'*' main-thread 'sleeping' in join-method, waiting for child-thread to finish
',' daemonized thread - 'ignores' lifetime of other threads;
terminates when main-programs exits; is normally meant for
join-independent tasks
所以你看不到任何变化的原因是你的主线程在你的join
之后什么都不做。
您可以说join
(仅)与主线程的执行流程相关。
例如,如果您想同时下载一堆页面以将它们连接成一个大页面,您可以使用线程开始并发下载,但需要等到最后一个页面/线程完成才能开始组装许多中的一页。那是你使用join()
的时候。
【讨论】:
请确认一个守护线程可以join()而不阻塞程序执行? @Aviator45003:是的,通过使用超时参数,例如:demon_thread.join(0.0)
,join()
默认情况下是阻塞的,而不考虑守护进程属性。但是加入一个被妖魔化的线程很可能会带来一大堆麻烦!我现在正在考虑删除守护线程小图中的join()
调用...
@DonQuestion 如果我们设置daemon=True
,如果我们需要在代码末尾设置join()
,我们是否不需要join()
?
@BenyaminJafari:是的。如果没有,那么如果只剩下守护线程,则主线程(=程序)将退出。但是(python)守护线程的本质是主线程不关心这个后台任务是否仍在运行。我会考虑如何在我的回答中详细说明,以解决这个问题。感谢您的评论!
在第一种情况下,当main thread
完成时,程序是否会在不让child-thread(long)
完成运行的情况下完成(即child-thread(long)
没有完全完成)?【参考方案7】:
感谢这个帖子——它也帮助了我很多。
我今天学到了一些关于 .join() 的知识。
这些线程并行运行:
d.start()
t.start()
d.join()
t.join()
这些是按顺序运行的(不是我想要的):
d.start()
d.join()
t.start()
t.join()
特别是,我试图聪明和整洁:
class Kiki(threading.Thread):
def __init__(self, time):
super(Kiki, self).__init__()
self.time = time
self.start()
self.join()
这行得通!但它是按顺序运行的。我可以将 self.start() 放入 __ init __,但不能放入 self.join()。这必须在每个线程启动后完成。
join() 是导致主线程等待线程完成的原因。否则,您的线程将自行运行。
因此,将 join() 视为主线程上的“保留”的一种方法——它在主线程可以继续之前将线程解线程并在主线程中按顺序执行。它确保您的线程在主线程向前移动之前完成。请注意,这意味着如果您的线程在调用 join() 之前已经完成,则可以 - 调用 join() 时,主线程会立即释放。
事实上,我现在才想到,主线程在 d.join() 处等待,直到线程 d 完成后才继续执行 t.join()。
其实说的很清楚,考虑一下这段代码:
import threading
import time
class Kiki(threading.Thread):
def __init__(self, time):
super(Kiki, self).__init__()
self.time = time
self.start()
def run(self):
print self.time, " seconds start!"
for i in range(0,self.time):
time.sleep(1)
print "1 sec of ", self.time
print self.time, " seconds finished!"
t1 = Kiki(3)
t2 = Kiki(2)
t3 = Kiki(1)
t1.join()
print "t1.join() finished"
t2.join()
print "t2.join() finished"
t3.join()
print "t3.join() finished"
它产生这个输出(注意打印语句是如何相互串接的。)
$ python test_thread.py
32 seconds start! seconds start!1
seconds start!
1 sec of 1
1 sec of 1 seconds finished!
21 sec of
3
1 sec of 3
1 sec of 2
2 seconds finished!
1 sec of 3
3 seconds finished!
t1.join() finished
t2.join() finished
t3.join() finished
$
t1.join() 正在阻止主线程。所有三个线程都在 t1.join() 完成之前完成,主线程继续执行 print 然后 t2.join() 然后 print 然后 t3.join() 然后 print。
欢迎指正。我也是线程新手。
(注意:如果您有兴趣,我正在为 DrinkBot 编写代码,我需要线程来同时运行配料泵,而不是按顺序运行 - 等待每杯饮料的时间更短。)
【讨论】:
嘿,我也是python线程的新手,对主线程感到困惑,第一个线程是主线程吗,如果不是,请指导我? 主线程是程序本身。每个线程都是从那里分叉的。然后将它们连接回来——因为在命令 join() 处,程序会等到线程完成后再继续执行。 我认为最大的问题是,当线程的全部目的是并行运行时,为什么要阻止主线程。我认为 join() 的答案是您可能希望并行运行程序的一部分,但您可能会到达主线程的一部分,该部分需要子线程的结果才能继续?【参考方案8】:“使用 join() 有什么用?”你说。真的,这与“关闭文件有什么用,因为当我的程序退出时python和操作系统会为我关闭我的文件?”的答案相同。
这只是一个好的编程问题。您应该在代码中线程应该不再运行的位置加入()您的线程,或者因为您必须确保线程没有运行以干扰您自己的代码,或者您想在更大的系统中正确运行。
您可能会说“我不希望我的代码延迟给出答案”只是因为 join() 可能需要额外的时间。这在某些情况下可能是完全有效的,但您现在需要考虑到您的代码“为 python 和操作系统清理留下了杂乱无章的东西”。如果您出于性能原因这样做,我强烈建议您记录该行为。如果您正在构建期望其他人使用的库/包,则尤其如此。
没有理由不加入(),除了性能原因,我认为您的代码不需要很好地执行。
【讨论】:
你所说的清理线程是不正确的。看一下threading.Thread.join()的源码。该函数所做的只是等待锁,然后返回。实际上没有清理任何东西。 @Collin - 线程本身可能持有资源,在这种情况下,解释器和操作系统确实需要清理“垃圾”。 再看threading.Thread.join()的源码。那里没有任何东西可以触发资源收集。 它不一定(正如你所说,根本不是)持有资源的线程模块,而是线程本身。使用 join() 意味着您正在等待线程完成它想做的事情,这可能包括分配和释放资源。 是否等待不影响线程持有的资源何时释放。我不知道你为什么要打电话给join()
。【参考方案9】:
直接来自docs
加入([超时]) 等到线程终止。这会阻塞调用线程,直到调用其 join() 方法的线程终止(正常或通过未处理的异常)或直到发生可选超时。
这意味着产生t
和d
的主线程等待t
完成,直到它完成。
根据您的程序采用的逻辑,您可能希望等到一个线程完成后再继续您的主线程。
也来自文档:
可以将线程标记为“守护线程”。这个标志的意义在于,当只剩下守护线程时,整个 Python 程序就退出了。
一个简单的例子,假设我们有这个:
def non_daemon():
time.sleep(5)
print 'Test non-daemon'
t = threading.Thread(name='non-daemon', target=non_daemon)
t.start()
结尾是:
print 'Test one'
t.join()
print 'Test two'
这将输出:
Test one
Test non-daemon
Test two
这里主线程显式等待t
线程完成,直到它第二次调用print
。
或者,如果我们有这个:
print 'Test one'
print 'Test two'
t.join()
我们会得到这个输出:
Test one
Test two
Test non-daemon
在这里,我们在主线程中完成我们的工作,然后等待t
线程完成。在这种情况下,我们甚至可以删除显式加入 t.join()
,程序将隐式等待 t
完成。
【讨论】:
您能否对我的代码进行一些更改,以便我可以看到t.join()
的不同之处。通过添加一些睡眠或其他东西。此刻,即使我是否使用它,我也可以看到程序中的任何变化。但是对于 damemon,如果我使用 d.join()
,我可以看到它的退出,当我不使用 d.join() 时我看不到它【参考方案10】:
方法join()
阻塞调用线程,直到调用join()方法的线程终止。
来源:http://docs.python.org/2/library/threading.html
【讨论】:
那么join有什么用呢?请参阅 OP 问题,不要只是解释文档 @DonQuestion 我什至尝试在不使用t.join()
的情况下在非守护线程中添加 sleep.timer(20) 并且程序在终止之前仍然等待它。我在我的代码中没有看到 t.join()
的任何使用
见我的回答,以获得进一步的解释。关于非恶魔中的 sleep.timer -> 恶魔线程与其父线程的生命周期解耦,因此父/兄弟线程不会受到恶魔化线程生命周期的影响,反之亦然.
“加入”和“阻止”术语令人费解。 “阻塞”表明调用进程被“阻塞”,无法执行它仍然必须做的任何事情,而实际上它只是被阻止终止(返回到操作系统),而不是更多。出于同样的原因,有一个主线程调用一个子线程来“加入”它(即终止)并不是那么明显。所以,Don Q,谢谢你的解释。以上是关于Python 线程中 join() 的用途是啥?的主要内容,如果未能解决你的问题,请参考以下文章