Какая польза от join () в потоке Python?


198

Я изучал потоки 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()как это не демон, и я не вижу изменений, даже если я удалю его


13
+1 за заголовок. «Присоединение», по-видимому, специально разработано для того, чтобы поощрять низкую производительность (путем непрерывного создания / завершения / уничтожения потоков), блокировки графического интерфейса пользователя (ожидание в обработчиках событий) и сбоев при завершении работы приложения (ожидание завершения непрерывных потоков). Обратите внимание - это не только Python, это анти-паттерн между языками.
Мартин Джеймс

Ответы:


288

Несколько неуклюжий 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().


Пожалуйста, подтвердите, что демонизированный поток может быть присоединен () без блокировки выполнения программы?
Aviator45003

@ Aviator45003: Да, с помощью аргумента таймаута , как: demon_thread.join(0.0), join()по умолчанию блокирующего без учета daemonized атрибута. Но присоединение к демонизированному потоку, скорее всего, может привести к неприятностям! Сейчас я собираюсь удалить join()вызов из моей маленькой диаграммы для потока демонов ...
Дон Вопрос

@DonQuestion Итак, если мы включаем daemon=True, не нужно join()ли нам, если нам нужно join()в конце кода?
Беньямин Джафари

@BenyaminJafari: Да. Если нет, то основной поток (= программа) будет завершен, если останется только поток демона. Но природа потока демона (python) заключается в том, что основному потоку все равно, выполняется ли эта фоновая задача. Я подумаю о том, как уточнить это в своем ответе, чтобы прояснить этот вопрос. Спасибо за ваш комментарий!
Дон Вопрос

В первом случае, когда main threadзавершится, завершится ли программа, не дав child-thread(long)завершить самому запуску (т. child-thread(long)Е. Не завершится полностью)?
Skytree

65

Прямо из документов

объединение ([тайм-аут]) Дождитесь завершения потока. Это блокирует вызывающий поток до тех пор, пока поток, чей метод 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 ()
user192362127

34

Спасибо за эту ветку - она ​​мне тоже очень помогла.

Я кое-что узнал о .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 () программа ожидает завершения потока, прежде чем продолжить его выполнение.
Кики Джуэлл

15

Метод join ()

блокирует вызывающий поток до тех пор, пока поток, чей метод join () вызывается, не завершится.

Источник: http://docs.python.org/2/library/threading.html


14
так какая польза от соединения? см. OP вопрос, не перефразируйте документы
Don Question

@DonQuestion Я даже попытался добавить sleep.timer (20) в нить демона без использования, t.join()и программа все еще ждет его до завершения. я не вижу здесь никакого t.join()
смысла

см. мой ответ, для дальнейшего объяснения. относительно вашего sleep.timer в non-demon -> демон-поток отделен от времени жизни своего родительского потока, поэтому время жизни демонизированного потока не повлияет на родительские / родственные потоки, и наоборот ,
Дон Вопрос

2
Терминология «присоединиться» и «блокировать» озадачивает. «Заблокировано» предполагает, что вызывающий процесс «заблокирован» от выполнения любого количества действий, которые он все еще должен сделать, в то время как на самом деле он просто заблокирован от завершения (возврата к ОС), не более. По тому же признаку, не так очевидно, что есть основной поток, вызывающий дочерний поток, чтобы «присоединиться» к нему (т.е. завершить). Итак, Дон Q, спасибо за объяснение.
RolfBly

5

Простое понимание,

with join - интерпретатор будет ждать, пока ваш процесс не будет завершен или завершен

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

1

При создании join(t)функции как для потока, не являющегося демоном, так и для потока демона, основной поток (или основной процесс) должен подождать tнесколько секунд, а затем продолжить работу над своим собственным процессом. В течение tсекунд ожидания оба дочерних потока должны делать то, что они могут, например, распечатывать текст. Через tнесколько секунд, если поток, не являющийся демоном, все еще не завершил свою работу, и он все еще может завершить его после того, как основной процесс завершит свою работу, но для потока демона он просто пропустил свое окно возможностей. Тем не менее, он в конечном итоге умрет после выхода из программы Python. Пожалуйста, поправьте меня, если что-то не так.


1

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

'''

0

Этот пример демонстрирует .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 kills, this thread will be killed too. 
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

0

Есть несколько причин для основного потока (или любого другого потока) присоединиться к другим потокам

  1. Поток мог создать или удерживать (блокировать) некоторые ресурсы. Поток, выполняющий соединение, может очистить ресурсы от своего имени.

  2. join () - это естественный блокирующий вызов для потока, вызывающего соединение, для продолжения после завершения вызываемого потока.

Если программа python не присоединяется к другим потокам, интерпретатор python по-прежнему присоединяется к потокам, не являющимся демонами, от своего имени.


-2

"Какая польза от использования join ()?" ты говоришь. Действительно, это тот же ответ, что и «зачем использовать закрытие файлов, так как python и ОС закроют мой файл для меня, когда выйдет моя программа?».

Это просто вопрос хорошего программирования. Вы должны присоединиться () к вашим потокам в той точке кода, которую поток не должен больше должен работать, либо потому, что вы должны убедиться, что поток не работает, чтобы мешать вашему собственному коду, либо что вы хотите правильно вести себя в большая система.

Вы можете сказать: «Я не хочу, чтобы мой код задерживал предоставление ответа» только из-за дополнительного времени, которое может потребоваться join (). Это может быть совершенно справедливо в некоторых сценариях, но теперь вы должны принять во внимание, что ваш код "оставляет желать лучшего для python и операционной системы для очистки". Если вы делаете это по причинам производительности, я настоятельно рекомендую вам задокументировать это поведение. Это особенно верно, если вы создаете библиотеку / пакет, который, как ожидается, будут использовать другие.

Там нет причин , чтобы не присоединиться (), кроме соображений производительности, и я бы сказал , что ваш код не должен выполнять эту скважину.


6
То, что вы говорите о чистке темы, не соответствует действительности. Взгляните на исходный код Threading.Thread.join (). Все, что делает эта функция - это ждать блокировки и затем возвращаться. Ничего на самом деле не убрано.
Коллин

1
@Collin - Сам поток может содержать ресурсы, в этом случае интерпретатору и ОС действительно нужно будет очистить «бесполезность».
Qneill

1
Опять же, посмотрите на исходный код Threading.Thread.join (). Там нет ничего, что вызывает сбор ресурсов.
Коллин

Это не обязательно (и, как вы говорите, совсем нет) модуль потоков, который содержит ресурсы, но сам поток. Использование join () означает, что вы ожидаете, когда поток завершит выполнение того, что он хотел сделать, что может включать выделение и освобождение ресурсов.
Крис Когдон

2
Ожидание или нет, не влияет на освобождение ресурсов, удерживаемых потоком. Я не уверен, почему вы связываете это с вызовом join().
Коллин
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.