Apa gunanya join () di Python threading?

197

Saya sedang mempelajari threading python dan menemukan join().

Penulis mengatakan bahwa jika utas berada dalam mode daemon maka saya perlu menggunakan join()agar utas dapat menyelesaikan sendiri sebelum utas utama berakhir.

tapi saya juga pernah melihatnya menggunakan t.join()meskipun ttidakdaemon

contoh kode adalah ini

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

saya tidak tahu apa yang digunakan t.join()karena ini bukan daemon dan saya tidak dapat melihat perubahan meskipun saya menghapusnya

pengguna192362127
sumber
13
+1 untuk judul. 'Bergabung' tampaknya dirancang khusus untuk mendorong kinerja yang buruk, (dengan terus-menerus membuat / mengakhiri / menghancurkan utas), penguncian GUI, (menunggu di event-handler) dan kegagalan penutupan aplikasi, (menunggu utas yang tidak terputus untuk berakhir). Catatan - bukan hanya Python, ini adalah anti-pola lintas-bahasa.
Martin James

Jawaban:

288

Sebuah seni yang agak kikuk untuk mendemonstrasikan mekanismenya: join()Agaknya disebut oleh utas-utas. Itu bisa juga dipanggil oleh utas lain, tetapi tidak perlu menyulitkan diagram.

join-calling harus ditempatkan di jalur main-thread, tetapi untuk mengekspresikan hubungan-thread dan menjaganya sesederhana mungkin, saya memilih untuk menempatkannya di child-thread sebagai gantinya.

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

Jadi alasan Anda tidak melihat perubahan adalah karena utas utama Anda tidak melakukan apa pun setelah Anda join. Bisa dibilang joinrelevan (hanya) untuk alur eksekusi dari utas utama.

Jika, misalnya, Anda ingin secara bersamaan mengunduh banyak halaman untuk menggabungkannya menjadi satu halaman besar, Anda dapat mulai mengunduh secara bersamaan menggunakan utas, tetapi perlu menunggu sampai halaman / utas terakhir selesai sebelum Anda mulai menyusun satu halaman dari banyak. Saat itulah Anda menggunakan join().

Don Question
sumber
Harap konfirmasikan bahwa utas yang di daemonisasi dapat digabungkan () tanpa memblokir eksekusi program?
Aviator45003
@ Aviator45003: Ya, dengan menggunakan argumen batas waktu seperti: demon_thread.join(0.0), join()secara default memblokir tanpa memperhatikan atribut daemonized. Tetapi bergabung dengan utas setan membuka kemungkinan besar keseluruhan masalah! Saya sekarang sedang mempertimbangkan untuk menghapus join()panggilan dalam diagram kecil saya untuk daemon-thread ...
Don Question
@ DonQuestion Jadi jika kita mulai daemon=Truetidak kita perlu ke join()jika kita perlu join()di akhir kode?
Benyamin Jafari
@BenyaminJafari: Ya. Jika tidak, maka utas utama (= program) akan keluar, jika hanya utas-daemon yang tersisa. Tetapi sifat dari daemon (python) thread adalah bahwa thread utama tidak peduli jika tugas latar belakang ini masih berjalan. Saya akan memikirkan bagaimana menjelaskannya dalam jawaban saya, untuk menyelesaikan masalah itu. Terima kasih atas komentar Anda!
Don Question
Dalam kasus pertama, ketika main threadselesai, akankah program selesai tanpa membiarkan child-thread(long)selesai berjalan sendiri (yaitu child-thread(long)tidak sepenuhnya dilakukan)?
skytree
65

Langsung dari dokumen

join ([timeout]) Tunggu hingga utas berakhir. Ini memblokir utas panggilan sampai utas yang bergabung () metode disebut berakhir - baik secara normal atau melalui pengecualian tidak tertangani - atau sampai batas waktu opsional terjadi.

Ini berarti bahwa utas utama yang memunculkan tdan d, menunggu untuk tselesai sampai selesai.

Bergantung pada logika yang digunakan program Anda, Anda mungkin ingin menunggu sampai utas selesai sebelum utas utama berlanjut.

Juga dari dokumen:

Sebuah utas dapat ditandai sebagai "utas daemon". Signifikansi flag ini adalah bahwa seluruh program Python keluar ketika hanya thread daemon yang tersisa.

Contoh sederhana, katakan kita memiliki ini:

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

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

t.start()

Yang diakhiri dengan:

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

Ini akan menampilkan:

Test one
Test non-daemon
Test two

Di sini master thread secara eksplisit menunggu sampai tthread selesai sampai ia memanggil printyang kedua kalinya.

Atau kalau kita punya ini:

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

Kami akan mendapatkan hasil ini:

Test one
Test two
Test non-daemon

