Python'da iş parçacığını nasıl kullanabilirim?


1279

Python'da iş parçacığını anlamaya çalışıyorum. Belgelere ve örneklere baktım, ama açıkçası, birçok örnek aşırı derecede karmaşık ve onları anlamakta zorlanıyorum.

Çok iş parçacıklı işlem için bölünen görevleri nasıl açıkça gösterirsiniz?


31
Bu konuyla ilgili iyi bir genel tartışma Python'un En Zor Sorunu'nda Jeff Knupp'ta bulunabilir. Özetle, diş çekme yeni başlayanlar için değil gibi görünüyor.
Matthew Walker

112
haha, iş parçacığının herkes için olduğunu düşünmeye eğilimliyim, ancak yeni başlayanlar iş parçacığı için değil :))))))
Bohdan

42
Sadece yeni dil özelliklerinden yararlandıkça daha sonra cevapları daha iyi olduğu gibi insanların tüm cevapları okuması gerektiğini işaretlemek için ...
Gwyn Evans

5
Python iş parçacığından gerçekten yararlanmak için çekirdek mantığınızı C olarak yazmayı ve ctypes ile çağırmayı unutmayın.
aaa90210

4
Ben sadece PyPubSub mesaj akışı göndermek ve almak için harika bir yol olduğunu eklemek istedim Konu akışı
ytpillai

Yanıtlar:


1417

Bu soru 2010 yılında sorulduğundan, harita ve havuzlu Python ile basit çoklu kullanımın nasıl yapılacağı konusunda gerçek bir basitlik olmuştur .

Aşağıdaki kod kesinlikle kontrol etmeniz gereken bir makale / blog gönderisinden gelir (bağlantı yok) - Paralellik tek bir satırda: Günlük Günlük İşler için Daha İyi Bir Model . Aşağıda özetleyeceğim - sadece birkaç satır kod olacak:

from multiprocessing.dummy import Pool as ThreadPool
pool = ThreadPool(4)
results = pool.map(my_function, my_array)

Hangi çok iş parçacıklı sürümü:

results = []
for item in my_array:
    results.append(my_function(item))

Açıklama

Harita harika bir küçük işlevdir ve Python kodunuza kolayca paralellik enjekte etmenin anahtarıdır. Bilmeyenler için harita Lisp gibi işlevsel dillerden kaldırılmış bir şeydir. Bir sekans üzerinde başka bir fonksiyonu eşleyen bir fonksiyondur.

Harita, yinelemeyi bizim için dizi üzerinde işler, işlevi uygular ve tüm sonuçları sonunda kullanışlı bir listede saklar.

Resim açıklamasını buraya girin


uygulama

Harita işlevinin paralel sürümleri iki kütüphane tarafından sağlanır: çoklu işlem ve aynı zamanda az bilinen, ancak aynı derecede fantastik adım çocuğu: multiprocessing.dummy.

