Việc sử dụng phép nối () trong phân luồng Python là gì?


197

Tôi đang nghiên cứu luồng trăn và tình cờ thấy join() .

Tác giả nói rằng nếu luồng ở chế độ daemon thì tôi cần sử dụng join()để luồng có thể tự hoàn thành trước khi luồng chính kết thúc.

nhưng tôi cũng đã thấy anh ấy sử dụng t.join()mặc dùt khôngdaemon

mã ví dụ là đây

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ôi không biết những gì được sử dụng của t.join() vì nó không phải là daemon và tôi không thể thấy sự thay đổi nào ngay cả khi tôi gỡ bỏ nó


13
+1 cho tiêu đề. 'Tham gia' dường như được thiết kế đặc biệt để khuyến khích hiệu suất kém, (bằng cách liên tục tạo / chấm dứt / hủy chủ đề), khóa GUI, (chờ trong trình xử lý sự kiện) và lỗi tắt ứng dụng, (chờ các luồng không liên tục kết thúc). Lưu ý - không chỉ Python, đây là kiểu chống ngôn ngữ chéo.
Martin James

Câu trả lời:


287

Một nghệ thuật ascii hơi vụng về để thể hiện cơ chế: Có join()lẽ được gọi là chủ đề chính. Nó cũng có thể được gọi bởi một luồng khác, nhưng sẽ không cần thiết làm phức tạp sơ đồ.

join-calling nên được đặt trong rãnh của luồng chính, nhưng để thể hiện mối quan hệ luồng và giữ nó đơn giản nhất có thể, tôi chọn đặt nó trong luồng con thay thế.

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

Vì vậy, lý do bạn không thấy bất kỳ thay đổi nào là vì luồng chính của bạn không làm gì sau đó join. Bạn có thể nói joinlà (chỉ) có liên quan đến luồng thực thi của luồng chính.

Ví dụ, nếu bạn muốn tải xuống đồng thời một loạt các trang để ghép chúng thành một trang lớn, bạn có thể bắt đầu tải xuống đồng thời bằng các luồng, nhưng cần đợi cho đến khi trang / luồng cuối cùng kết thúc trước khi bạn bắt đầu lắp ráp một trang duy nhất trong số nhiều Đó là khi bạn sử dụng join().


Vui lòng xác nhận rằng một luồng daemonized có thể được nối () mà không chặn thực thi chương trình?
Aviator45003

@ Aviator45003: Có, bằng cách sử dụng đối số hết thời gian như : demon_thread.join(0.0), join()theo mặc định chặn mà không liên quan đến thuộc tính daemonized. Nhưng tham gia một chủ đề bị quỷ ám mở ra rất có thể là một rắc rối! Bây giờ tôi đang xem xét để loại bỏ join()cuộc gọi trong sơ đồ nhỏ của tôi cho chuỗi daemon ...
Don Câu hỏi

@DonQuestion Vì vậy, nếu chúng ta thiết lập, chúng ta daemon=Truekhông join()cần đến phần join()cuối của mã?
Benyamin Jafari

@BenyaminJafari: Vâng. Nếu không, thì luồng chính (= chương trình) sẽ thoát, nếu chỉ còn lại daemon-thread. Nhưng bản chất của một luồng daemon (python) là luồng chính không quan tâm nếu tác vụ nền này vẫn đang chạy. Tôi sẽ suy nghĩ về cách giải thích vấn đề đó trong câu trả lời của tôi, để giải quyết vấn đề đó. Cám ơn bạn đã góp ý!
Don Câu hỏi

Trong trường hợp đầu tiên, khi main threadkết thúc, chương trình sẽ kết thúc mà không để child-thread(long)kết thúc tự chạy (tức child-thread(long)là chưa hoàn thành)?
skytree

65

Trực tiếp từ các tài liệu

