У чому полягає використання join () в потоці Python?


197

Я вивчав різьблення пітона і натрапив 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 для заголовка. Здається, що "Join" спеціально розроблений для заохочення низької продуктивності (шляхом постійного створення / завершення / знищення потоків), блокування графічного інтерфейсу (очікування в обробниках подій) та збоїв у відключенні програми (очікування припинення безперебійних потоків). Зауважте - не лише Python, це антимоделі міжмовної мови.
Мартін Джеймс

Відповіді:


287

Дещо незграбне мистецтво продемонструвати механізм: 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().


Підтвердьте, що демонізований потік може бути з'єднаний () без блокування виконання програми?
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 ([timeout]) Зачекайте, поки нитка закінчиться. Це блокує викликовий потік до тих пір, поки поток, чий метод 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 (), і основний потік рухається, щоб виконати друк, потім t2.join (), потім надрукувати, потім t3.join (), потім надрукувати.

Виправлення вітаються. Я також новачок у різьбі.

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


Привіт, я також новачок у python Threading і плутаюсь про головну нитку. Чи перша тема є основною ниткою, якщо ні, будь ласка, керуйте мені?
Рохіт Хатрі

Основна нитка - сама програма. Кожну з ниток розщеплюємо звідти. Потім вони приєднуються назад - тому що при команді join () програма чекає, поки нитка буде закінчена до її продовження.
Kiki Jewell

15

Метод join ()

блокує викликовий потік до тих пір, поки не припиняється потік, виклик якого методу join ().

Джерело: http://docs.python.org/2/library/threading.html


14
так у чому полягає користь приєднання? дивіться питання ОП, не перефразовуйте документи
Дон Питання

@DonQuestion я навіть спробував додати sleep.timer (20) у недемонову нитку без використання t.join()і програма все ще чекає її до завершення. я не бачу жодного використання t.join()тут у своєму коді
user192362127

див. мою відповідь, для подальшого пояснення. щодо вашого sleep.timer в недемонові -> демонова нитка відокремлена від часу життя її батьківської нитки, і тому батьківські / братові нитки не впливатимуть на життя демонізованою ниткою і навпаки .
Дон Питання

2
Термінологія "приєднання" та "блок" викликає здивування. "Заблокований" говорить про те, що процес виклику "заблокований" виконувати будь-яку кількість речей, які він все ще повинен робити, хоча насправді він просто заблокований до припинення (повернення в ОС), не більше. З тієї ж точки зору, не так очевидно, що існує головна нитка, яка закликає дочірній потік "приєднатися" до неї (тобто припинити). Отже, Дон Q, дякую за пояснення.
RolfBly

4

Просте розуміння,

з приєднанням - перекладач буде чекати, поки процес завершиться або припиниться

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