multiprocessing.dummyçok işlemcili modülle tamamen aynıdır, ancak bunun yerine iş parçacıklarını kullanır ( önemli bir ayrım - CPU yoğun görevler için birden çok işlem kullanın; G / Ç için iş parçacıkları (ve sırasında ):

multiprocessing.dummy, çoklu işlem API'sini çoğaltır, ancak iş parçacığı modülü etrafındaki bir paketleyiciden başka bir şey değildir.

import urllib2
from multiprocessing.dummy import Pool as ThreadPool

urls = [
  'http://www.python.org',
  'http://www.python.org/about/',
  'http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html',
  'http://www.python.org/doc/',
  'http://www.python.org/download/',
  'http://www.python.org/getit/',
  'http://www.python.org/community/',
  'https://wiki.python.org/moin/',
]

# Make the Pool of workers
pool = ThreadPool(4)

# Open the URLs in their own threads
# and return the results
results = pool.map(urllib2.urlopen, urls)

# Close the pool and wait for the work to finish
pool.close()
pool.join()

Ve zamanlama sonuçları:

Single thread:   14.4 seconds
       4 Pool:   3.1 seconds
       8 Pool:   1.4 seconds
      13 Pool:   1.3 seconds

Birden çok argüman iletme ( yalnızca Python 3.3 ve sonraki sürümlerde böyle çalışır ):

Birden çok diziyi geçmek için:

results = pool.starmap(function, zip(list_a, list_b))

Veya bir sabit ve bir dizi iletmek için:

results = pool.starmap(function, zip(itertools.repeat(constant), list_a))

Python'un önceki bir sürümünü kullanıyorsanız, bu geçici çözüm üzerinden birden çok bağımsız değişken iletebilirsiniz ).

( Yararlı yorum için user136036'ya teşekkürler .)


90
Bu sadece oy yok çünkü çok taze gönderildi. Bu cevap güzel çalışıyor ve sözdizimini anlamak için buradaki diğer cevaplardan çok daha kolay olan 'harita' işlevselliğini gösteriyor.
boşta

25
Bu süreçler değil, konu mu? Çok işlem yapmaya çalışıyor gibi görünüyor! = Multithread
AturSams

72
Bu arada, çocuklar with Pool(8) as p: p.map( *whatever* )da muhasebe defterleri yazabilir ve kurtulabilirsiniz.

11
@BarafuAlbino: Olduğu gibi kullanışlıdır, muhtemelen bunun sadece Python 3.3+ ile çalıştığını belirtmek gerekir .
fuglede

9
Bu yanıtı nasıl bırakabilir ve bunun yalnızca G / Ç işlemleri için yararlı olduğunu nasıl anlayabilirsiniz? Bu sadece çoğu durumda işe yaramayan tek bir iş parçacığında çalışır ve aslında normal şekilde yapmaktan daha yavaştır
Frobot

714

İşte basit bir örnek: Birkaç alternatif URL denemeniz ve yanıt vermek için ilk URL'nin içeriğini döndürmeniz gerekiyor.

import Queue
import threading
import urllib2

# Called by each thread
def get_url(q, url):
    q.put(urllib2.urlopen(url).read())

theurls = ["http://google.com", "http://yahoo.com"]

q = Queue.Queue()

for u in theurls:
    t = threading.Thread(target=get_url, args = (q,u))
    t.daemon = True
    t.start()

s = q.get()
print s

Bu, iş parçacığının basit bir optimizasyon olarak kullanıldığı bir durumdur: her alt iplik, içeriğini sıraya koymak için bir URL'nin çözümlenmesini ve yanıt vermesini bekler; her bir iş parçacığı bir daemon'dur (ana iş parçacığı biterse süreci devam ettirmez - bu daha yaygın değildir); ana iş parçacığı tüm alt konuları başlatır get, bunlardan biri yapılana kadar beklemek için kuyrukta bir a yapar put, sonra sonuçları yayar ve sonlandırır (bu daemon iş parçacıkları oldukları için hala çalışmakta olan alt başlıkları alır).

Python'da iş parçacıklarının doğru kullanımı her zaman G / Ç işlemlerine bağlıdır (CPython zaten CPU'ya bağlı görevleri çalıştırmak için birden fazla çekirdek kullanmadığından, diş açmanın tek nedeni bazı G / Ç'leri beklerken işlemi engellememektir. ). Kuyruklar neredeyse her zaman işi iş parçacıklarına ayırmanın ve / veya işin sonuçlarını toplamanın en iyi yoludur ve kendiliğinden iş parçacığı açısından güvenlidirler, böylece sizi kilitler, koşullar, olaylar, semaforlar ve diğer iç ortamlar hakkında endişelenmekten kurtarırlar. iplik koordinasyonu / iletişim kavramları.


10
Tekrar teşekkürler, MartelliBot. Tüm URL'lerin yanıt vermesini beklemek için örneği güncelledim: import Queue, threading, urllib2 q = Queue.Queue () urls = '' ' a.com b.com c.com' ''. Split () urls_received = 0 def get_url (q, url): req = urllib2.Request (url) resp = urllib2.urlopen (req) q.put (resp.read ()) global urls_received urls_received + = 1 urls_received urls: u = threading.Thread (hedef = get_url, args = (q, u)) t.daemon = q.empty () ve urls_received <len (urls): s = q.get () yazdırılıyor
htmldrum

3
@JRM: aşağıdaki bir sonraki cevaba bakarsanız, iş parçacıkları bitene kadar beklemenin daha iyi bir yolunun join()yöntemi kullanmak olacağını düşünüyorum , çünkü bu işlem ana işlemciyi sürekli olarak işlemci tüketmeden bitene kadar bekletecektir. değeri kontrol. @Alex: teşekkürler, iş parçacıklarını nasıl kullanacağımı anlamak için tam da ihtiyacım olan şey bu.
krs013

6
Python3 için, 'import urllib2' yerine 'import urllib.request olarak urllib2' yazın. ve parantezleri print deyimine yerleştirir.
Harvey

5
Python 3 için Queuemodül adını değiştirin queue. Yöntem adı aynı.
JSmyth

2
Çözümün yalnızca sayfalardan birini yazdıracağını unutmayın. Her iki sayfayı kuyruktan yazdırmak için komutu yeniden çalıştırın: s = q.get() print s @ krs013 joinÇünkü Queue.get () engelleniyor.
Tom Anderson

256

NOT : Python'da gerçek paralelleştirme için, paralel olarak çalışan birden çok işlemi çatallamak için çok işlem modülünü kullanmalısınız (global yorumlayıcı kilidi nedeniyle, Python iş parçacıkları araya ekleme sağlar, ancak aslında paralel olarak değil seri olarak yürütülür ve yalnızca G / Ç işlemlerini harmanlarken kullanışlıdır).

Ancak, yalnızca serpiştirme arıyorsanız (veya global yorumlayıcı kilidine rağmen paralelleştirilebilen G / Ç işlemleri yapıyorsanız), iş parçacığı modülü başlangıç ​​noktasıdır. Gerçekten basit bir örnek olarak, alt aralıkları paralel olarak toplayarak geniş bir aralığı toplama sorununu ele alalım:

import threading

class SummingThread(threading.Thread):
     def __init__(self,low,high):
         super(SummingThread, self).__init__()
         self.low=low
         self.high=high
         self.total=0

     def run(self):
         for i in range(self.low,self.high):
             self.total+=i


thread1 = SummingThread(0,500000)
thread2 = SummingThread(500000,1000000)
thread1.start() # This actually causes the thread to run
thread2.start()
thread1.join()  # This waits until the thread has completed
thread2.join()
# At this point, both threads have completed
result = thread1.total + thread2.total
print result

Yukarıdakilerin kesinlikle hiçbir I / O yapmadığı ve global yorumlayıcı kilidi nedeniyle CPython'da serpiştirilmiş olsa da (bağlam anahtarlama ek yükü ile) seri olarak yürütüleceğinden çok aptal bir örnek olduğuna dikkat edin.


16
@Alex, pratik olduğunu söylemedim, ancak OP'nin istediği şey olduğunu düşünüyorum, konuların nasıl tanımlanacağını ve ortaya çıkacağını gösteriyor.
Michael Aaron Safyan

6
Bu, iş parçacıklarının nasıl tanımlanacağını ve ortaya çıkacağını gösterse de, aslında alt aralıkları paralel olarak toplamaz. thread1ana iş parçacığı bloke edilirken tamamlanana kadar çalışır, sonra aynı şey olur thread2, daha sonra ana iş parçacığı devam eder ve biriktirdikleri değerleri yazdırır.
martineau

Olmamalı mı super(SummingThread, self).__init__()? Stackoverflow.com/a/2197625/806988'de
James Andres

@JamesAndres, "SummingThread" den kimsenin miras almadığını varsayarsak, ikisinden biri iyi çalışır; böyle bir durumda süper (SummingThread, self), bir metod çözme sırası (MRO) sonraki sınıf aramak için sadece fantezi bir yoludur.Thread (ve daha sonra her iki durumda da init çağırmak ). Ancak haklısın, super () kullanmak mevcut Python için daha iyi bir stil. Süper, bu cevabı verdiğim zaman nispeten yeni idi, bu yüzden super () kullanmak yerine doğrudan süper sınıfa çağırıyorum. Yine de süper kullanmak için güncelleyeceğim.
Michael Aaron Safyan

14
UYARI: Bu gibi görevlerde çoklu iş parçacığı kullanmayın! Dave Beazley tarafından gösterildiği gibi: dabeaz.com/python/NewGIL.pdf , 2 CPU'daki 2 python iş parçacığı, 1 CPU'daki 1 iş parçacığından 2 kez ve 1 CPU'daki 2 iş parçacığından 1,5 kat daha ağır CPU işini gerçekleştirir. Bu tuhaf davranış, OS ve Python arasındaki çabaların yanlış koordinasyonundan kaynaklanmaktadır. Dişler için gerçek hayattaki kullanım durumu, bir G / Ç ağır görevidir. Ağ üzerinden okuma / yazma işlemi gerçekleştirdiğinizde, verilerin işlenmesini / yazılmasını beklerken bir iş parçacığı koymak, CPU'yu veri işlemesi gereken başka bir iş parçacığına arka plana geçirmek ve değiştirmek mantıklıdır.
Boris Burkov

98

Belirtilen diğerleri gibi, CPython yalnızca GIL nedeniyle G / Ç beklemeleri için iş parçacığı kullanabilir .

CPU'ya bağlı görevler için birden çok çekirdekten yararlanmak istiyorsanız, çoklu işleme kullanın :

from multiprocessing import Process

def f(name):
    print 'hello', name

if __name__ == '__main__':
    p = Process(target=f, args=('bob',))
    p.start()
    p.join()

33
bunun ne yaptığını biraz açıklayabilir misiniz?
pandita

5
@pandita: kod bir işlem oluşturur, sonra başlatır. Şimdi aynı anda iki şey oluyor: programın ana satırı ve hedefle başlayan süreç, ffonksiyon. Buna paralel olarak, ana program şimdi sürecin kendisinden çıkmasını beklemektedir join. Ana parça yeni çıkmışsa, alt işlem tamamlanabilir veya tamamlanmayabilir, bu nedenle a'nın joinyapılması her zaman önerilir.
johntellsall

1
mapİşlevi içeren genişletilmiş bir yanıt burada: stackoverflow.com/a/28463266/2327328
philshem

2
@philshem Gönderdiğiniz bağlantı burada belirtildiği gibi bir iş parçacığı havuzu (işlemler değil) kullanıyorsa dikkatli olun stackoverflow.com/questions/26432411/… . Ancak bu cevap bir süreç kullanıyor. Bu şeyler için yeniyim, ancak (GIL nedeniyle) Python'da multithreading kullanırken yalnızca belirli durumlarda performans kazancı elde edeceğiniz gibi görünüyor. Bununla birlikte, bir işlem havuzunun kullanılması, bir işlem üzerinde 1'den fazla çekirdek çalışması yaparak çok çekirdekli bir işlemciden yararlanabilir.
user3731622

3
Bu aslında yararlı bir şey yapmak ve birden fazla CPU çekirdeğinden yararlanmak için en iyi cevaptır
Frobot

92

Sadece bir not: İplik geçirme için kuyruk gerekmez.

Bu, eşzamanlı olarak çalışan 10 işlemi gösterdiğini hayal edebileceğim en basit örnektir.

import threading
from random import randint
from time import sleep


def print_number(number):

    # Sleeps a random 1 to 10 seconds
    rand_int_var = randint(1, 10)
    sleep(rand_int_var)
    print "Thread " + str(number) + " slept for " + str(rand_int_var) + " seconds"

thread_list = []

for i in range(1, 10):

    # Instantiates the thread
    # (i) does not make a sequence, so (i,)
    t = threading.Thread(target=print_number, args=(i,))
    # Sticks the thread in a list so that it remains accessible
    thread_list.append(t)

# Starts threads
for thread in thread_list:
    thread.start()

# This blocks the calling thread until the thread whose join() method is called is terminated.
# From http://docs.python.org/2/library/threading.html#thread-objects
for thread in thread_list:
    thread.join()

# Demonstrates that the main process waited for threads to complete
print "Done"

3
Son alıntıyı yapmak için "Tamamlandı" yazdırıldı "Tamamlandı"
iChux

1
Bu örneği Martelli'ninkinden daha çok seviyorum, oynamak daha kolay. Ancak, neler olup bittiğini biraz daha net hale getirmek için printNumber'ın aşağıdakileri yapmasını öneriyorum: üzerinde uyumadan önce bir değişkeni zarı kaydetmeli ve sonra baskı "Thread" + str ( numarası) + "için uyudu" theRandintVariable + "saniye"
Nickolai

Her iş parçacığının ne zaman bittiğini bilmenin bir yolu var mı?
Matt

1
@Matt Böyle bir şey yapmanın birkaç yolu vardır, ancak bu sizin ihtiyaçlarınıza bağlıdır. Bunun bir yolu, bir while döngüsünde izlenen ve iş parçacığının sonunda güncellenen tek birtoniği veya diğer herkes tarafından erişilebilir bir değişkeni güncellemektir.
Douglas Adams

2
İkinci forloop'a gerek yok thread.start(), ilk loop'u arayabilirsiniz .
Mark Mishyn

49

Alex Martelli'nin cevabı bana yardımcı oldu. Ancak, burada daha yararlı olduğunu düşündüğüm değiştirilmiş bir versiyon (en azından benim için).

Güncellendi: Python 2 ve Python 3'te çalışır

try:
    # For Python 3
    import queue
    from urllib.request import urlopen
except:
    # For Python 2 
    import Queue as queue
    from urllib2 import urlopen

import threading

worker_data = ['http://google.com', 'http://yahoo.com', 'http://bing.com']

# Load up a queue with your data. This will handle locking
q = queue.Queue()
for url in worker_data:
    q.put(url)

# Define a worker function
def worker(url_queue):
    queue_full = True
    while queue_full:
        try:
            # Get your data off the queue, and do some work
            url = url_queue.get(False)
            data = urlopen(url).read()
            print(len(data))

        except queue.Empty:
            queue_full = False

# Create as many threads as you want
thread_count = 5
for i in range(thread_count):
    t = threading.Thread(target=worker, args = (q,))
    t.start()

6
Neden sadece istisnadan vazgeçmiyorsunuz?
Stavros Korokithakis

1
yapabilirsin, sadece kişisel tercih
JimJty

1
Kodu çalıştırmadım, ancak konuları arızalı hale getirmenize gerek yok mu? Bence bu son for-loop'tan sonra, programınız çıkabilir - en azından öyle olmalı çünkü iş parçacıkları bu şekilde çalışmalıdır. Ben daha iyi bir yaklaşım kuyrukta işçi verilerini koymak, ama sonra sadece bir mainloop olabilir çünkü bir sıraya çıktı koymak olmadığını düşünüyorum kolları , bilgi işçilerinden kuyruğa giren, ancak şimdi aynı zamanda diş değil ve erken çıkmayacağını biliyorsunuz .
dylnmc

1
@dylnmc, bu benim kullanım durumumun dışında (giriş kuyruğum önceden tanımlanmış).
Rotanıza

@JimJty neden bu hatayı alıyorum biliyor musunuz: import Queue ModuleNotFoundError: No module named 'Queue'Bazı mesajlar python 3.6.5 çalıştırıyorum python 3.6.5 olduğunu söyledi queueama ben değiştirdikten sonra bile, hala çalışmıyor
user9371654

25

Bir işlev verildiğinde şu şekilde işleyin f:

import threading
threading.Thread(target=f).start()

Argümanlarını f

threading.Thread(target=f, args=(a,b,c)).start()

Bu çok açık. İşiniz bittiğinde ipliklerin kapanmasını nasıl sağlarsınız?
cameronroytaylor

Anladığım kadarıyla, işlevden çıkıldığında Threadnesne temizlenir. Belgelere bakın . is_alive()Gerekirse bir iş parçacığını kontrol etmek için kullanabileceğiniz bir yöntem vardır .
starfry

is_aliveYöntemi gördüm , ancak iş parçacığına nasıl uygulanacağını anlayamadım. Atamayı thread1=threading.Thread(target=f).start()ve sonra kontrol etmeyi denedim thread1.is_alive(), ancak thread1nüfuslu None, bu yüzden orada şans yok. Konuya erişmenin başka bir yolu olup olmadığını biliyor musunuz?
cameronroytaylor

4
İş parçacığı nesnesini bir değişkene atamanız ve sonra bu değişken: thread1=threading.Thread(target=f)ardından onu kullanarak başlatmanız gerekir thread1.start(). Sonra yapabilirsin thread1.is_alive().
starfry

1
İşe yaradı. Ve evet, fonksiyon çıktığı anda testlerle thread1.is_alive()geri dönüş Falseyapar.
cameronroytaylor

25

Bu çok yararlı buldum: çekirdekler gibi çok sayıda iş parçacığı oluşturmak ve (bu durumda, bir kabuk programı çağırarak) görevleri (çok sayıda) yürütmek izin:

import Queue
import threading
import multiprocessing
import subprocess

q = Queue.Queue()
for i in range(30): # Put 30 tasks in the queue
    q.put(i)

def worker():
    while True:
        item = q.get()
        # Execute a task: call a shell program and wait until it completes
        subprocess.call("echo " + str(item), shell=True)
        q.task_done()

cpus = multiprocessing.cpu_count() # Detect number of cores
print("Creating %d threads" % cpus)
for i in range(cpus):
     t = threading.Thread(target=worker)
     t.daemon = True
     t.start()

q.join() # Block until all tasks are done

@shavenwarthog kişinin ihtiyaçlarına göre "cpus" değişkenini ayarlayabileceğinden emin olun. Her neyse, alt işlem çağrısı alt süreçleri ortaya çıkaracak ve bunlar OS tarafından cpus tahsis edilecektir (python'un "üst süreci" alt süreçler için "aynı CPU" anlamına gelmez).
yunus

2
haklısınız, "ana işlemle aynı CPU'da iş parçacıkları başlatılır" hakkındaki yorumum yanlış. Yanıtınız için teşekkürler!
johntellsall

1
aynı bellek alanını kullanan çoklu iş parçacığının aksine, çoklu işlemin değişkenleri / verileri kolayca paylaşamayacağını belirtmek gerekir. +1 olsa.
2014'te 9:07

22

Python 3, paralel görevler başlatma imkanına sahiptir . Bu işimizi kolaylaştırır.

İş parçacığı havuzu ve işlem havuzu vardır .

Aşağıdakiler bir fikir verir:

ThreadPoolExecutor Örneği ( kaynak )

import concurrent.futures
import urllib.request

URLS = ['http://www.foxnews.com/',
        'http://www.cnn.com/',
        'http://europe.wsj.com/',
        'http://www.bbc.co.uk/',
        'http://some-made-up-domain.com/']

# Retrieve a single page and report the URL and contents
def load_url(url, timeout):
    with urllib.request.urlopen(url, timeout=timeout) as conn:
        return conn.read()

# We can use a with statement to ensure threads are cleaned up promptly
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
    # Start the load operations and mark each future with its URL
    future_to_url = {executor.submit(load_url, url, 60): url for url in URLS}
    for future in concurrent.futures.as_completed(future_to_url):
        url = future_to_url[future]
        try:
            data = future.result()
        except Exception as exc:
            print('%r generated an exception: %s' % (url, exc))
        else:
            print('%r page is %d bytes' % (url, len(data)))

ProcessPoolExecutor ( kaynak )

import concurrent.futures
import math

PRIMES = [
    112272535095293,
    112582705942171,
    112272535095293,
    115280095190773,
    115797848077099,
    1099726899285419]

def is_prime(n):
    if n % 2 == 0:
        return False

    sqrt_n = int(math.floor(math.sqrt(n)))
    for i in range(3, sqrt_n + 1, 2):
        if n % i == 0:
            return False
    return True

def main():
    with concurrent.futures.ProcessPoolExecutor() as executor:
        for number, prime in zip(PRIMES, executor.map(is_prime, PRIMES)):
            print('%d is prime: %s' % (number, prime))

if __name__ == '__main__':
    main()

18

Çarpıcı yeni eşzamanlı.futures modülünü kullanma

def sqr(val):
    import time
    time.sleep(0.1)
    return val * val

def process_result(result):
    print(result)

def process_these_asap(tasks):
    import concurrent.futures

    with concurrent.futures.ProcessPoolExecutor() as executor:
        futures = []
        for task in tasks:
            futures.append(executor.submit(sqr, task))

        for future in concurrent.futures.as_completed(futures):
            process_result(future.result())
        # Or instead of all this just do:
        # results = executor.map(sqr, tasks)
        # list(map(process_result, results))

def main():
    tasks = list(range(10))
    print('Processing {} tasks'.format(len(tasks)))
    process_these_asap(tasks)
    print('Done')
    return 0

if __name__ == '__main__':
    import sys
    sys.exit(main())

Yürütücü yaklaşımı, daha önce Java ile ellerini kirleten herkese tanıdık gelebilir.

Ayrıca bir yan notta: Evren aklı başında tutmak için, withbağlam kullanmıyorsanız havuzlarınızı / uygulayıcılarınızı kapatmayı unutmayın (bu sizin için çok harika bir şeydir)


17

Benim için, diş çekme için mükemmel örnek asenkron olayları izlemek. Bu koda bak.

# thread_test.py
import threading
import time

class Monitor(threading.Thread):
    def __init__(self, mon):
        threading.Thread.__init__(self)
        self.mon = mon

    def run(self):
        while True:
            if self.mon[0] == 2:
                print "Mon = 2"
                self.mon[0] = 3;

Bir IPython oturumu açıp aşağıdaki gibi bir şey yaparak bu kodla oynayabilirsiniz :

>>> from thread_test import Monitor
>>> a = [0]
>>> mon = Monitor(a)
>>> mon.start()
>>> a[0] = 2
Mon = 2
>>>a[0] = 2
Mon = 2

Bir kaç dakika bekle

>>> a[0] = 2
Mon = 2

1
AttributeError: 'Monitor' nesnesinin 'stop' özelliği yok mu?
pandita

5
Etkinliğinizin gerçekleşmesini beklerken CPU döngülerini patlatıyor musunuz? Her zaman yapacak çok pratik bir şey değil.
mogul

3
Mogul'un dediği gibi, bu sürekli yürütülecek. En azından kısa bir uykuya (örneğin uyku) (0.1) ekleyebilirsiniz.
2014'te

3
Bu, bir çekirdeği boşa harcayan korkunç bir örnektir. En azından bir uyku ekleyin, ancak uygun çözüm bazı sinyalleme mekanizması kullanmaktır.
PureW

16

Çoğu belge ve öğretici Python ThreadingveQueue modülünü yeni başlayanlar için bunaltıcı görünebilir.

Belki de concurrent.futures.ThreadPoolExecutor Python 3 modülünü .

İle birlikte withfıkra ve liste anlama gerçek bir çekicilik olabilir.

from concurrent.futures import ThreadPoolExecutor, as_completed

def get_url(url):
    # Your actual program here. Using threading.Lock() if necessary
    return ""

# List of URLs to fetch
urls = ["url1", "url2"]

with ThreadPoolExecutor(max_workers = 5) as executor:

    # Create threads
    futures = {executor.submit(get_url, url) for url in urls}

    # as_completed() gives you the threads once finished
    for f in as_completed(futures):
        # Get the results
        rs = f.result()

15

Burada gerçek bir işin yapılmadığı birçok örnek gördüm ve çoğunlukla CPU'ya bağlıydı. İşte 10 milyon ila 10,05 milyon arasındaki tüm asal sayıları hesaplayan CPU'ya bağlı bir görev örneği. Burada dört yöntemi de kullandım:

import math
import timeit
import threading
import multiprocessing
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor


def time_stuff(fn):
    """
    Measure time of execution of a function
    """
    def wrapper(*args, **kwargs):
        t0 = timeit.default_timer()
        fn(*args, **kwargs)
        t1 = timeit.default_timer()
        print("{} seconds".format(t1 - t0))
    return wrapper

def find_primes_in(nmin, nmax):
    """
    Compute a list of prime numbers between the given minimum and maximum arguments
    """
    primes = []

    # Loop from minimum to maximum
    for current in range(nmin, nmax + 1):

        # Take the square root of the current number
        sqrt_n = int(math.sqrt(current))
        found = False

        # Check if the any number from 2 to the square root + 1 divides the current numnber under consideration
        for number in range(2, sqrt_n + 1):

            # If divisible we have found a factor, hence this is not a prime number, lets move to the next one
            if current % number == 0:
                found = True
                break

        # If not divisible, add this number to the list of primes that we have found so far
        if not found:
            primes.append(current)

    # I am merely printing the length of the array containing all the primes, but feel free to do what you want
    print(len(primes))

@time_stuff
def sequential_prime_finder(nmin, nmax):
    """
    Use the main process and main thread to compute everything in this case
    """
    find_primes_in(nmin, nmax)

@time_stuff
def threading_prime_finder(nmin, nmax):
    """
    If the minimum is 1000 and the maximum is 2000 and we have four workers,
    1000 - 1250 to worker 1
    1250 - 1500 to worker 2
    1500 - 1750 to worker 3
    1750 - 2000 to worker 4
    so let’s split the minimum and maximum values according to the number of workers
    """
    nrange = nmax - nmin
    threads = []
    for i in range(8):
        start = int(nmin + i * nrange/8)
        end = int(nmin + (i + 1) * nrange/8)

        # Start the thread with the minimum and maximum split up to compute
        # Parallel computation will not work here due to the GIL since this is a CPU-bound task
        t = threading.Thread(target = find_primes_in, args = (start, end))
        threads.append(t)
        t.start()

    # Don’t forget to wait for the threads to finish
    for t in threads:
        t.join()

@time_stuff
def processing_prime_finder(nmin, nmax):
    """
    Split the minimum, maximum interval similar to the threading method above, but use processes this time
    """
    nrange = nmax - nmin
    processes = []
    for i in range(8):
        start = int(nmin + i * nrange/8)
        end = int(nmin + (i + 1) * nrange/8)
        p = multiprocessing.Process(target = find_primes_in, args = (start, end))
        processes.append(p)
        p.start()

    for p in processes:
        p.join()

@time_stuff
def thread_executor_prime_finder(nmin, nmax):
    """
    Split the min max interval similar to the threading method, but use a thread pool executor this time.
    This method is slightly faster than using pure threading as the pools manage threads more efficiently.
    This method is still slow due to the GIL limitations since we are doing a CPU-bound task.
    """
    nrange = nmax - nmin
    with ThreadPoolExecutor(max_workers = 8) as e:
        for i in range(8):
            start = int(nmin + i * nrange/8)
            end = int(nmin + (i + 1) * nrange/8)
            e.submit(find_primes_in, start, end)

@time_stuff
def process_executor_prime_finder(nmin, nmax):
    """
    Split the min max interval similar to the threading method, but use the process pool executor.
    This is the fastest method recorded so far as it manages process efficiently + overcomes GIL limitations.
    RECOMMENDED METHOD FOR CPU-BOUND TASKS
    """
    nrange = nmax - nmin
    with ProcessPoolExecutor(max_workers = 8) as e:
        for i in range(8):
            start = int(nmin + i * nrange/8)
            end = int(nmin + (i + 1) * nrange/8)
            e.submit(find_primes_in, start, end)

def main():
    nmin = int(1e7)
    nmax = int(1.05e7)
    print("Sequential Prime Finder Starting")
    sequential_prime_finder(nmin, nmax)
    print("Threading Prime Finder Starting")
    threading_prime_finder(nmin, nmax)
    print("Processing Prime Finder Starting")
    processing_prime_finder(nmin, nmax)
    print("Thread Executor Prime Finder Starting")
    thread_executor_prime_finder(nmin, nmax)
    print("Process Executor Finder Starting")
    process_executor_prime_finder(nmin, nmax)

main()

İşte Mac OS X dört çekirdekli makinemdeki sonuçlar

Sequential Prime Finder Starting
9.708213827005238 seconds
Threading Prime Finder Starting
9.81836523200036 seconds
Processing Prime Finder Starting
3.2467174359990167 seconds
Thread Executor Prime Finder Starting
10.228896902000997 seconds
Process Executor Finder Starting
2.656402041000547 seconds

1
@TheUnfunCat hiçbir işlem yürütücüsü cpu bağlı görevler için iş parçacığından çok daha iyi değil
PirateApp

1
Harika cevap dostum. Windows üzerinde Python 3.6 (en azından) ThreadPoolExecutor CPU ağır görevler için iyi bir şey yapmadığını onaylayabilirsiniz. Hesaplama için çekirdekler kullanmıyor. ProcessPoolExecutor verileri ortaya çıkardığı HER işleme kopyalarken, büyük matrisler için ölümcüldür.
Anatoly Alekseev

1
Çok faydalı bir örnek, ama nasıl çalıştığını anlamıyorum. Biz gerek if __name__ == '__main__':aksi ölçüm yumurtlar kendisi ve baskılar, ana aramadan önce denemesi yapılmıştır önce yeni bir süreç başlatmak için ... .
Stein

1
@Stein, bunun sadece Windows'da bir sorun olduğuna inanıyorum.
AMC

12

İş parçacığı kullanarak CSV içe aktarmanın çok basit bir örneği . (Kütüphaneye katılım farklı amaçlar için farklılık gösterebilir.)

Yardımcı İşlevleri:

from threading import Thread
from project import app
import csv


def import_handler(csv_file_name):
    thr = Thread(target=dump_async_csv_data, args=[csv_file_name])
    thr.start()

def dump_async_csv_data(csv_file_name):
    with app.app_context():
        with open(csv_file_name) as File:
            reader = csv.DictReader(File)
            for row in reader:
                # DB operation/query

Sürücü Fonksiyonu:

import_handler(csv_file_name)

9

Basit bir örnek ve bu sorunu kendim halletmem gerektiğinde yararlı bulduğum açıklamalar ile katkıda bulunmak istiyorum.

Bu cevapta Python'un GIL (global tercüman kilidi) hakkında bazı bilgiler ve multiprocessing.dummy ve bazı basit karşılaştırmalar kullanılarak yazılmış basit bir günlük örnek bulacaksınız .

Global Tercüman Kilidi (GIL)

Python, kelimenin tam anlamıyla çoklu iş parçacığına izin vermez. Çok iş parçacıklı bir pakete sahiptir, ancak kodunuzu hızlandırmak için çok iş parçacıklı bir paket istiyorsanız, bunu kullanmak genellikle iyi bir fikir değildir.

Python'un global yorumlayıcı kilidi (GIL) adı verilen bir yapısı vardır. GIL herhangi bir anda 'iş parçacıklarınızdan' yalnızca birinin çalışabilmesini sağlar. Bir iş parçacığı GIL'i alır, küçük bir iş yapar, sonra GIL'i bir sonraki iş parçacığına geçirir.

Bu çok hızlı bir şekilde gerçekleşir, böylece insan gözü için iplikleriniz paralel olarak çalışıyor gibi görünebilir, ancak aynı CPU çekirdeğini kullanarak sırayla.

Tüm bu GIL geçişi yürütmeye ek yük getirir. Bu, kodunuzun daha hızlı çalışmasını istiyorsanız, iş parçacığı paketini kullanmanın genellikle iyi bir fikir olmadığı anlamına gelir.

Python'un iş parçacığı paketini kullanmanın nedenleri var. Bazı şeyleri aynı anda çalıştırmak istiyorsanız ve verimlilik bir endişe kaynağı değilse, tamamen iyi ve kullanışlıdır. Veya bir şey (bazı G / Ç gibi) beklemesi gereken bir kod çalıştırıyorsanız, bu çok mantıklı olabilir. Ancak iş parçacığı kütüphanesi ekstra CPU çekirdeği kullanmanıza izin vermez.

Çoklu iş parçacığı, işletim sistemine (çoklu işlem yaparak) ve Python kodunuzu (örneğin, Spark veya Hadoop ) çağıran bazı harici uygulamalara veya Python kodunuzun çağırdığı bazı kodlara (örneğin: Python kodunuzun pahalı çok iş parçacıklı şeyleri yapan bir C işlevini çağırmasını sağlayın).

Bu Neden Önemli?

Çünkü birçok insan, GIL'in ne olduğunu öğrenmeden önce süslü Python çok iş parçacıklı kodlarında darboğazlar bulmaya çalışırken çok zaman harcıyor.

Bu bilgiler netleştikten sonra işte kodum:

#!/bin/python
from multiprocessing.dummy import Pool
from subprocess import PIPE,Popen
import time
import os

# In the variable pool_size we define the "parallelness".
# For CPU-bound tasks, it doesn't make sense to create more Pool processes
# than you have cores to run them on.
#
# On the other hand, if you are using I/O-bound tasks, it may make sense
# to create a quite a few more Pool processes than cores, since the processes
# will probably spend most their time blocked (waiting for I/O to complete).
pool_size = 8

def do_ping(ip):
    if os.name == 'nt':
        print ("Using Windows Ping to " + ip)
        proc = Popen(['ping', ip], stdout=PIPE)
        return proc.communicate()[0]
    else:
        print ("Using Linux / Unix Ping to " + ip)
        proc = Popen(['ping', ip, '-c', '4'], stdout=PIPE)
        return proc.communicate()[0]


os.system('cls' if os.name=='nt' else 'clear')
print ("Running using threads\n")
start_time = time.time()
pool = Pool(pool_size)
website_names = ["www.google.com","www.facebook.com","www.pinterest.com","www.microsoft.com"]
result = {}
for website_name in website_names:
    result[website_name] = pool.apply_async(do_ping, args=(website_name,))
pool.close()
pool.join()
print ("\n--- Execution took {} seconds ---".format((time.time() - start_time)))

# Now we do the same without threading, just to compare time
print ("\nRunning NOT using threads\n")
start_time = time.time()
for website_name in website_names:
    do_ping(website_name)
print ("\n--- Execution took {} seconds ---".format((time.time() - start_time)))

# Here's one way to print the final output from the threads
output = {}
for key, value in result.items():
    output[key] = value.get()
print ("\nOutput aggregated in a Dictionary:")
print (output)
print ("\n")

print ("\nPretty printed output: ")
for key, value in output.items():
    print (key + "\n")
    print (value)

7

İşte size yardımcı olacak basit bir örnek ile çoklu iş parçacığı. Bunu çalıştırabilir ve Python'da çoklu iş parçacığının nasıl çalıştığını kolayca anlayabilirsiniz. Önceki iş parçacığı çalışmalarını bitirinceye kadar diğer iş parçacıklarına erişimi engellemek için bir kilit kullandım. Bu kod satırını kullanarak,

tLock = threading.BoundedSemaphore (değer = 4)

bir seferde birkaç işleme izin verebilir ve daha sonra veya önceki işlemlerin bitiminden sonra çalışacak olan diğer iş parçacıklarını tutabilirsiniz.

import threading
import time

#tLock = threading.Lock()
tLock = threading.BoundedSemaphore(value=4)
def timer(name, delay, repeat):
    print  "\r\nTimer: ", name, " Started"
    tLock.acquire()
    print "\r\n", name, " has the acquired the lock"
    while repeat > 0:
        time.sleep(delay)
        print "\r\n", name, ": ", str(time.ctime(time.time()))
        repeat -= 1

    print "\r\n", name, " is releaseing the lock"
    tLock.release()
    print "\r\nTimer: ", name, " Completed"

def Main():
    t1 = threading.Thread(target=timer, args=("Timer1", 2, 5))
    t2 = threading.Thread(target=timer, args=("Timer2", 3, 5))
    t3 = threading.Thread(target=timer, args=("Timer3", 4, 5))
    t4 = threading.Thread(target=timer, args=("Timer4", 5, 5))
    t5 = threading.Thread(target=timer, args=("Timer5", 0.1, 5))

    t1.start()
    t2.start()
    t3.start()
    t4.start()
    t5.start()

    print "\r\nMain Complete"

if __name__ == "__main__":
    Main()

5

Borç ile bu yazı biz çoklu işlem, çoklu arasında seçim biliyorum ve zaman uyumsuz / asynciove bunların kullanımı.

Python 3 , eşzamanlılık ve paralellik için yeni bir yerleşik kütüphaneye sahiptir: concurrent.futures

Bu yüzden dört görevi (yani .sleep()yöntemi) çalıştırmak için bir deney yoluyla Threading-Poolgöstereceğim:

from concurrent.futures import ThreadPoolExecutor, as_completed
from time import sleep, time

def concurrent(max_worker=1):
    futures = []

    tick = time()
    with ThreadPoolExecutor(max_workers=max_worker) as executor:
        futures.append(executor.submit(sleep, 2))  # Two seconds sleep
        futures.append(executor.submit(sleep, 1))
        futures.append(executor.submit(sleep, 7))
        futures.append(executor.submit(sleep, 3))

        for future in as_completed(futures):
            if future.result() is not None:
                print(future.result())

    print('Total elapsed time by {} workers:'.format(max_worker), time()-tick)

concurrent(5)
concurrent(4)
concurrent(3)
concurrent(2)
concurrent(1)

Çıktı:

Total elapsed time by 5 workers: 7.007831811904907
Total elapsed time by 4 workers: 7.007944107055664
Total elapsed time by 3 workers: 7.003149509429932
Total elapsed time by 2 workers: 8.004627466201782
Total elapsed time by 1 workers: 13.013478994369507

[ NOT ]:

  • Yukarıdaki sonuçlarda da görebileceğiniz gibi, en iyi durum bu dört görev için 3 işçiydi.
  • Eğer I yerine süreç görevi varsa / Ç bağlı ya da (engelleme multiprocessingvs threadingdeğiştirmek olabilir) ThreadPoolExecutoriçin ProcessPoolExecutor.

4

Önceki çözümlerin hiçbiri GNU / Linux sunucumda birden fazla çekirdek kullanmadı (burada yönetici haklarına sahip değilim). Sadece tek bir çekirdek üzerinde koştular.

os.forkBirden çok işlemi ortaya çıkarmak için alt düzey arayüzü kullandım. Bu benim için çalışan kod:

from os import fork

values = ['different', 'values', 'for', 'threads']

for i in range(len(values)):
    p = fork()
    if p == 0:
        my_function(values[i])
        break

2
import threading
import requests

def send():

  r = requests.get('https://www.stackoverlow.com')

thread = []
t = threading.Thread(target=send())
thread.append(t)
t.start()

1
@sP_ Tahmin ediyorum çünkü daha sonra onların bitmesini beklemek için iplik nesneleri var.
Aleksandar Makragić

1
t = iş parçacığı. İş parçacığı (hedef = gönderme ()) t = iş parçacığı olmalıdır. İş parçacığı (hedef = gönderme)
TRiNE

Bu cevabı küçümsüyorum çünkü ciddi bir yanlışlık içermesinin yanı sıra mevcut cevaplar üzerinde nasıl geliştiğine dair bir açıklama sağlamıyor.
Jules
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.