tham gia ([hết thời gian]) Chờ cho đến khi chuỗi kết thúc. Điều này chặn luồng gọi cho đến khi luồng có phương thức tham gia () được gọi là kết thúc - thông thường hoặc thông qua một ngoại lệ chưa được xử lý - hoặc cho đến khi hết thời gian chờ tùy chọn.

Điều này có nghĩa là luồng chính sinh ra tdchờ đợi tkết thúc cho đến khi hoàn thành.

Tùy thuộc vào logic chương trình của bạn sử dụng, bạn có thể muốn đợi cho đến khi một luồng kết thúc trước khi luồng chính của bạn tiếp tục.

Cũng từ các tài liệu:

Một chủ đề có thể được gắn cờ là một chủ đề daemon khác. Tầm quan trọng của cờ này là toàn bộ chương trình Python thoát khi chỉ còn lại các luồng daemon.

Một ví dụ đơn giản, giả sử chúng ta có điều này:

def non_daemon():
    time.sleep(5)
    print 'Test non-daemon'

t = threading.Thread(name='non-daemon', target=non_daemon)

t.start()

Kết thúc với:

print 'Test one'
t.join()
print 'Test two'

Điều này sẽ xuất ra:

Test one
Test non-daemon
Test two

Ở đây, chủ đề chính chờ đợi tchủ đề kết thúc cho đến khi nó gọi printlần thứ hai.

Ngoài ra, nếu chúng ta có điều này:

print 'Test one'
print 'Test two'
t.join()

Chúng tôi sẽ nhận được đầu ra này:

Test one
Test two
Test non-daemon

Ở đây chúng tôi thực hiện công việc của mình trong luồng chính và sau đó chúng tôi đợi tluồng kết thúc. Trong trường hợp này, chúng tôi thậm chí có thể loại bỏ việc tham gia rõ ràng t.join()và chương trình sẽ hoàn toàn chờ đợi tđể kết thúc.


Bạn có thể tạo một số chnage cho mã của tôi để tôi có thể thấy sự khác biệt của t.join(). bằng cách thêm giấc ngủ ngon hoặc cái gì khác tại thời điểm này tôi có thể thấy bất kỳ chnage nào trong chương trình ngay cả khi tôi có sử dụng nó hay không. nhưng đối với damemon tôi có thể thấy lối ra của nó nếu tôi sử dụng d.join()cái mà tôi không thấy khi tôi không sử dụng d.join ()
user192362127

34

Cảm ơn chủ đề này - nó cũng giúp tôi rất nhiều.

Tôi đã học được vài điều về .join () ngày hôm nay.

Các luồng này chạy song song:

d.start()
t.start()
d.join()
t.join()

và chúng chạy tuần tự (không phải những gì tôi muốn):

d.start()
d.join()
t.start()
t.join()

Cụ thể, tôi đã cố gắng khéo léo và gọn gàng:

class Kiki(threading.Thread):
    def __init__(self, time):
        super(Kiki, self).__init__()
        self.time = time
        self.start()
        self.join()

Những công việc này! Nhưng nó chạy tuần tự. Tôi có thể đặt self.start () trong __ init __, nhưng không đặt self.join (). Điều đó phải được thực hiện sau khi tất cả các chủ đề đã được bắt đầu.

tham gia () là nguyên nhân khiến luồng chính chờ đợi luồng của bạn kết thúc. Nếu không, chủ đề của bạn chạy tất cả của chính nó.

Vì vậy, một cách để nghĩ về phép nối () là "giữ" trên luồng chính - đó là loại bỏ các luồng của luồng của bạn và thực hiện tuần tự trong luồng chính, trước khi luồng chính có thể tiếp tục. Nó đảm bảo rằng chủ đề của bạn đã hoàn thành trước khi chủ đề chính di chuyển về phía trước. Lưu ý rằng điều này có nghĩa là nó ổn nếu luồng của bạn đã kết thúc trước khi bạn gọi hàm jo () - luồng chính được giải phóng ngay lập tức khi tham gia () được gọi.

