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


当前回答

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

这只是一个好的编程问题。您应该在代码中线程不应该再运行的地方join()您的线程,因为您必须确保线程的运行不会干扰您自己的代码,或者您希望在更大的系统中正确地运行。

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

除了性能原因之外,没有理由不使用join(),而且我认为您的代码不需要执行得那么好。

其他回答

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

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

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()有什么用?”实际上,这和“关闭文件有什么用,因为python和操作系统会在程序退出时为我关闭文件?”的答案是一样的。

这只是一个好的编程问题。您应该在代码中线程不应该再运行的地方join()您的线程,因为您必须确保线程的运行不会干扰您自己的代码,或者您希望在更大的系统中正确地运行。

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

除了性能原因之外,没有理由不使用join(),而且我认为您的代码不需要执行得那么好。

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

因此,下面的join()和daemon=False(守护线程)(daemon默认为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()等待Test1和Test2非守护进程或守护进程线程完成。因此,Main is completed在Test1和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")

Test1和Test2非守护线程正在与主线程并发地运行和完成。因此,Main is completed在Test1和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")

Test1和Test2守护线程与主线程并发运行。因此,Main is completed在Test1和Test2守护线程完成之前打印,当主线程完成时,Test1和Test2守护线程将在未完成的情况下退出,如下所示:

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

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

线程可能已经创建或持有(锁定)一些资源。调用连接的线程可以代表它清除资源 Join()是一个自然的阻塞调用,用于调用连接的线程在被调用的线程终止后继续执行。

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

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

我今天学了一些关于.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编写代码,我需要线程来并发地运行配料泵,而不是按顺序运行——这样就可以减少等待每种饮料的时间。)