Python threading'de join () kullanımı nedir?


208

Python ipliğini inceliyordum ve karşılaştım join().

Yazar, iş parçacığı daemon kipinde ise, join()ana iş parçacığı sona ermeden önce iş parçacığının kendisini bitirebilmesi için kullanmam gerektiğini söyledi.

ama ben de kullanarak onu görmüş t.join()olsa bile tdeğildidaemon

örnek kod budur

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()arka plan programı olmadığı için neyin kullanıldığını bilmiyorum ve kaldırsam bile hiçbir değişiklik göremiyorum


14
Başlık için +1. 'Birleştirme', (sürekli olarak iş parçacıkları oluşturarak / sonlandırarak / yok ederek), GUI kilitlenmelerini (olay işleyicilerinde bekleyerek) ve uygulama kapatma hatalarını (kesintisiz iş parçacıklarının sona ermesini bekleyerek) zayıf performansı teşvik etmek için özel olarak tasarlanmış gibi görünüyor. Not - sadece Python değil, bu bir diller arası anti-modeldir.
Martin James

Yanıtlar:


307

Mekanizmayı göstermek için biraz beceriksiz bir bilim sanatı: join()Muhtemelen ana iş parçacığı tarafından adlandırılır. Başka bir iş parçacığı tarafından da çağrılabilir, ancak diyagramı gereksiz yere karmaşıklaştırır.

join-calling, ana iş parçacığının izine yerleştirilmelidir, ancak iş parçacığı ilişkisini ifade etmek ve mümkün olduğunca basit tutmak için, bunun yerine alt iş parçacığına yerleştirmeyi seçiyorum.

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

Dolayısıyla herhangi bir değişiklik görmemenizin nedeni, ana iş parçacığınızın sizden sonra hiçbir şey yapmamasıdır join. joinAna iş parçacığının yürütme akışıyla (yalnızca) ilgili olduğunu söyleyebilirsin .

Örneğin, tek bir büyük sayfada birleştirmek için bir grup sayfayı aynı anda indirmek istiyorsanız, dizileri kullanarak eşzamanlı indirmelere başlayabilirsiniz, ancak tek bir sayfayı birleştirmeye başlamadan önce son sayfanın / iş parçacığının bitmesini beklemeniz gerekir. çoğunun dışında. İşte o zaman kullanıyorsun join().


Lütfen programın yürütülmesini engellemeden daemonize edilmiş bir iş parçacığının () birleştirilebileceğini onaylayın.
Aviator45003

Aviator45003 @: Evet, gibi zaman aşımı argümanı kullanarak: demon_thread.join(0.0), join()daemonized özniteliği bakılmaksızın engelleme varsayılan gereğidir. Ancak şeytanlaştırılmış bir konuya katılmak büyük olasılıkla büyük bir sorun yaratır! Şimdi join()arka plan iş parçacığı için küçük diyagramımdaki çağrıyı kaldırmayı düşünüyorum ...
Don Soru

Üzerinde ayarlarsanız @DonQuestion Yani daemon=Truebiz gerek yok join()biz gerekirse join()kodun sonuna?
Benyamin Jafari

@BenyaminJafari: Evet. Değilse, sadece arka plan iş parçacığı bırakılırsa ana iş parçacığı (= program) çıkar. Ancak bir (python) artalan süreci iş parçacığının doğası, ana iş parçacığının bu arka plan görevinin hala çalışıp çalışmadığını önemsememesidir. Sorunu gidermek için cevabımda bunu nasıl detaylandıracağımı düşüneceğim. Yorumun için teşekkürler!
Don Soru

İlk durumda, main threadbittiğinde, program child-thread(long)kendi kendini çalıştırmayı bitirmeden child-thread(long)biter mi (yani tamamen bitmez)?
skytree

67

Doğrudan belgelerden

katılma ([zaman aşımı]) İş parçacığı bitene kadar bekleyin. Bu, join () yöntemi çağrılan evre sonlanana kadar (normal olarak veya işlenmeyen bir istisna yoluyla) veya isteğe bağlı zaman aşımı oluşana kadar çağıran evreyi engeller.

Bu, ortaya çıkan tve bitene kadar bitmesini dbekleyen ana iş parçacığı anlamına gelir t.

Programınızın kullandığı mantığa bağlı olarak, ana iş parçacığınız devam etmeden önce bir iş parçacığı bitene kadar beklemek isteyebilirsiniz.

Ayrıca belgelerden:

Bir iş parçacığı, "arka plan iş parçacığı" olarak işaretlenebilir. Bu bayrağın önemi, yalnızca arka plan iş parçacığı kaldığında tüm Python programının çıkmasıdır.

Basit bir örnek, diyelim ki bizde:

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

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

t.start()

Hangisi ile biter:

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

Bu çıktı:

Test one
Test non-daemon
Test two

Burada ana iş tparçacığı print, ikinci kez çağırana kadar iş parçacığının bitmesini açıkça bekler .

