Что такое использование join() в потоке python

Я изучал потоки 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

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

Ответ 2

Прямо от 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.

Ответ 3

Спасибо за эту тему - это тоже помогло мне.

Я узнал кое-что о .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(), и основной поток перемещается, чтобы выполнить печать, затем t2.join(), затем распечатайте затем t3.join(), затем напечатайте.

Исправления приветствуются. Я также новичок в потоковом режиме.

(Примечание: в случае, если вам интересно, я пишу код для DrinkBot, и мне нужно нарезать резьбу для запуска насосов ингредиентов одновременно, а не последовательно - меньше времени, чтобы ждать каждого напитка.)

Ответ 4

Метод join()

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

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

Ответ 5

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

Ответ 6

В простых словах подождите, пока поток не будет завершен

Ответ 7

Этот пример демонстрирует действие .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

Ответ 8

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

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

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

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