join()在线程中的用途是什么?

我在学习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()的用途是什么,因为它不是守护进程,即使我删除它,我也看不到任何变化

377187 次浏览

join()方法

阻塞调用线程,直到调用join()方法的线程终止。

来源:http://docs.python.org/2/library/threading.html

直接从文档

< p >加入((超时)) 等待线程终止。这将阻塞调用线程,直到调用join()方法的线程终止—正常或通过未处理的异常终止—或者直到可选的超时发生

这意味着生成td的主线程会等待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完成。

一个有点笨拙的ascii-art来演示机制: join()可能是由主线程调用的。它也可以由另一个线程调用,但会不必要地使图复杂化

__abc0调用应该放在主线程的轨道中,但是为了表达线程关系并尽可能地保持它的简单,我选择将它放在子线程中。

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(t)对非守护线程和守护线程都起作用时,主线程(或主进程)应该等待t秒,然后可以进一步在自己的进程上工作。在t秒的等待时间内,两个子线程都应该做它们能做的事情,比如打印一些文本。在t秒之后,如果非守护线程仍然没有完成它的任务,它仍然可以在主进程完成它的任务之后完成它,但是对于守护线程来说,它错过了它的机会窗口。然而,它最终会在python程序退出后死亡。有什么不对的地方请指正。

您可能会说:“使用join()有什么用?”实际上,这和“关闭文件有什么用,因为python和操作系统会在程序退出时为我关闭文件?”的答案是一样的。

这只是一个好的编程问题。你应该在代码中线程应该继续运行的地方join()你的线程,要么是因为你必须确保线程不会干扰你自己的代码,要么是因为你想在一个更大的系统中正确地运行。

您可能会说“我不希望我的代码延迟给出答案”,只是因为join()可能需要额外的时间。在某些情况下,这可能是完全有效的,但现在你需要考虑到你的代码“留下了粗糙的东西,让python和操作系统来清理”。如果您这样做是出于性能原因,我强烈建议您记录该行为。如果您正在构建其他人希望使用的库/包,则尤其如此。

除了性能原因之外,没有理由不join(),并且我认为您的代码不需要很好地执行

谢谢你的这篇文章——它也帮了我很多。

我今天学了一些关于.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()视为主线程上的“hold”——它在某种程度上解除线程的线程,并在主线程继续执行之前在主线程中顺序执行。它确保主线程向前移动之前线程已经完成。请注意,这意味着如果在调用join()之前线程已经完成,也没关系——当调用join()时,主线程会立即被释放。

事实上,我刚刚想到主线程会在d.t join()上等待,直到线程d结束,然后才移动到t.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"

它产生这样的输出(注意print语句是如何相互衔接的)。

$ 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()结束之前,所有三个线程都完成了,主线程继续执行打印,然后t2.join(),然后打印,然后t3.join(),然后打印。

修正的欢迎。我也是线程的新手。

(注意:如果你感兴趣的话,我正在为DrinkBot编写代码,我需要线程来并发地运行配料泵,而不是按顺序运行——这样就可以减少等待每种饮料的时间。)

下面的例子演示了.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

在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!


