การใช้ join () ในเธรด Python คืออะไร


198

ฉันกำลังศึกษาเกลียวหลามและพบjoin()ว่า

ผู้เขียนบอกว่าหากเธรดอยู่ในโหมด daemon ฉันต้องใช้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 สำหรับชื่อ ดูเหมือนว่า 'เข้าร่วม' ได้รับการออกแบบมาเป็นพิเศษเพื่อส่งเสริมประสิทธิภาพที่ไม่ดี (โดยการสร้าง / ยุติ / ทำลายเธรดอย่างต่อเนื่อง), การล็อก GUI, (รอในตัวจัดการเหตุการณ์) และการปิดแอปล้มเหลว (รอเธรดที่ไม่หยุดชะงัก) หมายเหตุ - ไม่ใช่แค่ Python นี่เป็นรูปแบบการต่อต้านข้ามภาษา
Martin James

คำตอบ:


291

Ascii-art ที่ค่อนข้างซุ่มซ่ามเพื่อแสดงให้เห็นถึงกลไก: ซึ่งjoin()เรียกได้ว่าเป็นเธรดหลัก มันอาจจะถูกเรียกโดยเธรดอื่น แต่จะทำให้แผนภาพซับซ้อนขึ้นโดยไม่จำเป็น

join- การโทรควรอยู่ในแทร็กของเธรดหลัก แต่เพื่อแสดงความสัมพันธ์ของเธรดและทำให้มันเรียบง่ายที่สุดเท่าที่จะทำได้ฉันเลือกที่จะวางมันไว้ใน child-thread แทน

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()นั่นคือเมื่อคุณใช้


โปรดยืนยันว่าอาจมีการรวมเธรด daemonized () โดยไม่มีการบล็อกการทำงานของโปรแกรม?
Aviator45003

@ Aviator45003: ใช่โดยใช้อาร์กิวเมนต์หมดเวลาชอบdemon_thread.join(0.0), join()เป็นค่าเริ่มต้นโดยการปิดกั้นโดยไม่คำนึงถึงแอตทริบิวต์ daemonized แต่การเข้าร่วมเธรดภูตผีปีศาจเปิดโอกาสให้กับปัญหาได้ทั้งหมด! ตอนนี้ฉันกำลังพิจารณาที่จะลบการjoin()โทรออกในไดอะแกรมเล็ก ๆ ของฉันสำหรับ daemon-thread ...
คำถาม Don

@ DonQuestion ดังนั้นหากเราตั้งค่าไว้เราdaemon=Trueไม่จำเป็นต้องใช้join()ถ้าเราต้องการที่ส่วนjoin()ท้ายของรหัส?
Benyamin Jafari

@BenyaminJafari: ใช่ หากไม่เป็นเช่นนั้นเธรดหลัก (= โปรแกรม) จะออกหากเหลือเพียง daemon-thread เท่านั้น แต่ลักษณะของเธรดภูต (หลาม) คือเธรดหลักไม่สนใจว่างานพื้นหลังนี้ยังทำงานอยู่หรือไม่ ฉันจะคิดเกี่ยวกับวิธีการทำอย่างละเอียดในคำตอบของฉันเพื่อแก้ไขปัญหานั้น ขอบคุณสำหรับความคิดเห็นของคุณ!
อย่าตั้งคำถาม

ในกรณีแรกเมื่อmain threadเสร็จสิ้นโปรแกรมจะเสร็จสิ้นโดยไม่ปล่อยให้child-thread(long)ตัวเองทำงานเสร็จสิ้น (เช่นchild-thread(long)ไม่เสร็จสมบูรณ์) หรือไม่
กายทรี

65

ส่งตรงจากเอกสาร

เข้าร่วม ([หมดเวลา]) รอจนกว่าเธรดจะสิ้นสุด สิ่งนี้จะบล็อกเธรดการโทรจนกว่าเธรดที่มีวิธีการเข้าร่วม () จะถูกเรียกว่ายุติ - ตามปกติหรือผ่านข้อยกเว้นที่ไม่สามารถจัดการได้ - หรือจนกว่าจะหมดเวลาใช้งานเพิ่มเติม

ซึ่งหมายความว่าเธรดหลักที่วางไข่tและdรอtให้เสร็จจนกว่าจะเสร็จสิ้น

ขึ้นอยู่กับตรรกะที่โปรแกรมของคุณใช้คุณอาจต้องรอจนกว่าเธรดจะเสร็จสิ้นก่อนที่เธรดหลักของคุณจะดำเนินการต่อไป

รวมทั้งจากเอกสาร:

เธรดสามารถถูกแฟล็กเป็น“ daemon thread” ความสำคัญของการตั้งค่าสถานะนี้คือโปรแกรม Python ทั้งหมดจะออกเมื่อเหลือเพียง daemon thread เท่านั้น