Di sini kita melakukan pekerjaan kita di utas utama dan kemudian kita menunggu tutas selesai. Dalam hal ini kita bahkan mungkin menghapus bergabung secara eksplisit t.join()dan program secara implisit akan menunggu untuk tselesai.

dmg
sumber
Bisakah Anda membuat beberapa chnage ke kode saya sehingga saya dapat melihat perbedaannya t.join()? dengan menambahkan tidur malam atau sesuatu yang lain. saat ini saya dapat melihat chnage dalam program bahkan jika saya menggunakannya atau tidak. tetapi untuk damemon saya dapat melihat keluarnya jika saya menggunakan d.join()yang saya tidak lihat ketika saya tidak menggunakan d.join ()
user192362127
34

Terima kasih atas utas ini - ini banyak membantu saya.

Saya belajar sesuatu tentang .join () hari ini.

Utas ini berjalan secara paralel:

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

dan ini berjalan secara berurutan (bukan yang saya inginkan):

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

Secara khusus, saya mencoba untuk pintar dan rapi:

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

Ini bekerja! Tapi itu berjalan berurutan. Saya bisa meletakkan self.start () di __ init __, tetapi bukan self.join (). Itu harus dilakukan setelah setiap utas dimulai.

join () adalah apa yang menyebabkan utas utama menunggu utas Anda selesai. Jika tidak, utas Anda berjalan dengan sendirinya.

Jadi salah satu cara untuk menganggap join () sebagai "tahan" pada utas utama - itu semacam mencabut utas dan menjalankan secara berurutan pada utas utama, sebelum utas utama dapat dilanjutkan. Ini memastikan bahwa utas Anda selesai sebelum utas utama bergerak maju. Perhatikan bahwa ini berarti tidak apa-apa jika utas Anda sudah selesai sebelum Anda memanggil join () - utas utama dilepaskan segera saat join () dipanggil.

Sebenarnya, barusan saya sadar bahwa utas utama menunggu di d.join () hingga utas d selesai sebelum beralih ke t.join ().

Bahkan, agar sangat jelas, pertimbangkan kode ini:

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"

Ini menghasilkan output ini (perhatikan bagaimana pernyataan cetak saling berulir.)

$ 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 () sedang memegang utas utama. Ketiga utas selesai sebelum t1.join () selesai dan utas utama melanjutkan untuk menjalankan cetak kemudian t2.join () kemudian cetak lalu t3.join () lalu cetak.

Selamat datang koreksi. Saya juga baru mengenal threading.

(Catatan: jika Anda tertarik, saya menulis kode untuk DrinkBot, dan saya perlu threading untuk menjalankan pompa bahan secara bersamaan daripada berurutan - lebih sedikit waktu untuk menunggu setiap minuman.)

Kiki Jewell
sumber
Hei, saya juga baru ke python threading dan bingung tentang utas utama, Apakah utas pertama adalah utas utama, Jika tidak, tolong bimbing saya?
Rohit Khatri
Utas utama adalah program itu sendiri. Setiap utasnya bercabang dari sana. Mereka kemudian bergabung kembali - karena pada perintah join (), program menunggu hingga utas selesai sebelum terus dijalankan.
Kiki Jewell
15

Metode bergabung ()

memblokir utas panggilan sampai utas yang metode join () dipanggil diakhiri.

Sumber: http://docs.python.org/2/library/threading.html

Ketouem
sumber
14
jadi apa gunanya join? lihat pertanyaan OP, jangan hanya memparafrasekan dokumen
Don Question
@ DonQuestion saya bahkan mencoba menambahkan sleep.timer (20) di utas non daemon tanpa menggunakan t.join()dan program masih menunggu sebelum penghentian. saya tidak melihat ada gunanya t.join()di sini dalam kode saya
user192362127
lihat jawaban saya, untuk penjelasan lebih lanjut. mengenai tidur Anda. lebih dulu dalam non-iblis -> iblis-utas dipisahkan dari masa hidup dari utas induknya sehingga benang induk / saudara tidak akan terpengaruh oleh masa pakai utas yang di-iblis dan sebaliknya .
Don Question
2
Terminologi 'gabung' dan 'blok' membingungkan. 'Diblokir' menyarankan proses panggilan 'diblokir' dari melakukan sejumlah hal yang masih harus dilakukan, sementara pada kenyataannya itu hanya diblokir dari penghentian (kembali ke OS), tidak lebih. Dengan cara yang sama, tidak begitu jelas bahwa ada utas utama yang memanggil utas anak untuk 'bergabung' dengannya (yaitu mengakhiri). Jadi, Don Q, terima kasih atas penjelasannya.
RolfBly
5

Memahami sederhana,