Trong thực tế, điều này xảy ra với tôi rằng luồng chính chờ ở d.join () cho đến khi luồng d kết thúc trước khi nó chuyển sang t.join ().

Trong thực tế, để rất rõ ràng, hãy xem xét mã này:

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"

Nó tạo ra kết quả đầu ra này (lưu ý cách các câu lệnh in được xâu vào nhau.)

$ 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 () đang giữ luồng chính. Tất cả ba luồng hoàn thành trước khi t1.join () kết thúc và luồng chính di chuyển để thực hiện in sau đó t2.join () sau đó in rồi t3.join () sau đó in.

Sửa chữa chào mừng. Tôi cũng mới bắt đầu xâu chuỗi.

(Lưu ý: trong trường hợp bạn quan tâm, tôi đang viết mã cho một DrinkBot và tôi cần xâu chuỗi để chạy các bơm thành phần đồng thời thay vì tuần tự - ít thời gian chờ đợi cho mỗi lần uống.)


Xin chào, tôi cũng chưa quen với luồng python và bối rối về luồng chính, luồng đầu tiên có phải là luồng chính không, nếu không, xin vui lòng hướng dẫn cho tôi?
Rohit Khatri

Chủ đề chính là chương trình chính nó. Mỗi chủ đề được rẽ nhánh từ đó. Sau đó chúng được nối lại - bởi vì tại lệnh jo (), chương trình sẽ đợi cho đến khi luồng kết thúc trước khi nó tiếp tục thực thi.
Kiki Jewell

15

Phương thức tham gia ()

chặn luồng gọi cho đến khi luồng có phương thức tham gia () được gọi bị chấm dứt.

Nguồn: http://docs.python.org/2/l Library / threading.html


14
Vì vậy, việc sử dụng tham gia là gì? xem câu hỏi của OP, đừng chỉ diễn giải các tài liệu
Don Câu hỏi

@DonQuestion tôi thậm chí đã thử thêm ngủ.timer (20) trong luồng không daemon mà không sử dụng t.join()và chương trình vẫn đợi nó trước khi chấm dứt. tôi không thấy bất kỳ việc sử dụng nào t.join()ở đây trong mã của mình
user192362127

xem câu trả lời của tôi, để giải thích thêm. liên quan đến giấc ngủ của bạn .
Don Câu hỏi

2
Thuật ngữ 'tham gia' và 'chặn' là khó hiểu. 'Bị chặn' cho thấy quy trình gọi điện bị 'chặn' khi thực hiện bất kỳ số lượng việc nào vẫn phải thực hiện, trong khi thực tế, nó chỉ bị chặn chấm dứt (quay lại HĐH), chứ không phải nhiều hơn. Với cùng một mã thông báo, không rõ ràng đến mức có một luồng chính gọi một luồng con để 'tham gia' nó (tức là chấm dứt). Vì vậy, Don Q, cảm ơn đã giải thích.
RolfBly

5

Hiểu đơn giản,

với sự tham gia - thông dịch viên sẽ đợi cho đến khi quá trình của bạn được hoàn thành hoặc chấm dứt

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

không tham gia - thông dịch viên sẽ không đợi cho đến khi quá trình kết thúc ,

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

Khi thực hiện join(t)chức năng cho cả luồng không phải daemon và luồng daemon, luồng chính (hoặc tiến trình chính) phải đợi tvài giây, sau đó có thể đi xa hơn để làm việc theo quy trình riêng của mình. Trong tthời gian chờ đợi vài giây, cả hai chủ đề trẻ em nên làm những gì chúng có thể làm, chẳng hạn như in ra một số văn bản. Sau tvài giây, nếu luồng không phải daemon vẫn không hoàn thành công việc của nó và nó vẫn có thể hoàn thành nó sau khi quá trình chính hoàn thành công việc của nó, nhưng đối với luồng daemon, nó chỉ bỏ lỡ cửa sổ cơ hội. Tuy nhiên, cuối cùng nó sẽ chết sau khi chương trình python thoát ra. Xin hãy sửa cho tôi nếu có gì sai.