Alternatif olarak şuna sahipsek:

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

Bu çıktıyı alacağız:

Test one
Test two
Test non-daemon

Burada işimizi ana iş parçacığında yapıyoruz ve ardından tiş parçacığının bitmesini bekliyoruz. Bu durumda, açıkça katılmayı bile kaldırabiliriz t.join()ve program dolaylı tolarak bitmesini bekler .


Koduma biraz değişiklik yapabilir misin, böylece farkını görebilirim t.join(). sakin bir uyku veya başka bir şey ekleyerek. şu anda programda kullansam da kullanmasam da herhangi bir değişiklik görebiliyorum. ama damemon d.join()için d.join () kullanmadığım zaman görmediğim kullanırsam çıkışını görebilirim
user192362127

35

Bu konu için teşekkürler - bana da çok yardımcı oldu.

Bugün .join () hakkında bir şey öğrendim.

Bu iplikler paralel olarak çalışır:

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

ve bunlar sırayla çalışıyor (istediğim gibi değil):

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

Özellikle, akıllı ve düzenli olmaya çalışıyordum:

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

Bu çalışıyor! Ama sırayla çalışır. Self.start () 'ı __ init __' e koyabilirim, ancak self.join () 'i koyamıyorum. Bu daha sonra yapılmalı her iş parçacığı başlatıldıktan .

Join () ana iş parçacığının iş parçacığınızın bitmesini beklemesine neden olan şeydir. Aksi takdirde, iş parçacığınız kendi başına çalışır.

Join () 'i ana iş parçacığı üzerinde bir "bekletme" olarak düşünmenin bir yolu - ana iş parçacığı devam etmeden önce iş parçacığınızın iş parçacığını çözer ve ana iş parçacığında sırayla yürütülür. Ana iş parçacığı ilerlemeden önce iş parçacığınızın tamamlanmış olmasını sağlar. Join () çağrılmadan önce iş parçacığınız zaten bitmişse bunun bir sorun olmadığı anlamına geldiğini unutmayın - join () çağrıldığında ana iş parçacığı hemen serbest bırakılır.

Aslında, ana iş parçacığının t.join () 'e geçmeden önce d iş parçacığı bitene kadar d.join ()' de beklediğini şimdi anlıyorum.

Aslında, çok açık olmak gerekirse, şu kodu göz önünde bulundurun:

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"

Bu çıktıyı üretir (yazdırma ifadelerinin birbirine nasıl akıtıldığına dikkat edin.)

$ 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 () ana iş parçacığını tutuyor. Üç iş parçacığı da t1.join () bitmeden önce tamamlanır ve ana iş parçacığı, yazdırmayı yürütmek için ilerler ve ardından t2.join (), ardından t3.join () ve ardından yazdırır.

Düzeltmeler hoş geldiniz. Ayrıca iş parçacığı açma konusunda yeniyim.

(Not: İlgilenmeniz durumunda, bir DrinkBot için kod yazıyorum ve içerik pompalarını sırayla değil, eşzamanlı olarak çalıştırmak için iş parçacığı açmam gerekiyor - her bir içeceği beklemek için daha az zaman.)


Hey, ben de python iş parçacığı konusunda yeniyim ve ana iş parçacığı hakkında kafam karıştı, İlk iş parçacığı ana iş parçacığı mı, değilse, lütfen bana yol göster?
Rohit Khatri

Ana konu programın kendisidir. İpliklerin her biri oradan çatallanır. Daha sonra tekrar birleştirilirler - çünkü join () komutunda, program iş parçacığı çalışmaya devam etmeden önce bitene kadar bekler.
Kiki Jewell

16

Join () yöntemi

join () yöntemi çağrılan evre sonlandırılana kadar çağıran evreyi engeller.

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


15
öyleyse birleştirmenin faydası nedir? OP sorusuna bakın, sadece belgeleri açıklamayın
Don Soru

@DonQuestion kullanmadan daemon olmayan iş parçacığına sleep.timer (20) eklemeyi denedim t.join()ve program hala sonlandırmadan önce bekliyor. t.join()
kodumda

daha fazla açıklama için cevabıma bakınız. non-demon'daki uyku zamanlayıcınızla ilgili -> bir iblis dizisi, ana iş parçacığının yaşam süresinden ayrıştırılır ve böylece ebeveyn / kardeş dizileri şeytanlaştırılmış dizinin yaşam süresinden etkilenmez ve bunun tersi de geçerlidir .
Don Soru

2
'Birleştirme' ve 'engelleme' terminolojisi kafa karıştırıcıdır. 'Engellendi', arama işleminin hala yapması gereken birçok şeyi yapmasının 'engellendiğini', ancak aslında sonlandırmasının (işletim sistemine geri dönmesinin) engellendiğini, daha fazlasını değil. Aynı şekilde, bir alt evreyi ona 'katılmaya' çağıran bir ana iş parçacığı olduğu (yani sonlandırmak) o kadar açık değildir. Don Q, açıklama için teşekkürler.
RolfBly