dengan join - interpreter akan menunggu sampai proses Anda selesai atau dihentikan

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

tanpa bergabung - penerjemah tidak akan menunggu sampai proses dihentikan ,

>>> t = Thread(target=sam)
>>> t.start()
started
>>> print 'yes done' #without join interpreter wont wait until process get terminated
yes done
>>> waiting for 10sec
Mohideen bin Mohammed
sumber
1

Saat membuat join(t)fungsi untuk utas non-daemon dan utas daemon, utas utama (atau proses utama) harus menunggu beberapa tdetik, kemudian dapat melangkah lebih jauh untuk mengerjakan prosesnya sendiri. Selama twaktu tunggu detik, kedua utas anak-anak harus melakukan apa yang dapat mereka lakukan, seperti mencetak beberapa teks. Setelah beberapa tdetik, jika utas non-daemon masih belum menyelesaikan tugasnya, dan masih bisa menyelesaikannya setelah proses utama menyelesaikan tugasnya, tetapi untuk utas daemon, ia hanya melewatkan jendela peluangnya. Namun, akhirnya akan mati setelah program python keluar. Harap perbaiki saya jika ada sesuatu yang salah.

pengguna1342336
sumber
1

Dalam python 3.x join () digunakan untuk bergabung dengan utas dengan utas utama yaitu ketika join () digunakan untuk utas tertentu, utas utama akan berhenti dieksekusi sampai eksekusi utas gabungan selesai.

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

'''
Shishir Nanoty
sumber
0

Contoh ini menunjukkan .join()tindakan:

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

Di luar:

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
Benyamin Jafari
sumber
0

Ada beberapa alasan mengapa utas utama (atau utas lainnya) bergabung dengan utas lainnya

  1. Utas mungkin telah membuat atau menahan (mengunci) beberapa sumber. Utas panggilan gabungan mungkin dapat menghapus sumber daya atas namanya

  2. join () adalah panggilan pemblokiran alami untuk utas panggilan penggabungan untuk melanjutkan setelah utas yang dipanggil dihentikan.

Jika program python tidak bergabung dengan utas lain, juru bahasa python masih akan bergabung dengan utas non-daemon atas namanya.

yoonghm
sumber
-2

"Apa gunanya menggunakan join ()?" kamu bilang. Sungguh, itu jawaban yang sama dengan "apa gunanya menutup file, karena python dan OS akan menutup file saya ketika program saya keluar?".

Ini hanya masalah pemrograman yang bagus. Anda harus bergabung () utas pada titik di kode bahwa utas tidak boleh berjalan lagi, baik karena Anda harus memastikan utas tidak berjalan untuk mengganggu kode Anda sendiri, atau bahwa Anda ingin berperilaku dengan benar dalam sistem yang lebih besar.

Anda mungkin berkata "Saya tidak ingin kode saya menunda memberikan jawaban" hanya karena waktu tambahan yang diperlukan untuk bergabung (). Ini mungkin benar-benar valid dalam beberapa skenario, tetapi Anda sekarang harus mempertimbangkan bahwa kode Anda "meninggalkan penggerusan untuk python dan OS untuk dibersihkan". Jika Anda melakukan ini karena alasan kinerja, saya sangat menyarankan Anda untuk mendokumentasikan perilaku itu. Ini terutama benar jika Anda sedang membangun perpustakaan / paket yang diharapkan orang lain manfaatkan.

Tidak ada alasan untuk tidak bergabung (), selain alasan kinerja, dan saya berpendapat bahwa kode Anda tidak perlu melakukan itu dengan baik.

Chris Cogdon
sumber
6
Apa yang Anda katakan tentang membersihkan utas tidak benar. Lihatlah kode sumber threading.Thread.join (). Semua fungsi yang dilakukan adalah menunggu di kunci, dan kemudian kembali. Tidak ada yang benar-benar dibersihkan.
Collin
1
@Collin - Thread itu sendiri mungkin memegang sumber daya, dalam hal ini penerjemah dan OS memang perlu untuk membersihkan "cruft".
qneill
1
Sekali lagi, lihat kode sumber threading.Thread.join (). Tidak ada apa pun di sana yang memicu pengumpulan sumber daya.
Collin
Ini belum tentu (dan seperti yang Anda katakan, tidak sama sekali) modul threading yang menahan sumber daya, tetapi utas itu sendiri. Menggunakan join () berarti Anda menunggu utas selesai melakukan apa yang ingin dilakukan, yang dapat mencakup mengalokasikan dan melepaskan sumber daya.
Chris Cogdon
2
Apakah Anda menunggu atau tidak, tidak mempengaruhi kapan sumber daya yang dimiliki oleh utas dilepaskan. Saya tidak yakin mengapa Anda mengikat ini dengan menelepon join().
Collin