1

Trong python 3.x, jo () được sử dụng để nối một luồng với luồng chính, tức là khi jo () được sử dụng cho một luồng cụ thể, luồng chính sẽ dừng thực thi cho đến khi việc thực hiện luồng được nối hoàn tất.

#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

Ví dụ này thể hiện .join()hành động:

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

Ngoài:

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

Có một vài lý do để chủ đề chính (hoặc bất kỳ chủ đề nào khác) tham gia các chủ đề khác

  1. Một luồng có thể đã tạo hoặc giữ (khóa) một số tài nguyên. Chuỗi gọi tham gia có thể thay mặt xóa tài nguyên

  2. tham gia () là một cuộc gọi chặn tự nhiên để chuỗi gọi tham gia tiếp tục sau khi chuỗi được gọi đã kết thúc.

Nếu một chương trình python không tham gia các chủ đề khác, trình thông dịch python vẫn sẽ tham gia các chủ đề không phải daemon thay mặt.


-2

"Việc sử dụng tham gia () là gì?" bạn nói. Thực sự, đó là câu trả lời giống như "việc sử dụng đóng tệp là gì, vì python và HĐH sẽ đóng tệp của tôi cho tôi khi chương trình của tôi thoát?".

Nó đơn giản chỉ là vấn đề lập trình tốt. Bạn nên tham gia () các chủ đề của bạn tại điểm trong mã mà chủ đề không nên nên chạy nữa, vì bạn phải đảm bảo chủ đề không chạy để can thiệp vào mã của riêng bạn hoặc bạn muốn hành xử chính xác trong một hệ thống lớn hơn.

Bạn có thể nói "Tôi không muốn mã của mình trì hoãn đưa ra câu trả lời" chỉ vì thời gian bổ sung mà phép nối () có thể yêu cầu. Điều này có thể hoàn toàn hợp lệ trong một số trường hợp, nhưng bây giờ bạn cần phải tính đến việc mã của bạn đang "để lại xung quanh cho python và HĐH để dọn sạch". Nếu bạn làm điều này vì lý do hiệu suất, tôi khuyến khích bạn ghi lại hành vi đó. Điều này đặc biệt đúng nếu bạn đang xây dựng một thư viện / gói mà người khác dự kiến ​​sẽ sử dụng.

Không có lý do để không tham gia (), ngoài lý do hiệu suất và tôi cho rằng mã của bạn không cần phải thực hiện tốt điều đó .


6
Những gì bạn nói về làm sạch chủ đề là không đúng sự thật. Hãy xem mã nguồn của luồng .Thread.join (). Tất cả chức năng đó là chờ trên một khóa, và sau đó quay trở lại. Không có gì thực sự được làm sạch.
Collin

1
@Collin - Bản thân luồng có thể đang nắm giữ tài nguyên, trong trường hợp đó, trình thông dịch và HĐH thực sự sẽ cần phải dọn sạch "cruft".
qneill

1
Một lần nữa, hãy nhìn vào mã nguồn của luồng .Thread.join (). Không có gì trong đó kích hoạt bộ sưu tập tài nguyên.
Collin

Nó không nhất thiết (và như bạn nói, hoàn toàn không) mô-đun luồng đang giữ tài nguyên, mà chính là luồng. Sử dụng phép nối () có nghĩa là bạn đang chờ đợi chuỗi hoàn tất thực hiện những gì nó muốn làm, có thể bao gồm phân bổ và giải phóng tài nguyên.
Chris Cogdon 2/12/2015

2
Cho dù bạn chờ hay không không ảnh hưởng khi tài nguyên được giữ bởi luồng được phát hành. Tôi không chắc tại sao bạn buộc điều này bằng cách gọi join().
Collin
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.