ตัวอย่างง่ายๆสมมติว่าเรามีสิ่งนี้:

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ให้เสร็จโดยปริยาย


คุณสามารถทำบางอย่างเพื่อ chnage t.join()รหัสของฉันเพื่อที่ฉันสามารถเห็นความแตกต่างของ โดยการเพิ่มการนอนหลับ soome หรืออย่างอื่น ในขณะนี้ฉันสามารถเห็นการเปลี่ยนแปลงใด ๆ ในโปรแกรมแม้ว่าฉันจะใช้มันหรือไม่ แต่สำหรับ damemon ฉันสามารถเห็นทางออกถ้าฉันใช้d.join()ซึ่งฉันไม่เห็นเมื่อฉันไม่ใช้ d.join ()
user192362127

35

ขอบคุณสำหรับกระทู้นี้ - มันช่วยฉันได้มากเช่นกัน

ฉันเรียนรู้บางอย่างเกี่ยวกับ. 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" บนเธรดหลัก - เป็นการเรียงลำดับเธรดของคุณและดำเนินการตามลำดับในเธรดหลักก่อนที่เธรดหลักจะสามารถดำเนินต่อไปได้ เป็นการรับรองว่าเธรดของคุณเสร็จสมบูรณ์ก่อนที่เธรดหลักจะเลื่อนไปข้างหน้า โปรดทราบว่าสิ่งนี้หมายความว่ามันไม่เป็นไรถ้าเธรดของคุณเสร็จสิ้นแล้วก่อนที่คุณจะเรียกการเข้าร่วม () - เธรดหลักจะถูกปล่อยทันทีเมื่อเข้าร่วม ()

อันที่จริงตอนนี้มันเกิดขึ้นกับฉันแล้วที่เธรดหลักจะรอที่ 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 ที่เธรดและสับสนเกี่ยวกับเธรดหลัก, เธรดแรกคือเธรดหลัก, ถ้าไม่, โปรดช่วยแนะนำฉัน?
Rohit Khatri

เธรดหลักคือโปรแกรมเอง แต่ละเธรดถูกแยกออกจากที่นั่น พวกเขาจะเข้าร่วมกลับเพราะที่คำสั่งเข้าร่วม () โปรแกรมรอจนกว่าเธรดจะเสร็จสิ้นก่อนที่จะดำเนินการต่อไป
Kiki Jewell

15

วิธีการเข้าร่วม ()

บล็อกเธรดการโทรจนกว่าเธรดที่เรียกว่า join () จะถูกยกเลิก

ที่มา: http://docs.python.org/2/library/threading.html


14
ดังนั้นการเข้าร่วมใช้คืออะไร? ดูคำถาม OP ไม่เพียงแปลความหมายเอกสาร
Don คำถาม

@ DonQuestion ฉันลองเพิ่ม sleep.timer (20) ในเธรดที่ไม่ใช่ daemon โดยไม่ใช้t.join()และโปรแกรมยังคงรอก่อนที่จะยกเลิก ฉันไม่เห็นการใช้t.join()ที่นี่ในรหัสของฉัน
user192362127

ดูคำตอบของฉันสำหรับคำอธิบายเพิ่มเติม เกี่ยวกับ sleep.timer ของคุณที่ไม่ใช่อสูร -> อสูรเธรดถูกแยกออกจากอายุการใช้งานของเธรดเธรดดังนั้นเธรดเธรด / พ่อแม่จะไม่ได้รับผลกระทบจากอายุการใช้งานของเธรดภูตและในทางกลับกัน .
คำถาม Don

2
คำศัพท์ 'เข้าร่วม' และ 'บล็อก' กำลังทำให้สับสน 'ถูกบล็อก' แสดงให้เห็นว่ากระบวนการโทรนั้น 'ถูกบล็อก' จากการทำสิ่งต่าง ๆ ที่ยังต้องทำในขณะที่ในความเป็นจริงมันเพิ่งถูกบล็อกไม่ให้สิ้นสุด (กลับสู่ระบบปฏิบัติการ) ไม่มาก ด้วยโทเค็นเดียวกันไม่ชัดเจนว่ามีเธรดหลักที่เรียกเธรดลูกเพื่อ 'เข้าร่วม' (เช่นยกเลิก) ดังนั้นดอนคิวขอบคุณสำหรับคำอธิบาย
RolfBly

5

เข้าใจง่าย

ด้วยการเข้าร่วม - ล่ามจะรอจนกว่ากระบวนการของคุณจะเสร็จสมบูรณ์หรือถูกยกเลิก