5

Birleştirme ile - tercüman işleminizin tamamlanmasını veya sonlandırılmasını bekleyecektir

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

birleştirme olmadan - tercüman işlemin sonlandırılmasını beklemeyecek ,

>>> 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)Hem artalan süreci olmayan evre hem de artalan süreci iş parçacığı için işlev yaparken , ana iş parçacığı (veya ana süreç) tsaniyeler beklemelidir , ardından kendi süreci üzerinde çalışmaya devam edebilir. tSaniyeler bekleme süresi boyunca , her iki alt ileti dizisi de bazı metinleri yazdırmak gibi yapabileceklerini yapmalıdır. tSaniyeler sonra, arka plan programı olmayan iş parçacığı işini hala bitirmediyse ve ana işlem işini bitirdikten sonra da onu bitirebilir, ancak arka plan iş parçacığı için fırsat penceresini kaçırdı. Ancak, python programı çıktıktan sonra sonunda ölecektir. Bir sorun varsa lütfen beni düzeltin.


1

Python 3.x'de birleştirme (), ana iş parçacığı ile bir evreyi birleştirmek için kullanılır, yani belirli bir iş parçacığı için join () kullanıldığında, ana iş parçacığı, birleştirilen iş parçacığının yürütülmesi tamamlanana kadar yürütülmeyi durdurur.

#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

Bu örnek .join()eylemi göstermektedir :

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

Dışarı:

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

Ana iş parçacığının (veya başka bir iş parçacığının) diğer konularla birleştirilmesinin birkaç nedeni vardır.

  1. Bir iş parçacığı bazı kaynakları oluşturmuş veya tutmuş (kilitlemiş) olabilir. Katılma çağrısı iş parçacığı kaynakları kendi adına temizleyebilir

  2. join (), çağrılan iş parçacığı sonlandırıldıktan sonra katılma çağrısı yapan iş parçacığının devam etmesi için doğal bir engelleme çağrısıdır.

Bir python programı diğer evrelere katılmazsa, python yorumlayıcısı yine de onun adına artalan süreci olmayan iş parçacıklarına katılır.


-2

"Join () kullanmanın faydası nedir?" diyorsun. Gerçekte, "Python ve işletim sistemi, programım çıktığı zaman dosyamı benim için kapatacağından, dosyaları kapatmak ne işe yarar?" İle aynı cevaptır.

Bu sadece iyi bir programlama meselesi. Sen kodunda noktasında) (sizin konuları katılması gerektiğini iplik gerektiği değil artık gereken , ya iş parçacığının kendi kodunuza müdahale etmek için çalışmadığından emin olmalısınız ya da doğru şekilde davranmak daha büyük sistem.

Join () işleminin gerektirebileceği ek süre nedeniyle "Kodumun yanıt vermeyi geciktirmesini istemiyorum" diyebilirsiniz. Bu, bazı senaryolarda mükemmel şekilde geçerli olabilir, ancak şimdi kodunuzun "python ve işletim sisteminin temizlenmesi için ortalıkta dolaştığını" dikkate almanız gerekir. Bunu performans nedenleriyle yaparsanız, bu davranışı belgelemenizi şiddetle tavsiye ederim. Bu, özellikle başkalarının kullanması beklenen bir kitaplık / paket oluşturuyorsanız geçerlidir.

() 'E katılmamak için performans nedenleri dışında bir neden yok ve kodunuzun bu kadar iyi performans göstermesi gerekmediğini iddia ediyorum .


7
İplikleri temizlemek hakkında söyledikleriniz doğru değil. Threading.Thread.join () kaynak koduna bir göz atın. Bu işlevin tek yaptığı bir kilit üzerinde beklemek ve sonra geri dönmek. Aslında hiçbir şey temizlenmedi.
Collin

1
@Collin - İş parçacığının kendisi kaynakları tutuyor olabilir, bu durumda yorumlayıcı ve işletim sisteminin gerçekten de "bozuk "ları temizlemesi gerekecektir.
qneill

1
Tekrar, threading.Thread.join () kaynak koduna bakın. Orada kaynakların toplanmasını tetikleyen hiçbir şey yok.
Collin

Kaynakları tutan iş parçacığı modülü değil (ve sizin de söylediğiniz gibi) değil, iş parçacığının kendisi. Join () kullanmak, iş parçacığının yapmak istediği şeyi bitirmesini beklediğiniz anlamına gelir; bu, kaynakları ayırmayı ve serbest bırakmayı içerebilir.
Chris Cogdon

2
Bekleyip beklememeniz, iş parçacığı tarafından tutulan kaynakların ne zaman serbest bırakılacağını etkilemez. Bunu neden arayarak bağladığından emin değilim join().
Collin
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.