'''

With join - interpreter将等待您的进程获得完成终止

>>> 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?

没有join -解释器不会等待进程得到终止

>>> t = Thread(target=sam)
>>> t.start()
started
>>> print 'yes done' #without join interpreter wont wait until process get terminated
yes done
>>> waiting for 10sec

主线程(或任何其他线程)加入其他线程有几个原因

  1. 线程可能已经创建或持有(锁定)一些资源。调用连接的线程可以代表它清除资源

  2. Join()是一个自然的阻塞调用,用于调用连接的线程在被调用的线程终止后继续执行。

如果一个python程序没有加入其他线程,python解释器仍然会代表它加入非守护线程。

这里似乎误解了同步和异步处理之间的区别。

线程是用来执行子过程的,大多数时候是在“并行”上。或“;concurrent"时尚(取决于设备是否有多处理器)。但是,并发有什么意义呢?在大多数情况下,它是关于通过应用“分而治之”的思想来改进流程的性能。让几个线程(子进程)执行一个“部分”;整个过程同时,然后有一个"最终"将所有子流程的结果进行组合(连接;因此有了“join”;方法)。

当然,为了达到这种效率上的提高,被分成线程的部分必须是“互斥的”。(也就是说,它们不共享需要更新的值……——在并行计算中被称为“临界区”;-)。如果至少有一个值由两个或多个线程更新,那么其中一个必须等待另一个“完成”;它的更新,否则得到不一致的结果(即,两个拥有银行账户的人打算在ATM机取一定数量的钱…如果没有一个适当的机制来“锁定”;或“;protects"变量“平衡”;在这两种ATM设备中,取款会完全搞乱余额的最终值,给账户所有者带来明显的严重财务问题)。

那么,回到并行计算中线程的目的:让所有线程完成各自的部分,并使用&;join&;让他们“回来”;到主要过程,以便每个单独的结果是“巩固”;变成全球性的。

例子吗?有很多,但让我们列举几个解释清楚的:

  • 矩阵乘法:让每个线程用矩阵a的一个向量乘以整个第二个矩阵B,得到矩阵c的一个向量。最后,把所有得到的向量放在一起“显示”。在本例中,尽管矩阵B被所有线程使用,但它的值从未被更新或修改(只读)。

  • 求和,海量数字数组的乘积(数千个值的数组,无论是整数还是浮点数)。让线程执行部分和/乘积(例如,如果你必须和10K个值,创建5个线程,每个线程都有2K个值);然后用"join"让它们返回主进程,并将所有5个线程的结果相加。

    理论上,该进程将执行2000 + 5个步骤(在5个线程中同时执行2000个步骤,加上主进程中最后5个小计的总和)。但是在实践中,5个线程进行自己的2000个数字求和所花费的时间完全是可变的,因为这里涉及到不同的因素(处理器速度、电流流,或者是否是web服务、网络延迟等等)。然而,在“最坏的情况”中投入的时间,在“最慢的情况”中投入的时间。线程采取,加上5个结果的最后和步骤。此外,在实践中,一个线程要完成整个工作的20%,不太可能比一个单独的连续进程要完成100%的工作(当然,这也取决于要处理的样本的大小……在相同的5个线程中,10K个值的和与10个值的和的优势是不一样的……这是不切实际的,不值得)。

  • 快速排序:我们都知道快速排序是如何工作的。然而,如果我们在两个线程中执行它:一个处理奇数,一个处理偶数,这是有机会改进它的。然后递归执行,在某些时候,它将两个线程的结果连接起来,并以一种不需要太多重复的方式进行最终快速排序,因为在两个线程完成初始工作后,数字将得到充分的排序。这是一个相当大的和无序数量的项目在性能上的严重增益。通过对其背后的逻辑进行一些安排,有可能使用三个线程,但其收益实际上是最小的,不值得编程。但是,两个线程有一个不错的性能(时间)增益。

所以,“join”的用法;在python中(或等价于其他“并发”;语言)具有重要的意义;但这在很大程度上取决于编程人员理解他/她想要“平行ellizee”。以及他/她如何熟练地将算法拆分为需要并行化的正确步骤,以及在主进程中需要保留哪些步骤。这更多的是一个“逻辑”问题。思考不是编程的“反模式”。

  • join()等待非守护进程和守护进程线程完成。

  • 如果没有join(),非守护线程正在运行,并与主线程并发完成。

  • 如果没有join(),守护线程与主线程并发运行,当主线程完成时,如果守护线程仍在运行,守护线程将在未完成的情况下退出。

因此,下面的join()daemon=False守护进程(线程) (守护进程默认为False):

import time
from threading import Thread


def test1():
for _ in range(3):
print("Test1 is running...")
time.sleep(1)
print("Test1 is completed")
    

def test2():
for _ in range(3):
print("Test2 is running...")
time.sleep(1)
print("Test2 is completed")
# Here
thread1 = Thread(target=test1, daemon=False)
thread2 = Thread(target=test2, daemon=False)
# Here
thread1.start()
thread2.start()
thread1.join() # Here
thread2.join() # Here
print("Main is completed")

或者,下面有join()daemon=True(非守护线程):

# ...
# Here
thread1 = Thread(target=test1, daemon=True)
thread2 = Thread(target=test2, daemon=True)
# Here
# ...
thread1.join() # Here
thread2.join() # Here
print("Main is completed")

join()等待__ABC1和Test2非守护进程或守护进程线程完成。因此,Main is completed__ABC1和Test2线程完成后被打印出来,如下所示:

Test1 is running...
Test2 is running...
Test1 is running...
Test2 is running...
Test1 is running...
Test2 is running...
Test1 is completed
Test2 is completed
Main is completed

并且,如果没有使用join(),并且如果下面有daemon=False(非守护线程):

# ...
# Here
thread1 = Thread(target=test1, daemon=False)
thread2 = Thread(target=test2, daemon=False)
# Here
# ...
# thread1.join()
# thread2.join()
print("Main is completed")

__ABC0和Test2非守护线程主线程同时运行和完成。因此,Main is completed__ABC0和Test2线程完成之前被打印出来,如下所示:

Test1 is running...
Test2 is running...
Main is completed
Test1 is running...
Test2 is running...
Test1 is running...
Test2 is running...
Test1 is completed
Test2 is completed

并且,如果没有使用join(),并且如果下面有daemon=True守护进程(线程):

# ...
# Here
thread1 = Thread(target=test1, daemon=True)
thread2 = Thread(target=test2, daemon=True)
# Here
# ...
# thread1.join()
# thread2.join()
print("Main is completed")

__ABC0和Test2守护线程主线程同时运行。因此,Main is completed__ABC0和Test2守护线程完成之前被打印,而当主线程完成时,__ABC0和Test2守护线程将在未完成的情况下退出,如下所示:

Test1 is running...
Test2 is running...
Main is completed