>>> 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)ฟังก์ชันสำหรับทั้งเธรดที่ไม่ใช่ daemon และเธรด daemon เธรดหลัก (หรือกระบวนการหลัก) ควรรอtวินาทีจากนั้นสามารถทำงานต่อไปในกระบวนการของตัวเองได้ ในช่วงtเวลารอวินาทีที่ทั้งสองหัวข้อเด็กควรทำสิ่งที่พวกเขาสามารถทำได้เช่นการพิมพ์ข้อความบางอย่าง หลังจากtวินาทีหากเธรดที่ไม่ใช่ daemon ยังคงทำงานไม่เสร็จและยังสามารถเสร็จสิ้นได้หลังจากกระบวนการหลักทำงานเสร็จ แต่สำหรับเธรด daemon จะเพิ่งพลาดโอกาสในการทำงาน อย่างไรก็ตามในที่สุดมันก็จะตายหลังจากออกจากโปรแกรมไพ ธ อน โปรดแก้ไขฉันหากมีสิ่งผิดปกติ


1

ใน python 3.x 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. เข้าร่วม () คือการปิดกั้นการโทรตามธรรมชาติสำหรับหัวข้อการโทรเข้าร่วมเพื่อดำเนินการต่อหลังจากด้ายที่เรียกว่าได้ยุติลง

หากโปรแกรมไพ ธ อนไม่ได้เข้าร่วมเธรดอื่นตัวแปลของไพ ธ อนจะยังคงเข้าร่วมเธรดที่ไม่ใช่ daemon ในนามของมัน


-2

"การใช้ join () ใช้อย่างไร?" คุณพูด. จริงๆแล้วมันเป็นคำตอบเดียวกับ "สิ่งที่ใช้ในการปิดไฟล์เนื่องจาก python และระบบปฏิบัติการจะปิดไฟล์ของฉันให้ฉันเมื่อโปรแกรมของฉันจบการทำงาน"

เป็นเพียงเรื่องของการเขียนโปรแกรมที่ดี คุณควรเข้าร่วม () กระทู้ของคุณที่จุดในรหัสที่เธรดไม่ควรทำงานอีกต่อไปอย่างใดอย่างหนึ่งเนื่องจากคุณต้องแน่ใจว่าเธรดจะไม่ทำงานเพื่อแทรกแซงรหัสของคุณเองหรือว่าคุณต้องการทำงานอย่างถูกต้องใน ระบบขนาดใหญ่

คุณอาจพูดว่า "ฉันไม่ต้องการให้รหัสล่าช้าในการตอบคำถาม" เพียงเพราะเวลาเพิ่มเติมที่อาจเข้าร่วม () สิ่งนี้อาจใช้ได้อย่างสมบูรณ์ในบางสถานการณ์ แต่ตอนนี้คุณต้องคำนึงว่ารหัสของคุณคือ "ปล่อยให้ cruft รอบ ๆ สำหรับไพ ธ อนและ OS เพื่อล้าง" หากคุณทำเช่นนี้ด้วยเหตุผลด้านประสิทธิภาพฉันขอแนะนำให้คุณบันทึกพฤติกรรมนั้น โดยเฉพาะอย่างยิ่งหากคุณกำลังสร้างห้องสมุด / แพ็คเกจที่ผู้อื่นคาดว่าจะใช้

ไม่มีเหตุผลที่จะไม่เข้าร่วม () อื่น ๆ นอกเหนือจากเหตุผลด้านประสิทธิภาพของและฉันจะยืนยันว่ารหัสของคุณไม่จำเป็นต้องมีการดำเนินการที่ดี


6
สิ่งที่คุณพูดเกี่ยวกับการล้างเธรดไม่เป็นความจริง ลองดูที่ซอร์สโค้ดของเธรด. เธรด.join () ฟังก์ชั่นทั้งหมดนั้นคือรอการล็อคจากนั้นกลับมา ไม่มีการล้างข้อมูลจริง
Collin

1
@Collin - เธรดอาจถือทรัพยากรในกรณีที่ล่ามและระบบปฏิบัติการจะต้องล้าง "cruft"
qneill

1
อีกครั้งดูที่รหัสแหล่งที่มาของเธรด. เธรด.join () ไม่มีสิ่งใดในที่ช่วยกระตุ้นการรวบรวมทรัพยากร
Collin

มันไม่จำเป็นต้อง (และอย่างที่คุณพูดไม่ใช่ทั้งหมด) โมดูลเธรดที่กำลังเก็บทรัพยากร แต่เป็นเธรดเอง การใช้การเข้าร่วม () หมายถึงคุณกำลังรอให้เธรดดำเนินการสิ่งที่ต้องการทำซึ่งอาจรวมถึงการจัดสรรและการปล่อยทรัพยากร
Chris Cogdon

2
ไม่ว่าคุณจะรอหรือไม่ไม่มีผลกระทบเมื่อมีการปล่อยทรัพยากรที่จัดเก็บโดยเธรด join()ฉันไม่แน่ใจว่าทำไมคุณกำลังผูกนี้ด้วยการโทร
Collin
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.