Bagaimana saya bisa menggunakan threading dengan Python?

1281

Saya mencoba memahami threading dengan Python. Saya telah melihat dokumentasi dan contoh-contohnya, tetapi sejujurnya, banyak contoh yang terlalu canggih dan saya kesulitan memahaminya.

Bagaimana Anda dengan jelas menunjukkan tugas yang dibagi untuk multi-threading?

albruno
sumber
31
Diskusi umum yang baik tentang topik ini dapat ditemukan di Python's Hardest Problem oleh Jeff Knupp. Singkatnya, sepertinya threading bukan untuk pemula.
Matthew Walker
112
haha, saya cenderung berpikir bahwa threading adalah untuk semua orang, tetapi pemula bukan untuk threading :)))))
Bohdan
42
Hanya untuk memberi tanda bahwa orang harus membaca semua jawaban karena jawaban yang belakangan bisa dibilang lebih baik karena fitur bahasa baru dimanfaatkan ...
Gwyn Evans
5
Ingatlah untuk menulis logika inti Anda dalam C dan menyebutnya melalui ctypes untuk benar-benar memanfaatkan threading Python.
aaa90210
4
Saya hanya ingin menambahkan bahwa PyPubSub adalah cara yang bagus untuk mengirim dan menerima pesan untuk mengontrol aliran Thread
ytpillai

Jawaban:

1418

Sejak pertanyaan ini diajukan pada tahun 2010, telah ada penyederhanaan nyata dalam bagaimana melakukan multithreading sederhana dengan Python dengan peta dan kumpulan .

Kode di bawah ini berasal dari artikel / posting blog yang harus Anda periksa (tidak ada afiliasi) - Paralelisme dalam satu baris: Model yang Lebih Baik untuk Tugas Sehari-Hari Threading . Saya akan meringkas di bawah ini - akhirnya hanya menjadi beberapa baris kode:

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

Yang merupakan versi multithreaded dari:

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

Deskripsi

Peta adalah fungsi kecil yang keren, dan kunci untuk dengan mudah menyuntikkan paralelisme ke dalam kode Python Anda. Bagi mereka yang tidak terbiasa, peta adalah sesuatu yang diangkat dari bahasa fungsional seperti Lisp. Ini adalah fungsi yang memetakan fungsi lain melalui urutan.

Peta menangani iterasi di atas urutan untuk kami, menerapkan fungsi, dan menyimpan semua hasil dalam daftar praktis di akhir.

Masukkan deskripsi gambar di sini


Penerapan

Versi paralel dari fungsi peta disediakan oleh dua pustaka: multiprocessing, dan juga anak langkahnya yang sedikit diketahui, tetapi sama-sama fantastis: multiprocessing.dummy.

multiprocessing.dummypersis sama dengan modul multiprosesing, tetapi menggunakan utas sebagai gantinya ( perbedaan penting - gunakan beberapa proses untuk tugas-tugas yang intensif CPU; utas untuk (dan selama) I / O ):

multiprocessing.dummy mereplikasi API multiprocessing, tetapi tidak lebih dari pembungkus di sekitar modul threading.

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

Dan hasil waktu:

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

Melewati banyak argumen (hanya berfungsi seperti ini di Python 3.3 dan yang lebih baru ):

Untuk melewati beberapa larik:

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

Atau untuk melewatkan konstanta dan array:

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

Jika Anda menggunakan versi Python yang lebih lama, Anda bisa meneruskan beberapa argumen melalui solusi ini ).

(Terima kasih kepada pengguna136036 untuk komentar bermanfaat.)

philshem
sumber
90
Ini hanya kurang suara karena begitu baru diposting. Jawaban ini berfungsi dengan baik dan menunjukkan fungsionalitas 'peta' yang memberikan sintaksis yang jauh lebih mudah dipahami daripada jawaban lain di sini.
idle
25
Apakah ini utas dan bukan proses? Sepertinya ini mencoba untuk multiprocess! = Multithread
AturSams
72
Ngomong-ngomong, teman-teman, kamu bisa menulis with Pool(8) as p: p.map( *whatever* )dan menyingkirkan garis pembukuan juga.
11
@BarafuAlbino: Berguna seperti itu, mungkin perlu dicatat bahwa ini hanya bekerja di Python 3.3+ .
fuglede
9
Bagaimana Anda dapat meninggalkan jawaban ini dan tidak menyebutkan bahwa ini hanya berguna untuk operasi I / O? Ini hanya berjalan pada satu utas yang tidak berguna untuk kebanyakan kasus, dan sebenarnya lebih lambat daripada hanya melakukannya dengan cara biasa
Frobot
714

Berikut ini contoh sederhana: Anda perlu mencoba beberapa URL alternatif dan mengembalikan konten yang pertama merespons.

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

Ini adalah kasus di mana threading digunakan sebagai optimisasi sederhana: setiap subthread sedang menunggu URL untuk menyelesaikan dan merespons, untuk menempatkan kontennya pada antrian; setiap utas adalah daemon (tidak akan mempertahankan proses jika utas utama berakhir - itu lebih umum daripada tidak); utas utama memulai semua subthreads, melakukan getantrian untuk menunggu sampai salah satu dari mereka melakukan aput , kemudian memancarkan hasil dan mengakhiri (yang mencatat semua subthreads yang mungkin masih berjalan, karena mereka adalah daemon threads).

Penggunaan utas yang benar dalam Python selalu terhubung ke operasi I / O (karena CPython tidak menggunakan banyak inti untuk menjalankan tugas yang terikat CPU, satu-satunya alasan untuk threading adalah tidak memblokir proses sementara ada menunggu beberapa I / O ). Antrian hampir selalu merupakan cara terbaik untuk membuat pekerjaan keluar dari utas ke thread dan / atau mengumpulkan hasil pekerjaan, dan, pada dasarnya, mereka secara intrinsik aman, sehingga mereka menyelamatkan Anda dari kekhawatiran tentang kunci, kondisi, peristiwa, semaphore, dan inter Konsep koordinasi / komunikasi.

Alex Martelli
sumber
10
Terima kasih lagi, MartelliBot. Saya telah memperbarui contoh untuk menunggu semua URL merespons: impor Antrian, threading, urllib2 q = Antrian.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 url cetak diterima untuk Anda di url: t = threading.Thread (target = get_url, args = (q, u)) t.daemon = True t.start () sementara q.empty () dan urls_received <len (url): s = q.get () print s
htmldrum
3
@ JRM: jika Anda melihat jawaban berikutnya di bawah ini, saya pikir cara yang lebih baik untuk menunggu sampai utas selesai adalah dengan menggunakan join()metode ini, karena itu akan membuat utas utama menunggu sampai selesai tanpa memakan prosesor dengan terus-menerus memeriksa nilainya. @Alex: terima kasih, inilah yang saya butuhkan untuk memahami cara menggunakan utas.
krs013
6
Untuk python3, ganti 'import urllib2' dengan 'import urllib.request as urllib2'. dan letakkan tanda kurung dalam pernyataan cetak.
Harvey
5
Untuk python 3 ganti Queuenama modul dengan queue. Nama metode sama.
JSmyth
2
Saya perhatikan bahwa solusi hanya akan mencetak salah satu halaman. Untuk mencetak kedua halaman dari antrian cukup jalankan perintah lagi: s = q.get() print s @ krs013 Anda tidak memerlukannya joinkarena Queue.get () sedang memblokir.
Tom Anderson
256

CATATAN : Untuk paralelisasi aktual dalam Python, Anda harus menggunakan multiprosesor modul untuk memotong beberapa proses yang dieksekusi secara paralel (karena kunci juru bahasa global, benang Python menyediakan interleaving, tetapi sebenarnya dieksekusi secara seri, bukan paralel, dan hanya berguna saat interleaving operasi I / O).

Namun, jika Anda hanya mencari interleaving (atau sedang melakukan operasi I / O yang dapat diparalelkan meskipun ada kunci juru bahasa global), maka modul threading adalah tempat untuk memulai. Sebagai contoh yang sangat sederhana, mari kita pertimbangkan masalah menjumlahkan rentang besar dengan menjumlahkan subrang secara paralel:

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

Perhatikan bahwa di atas adalah contoh yang sangat bodoh, karena sama sekali tidak ada I / O dan akan dieksekusi secara serial meskipun diselingi (dengan tambahan overhead konteks switching) di CPython karena kunci juru bahasa global.

Michael Aaron Safyan
sumber
16
@Alex, saya tidak mengatakan itu praktis, tapi itu menunjukkan bagaimana mendefinisikan dan memunculkan thread, yang saya pikir adalah apa yang diinginkan OP.
Michael Aaron Safyan
6
Meskipun ini menunjukkan bagaimana mendefinisikan dan memunculkan thread, sebenarnya tidak menjumlahkan subrang secara paralel. thread1berjalan sampai selesai sementara utas utama blok, maka hal yang sama terjadi dengan thread2, maka utas utama melanjutkan dan mencetak nilai-nilai yang mereka kumpulkan.
martineau
Bukankah seharusnya begitu super(SummingThread, self).__init__()? Seperti dalam stackoverflow.com/a/2197625/806988
James Andres
@JamesAndres, dengan asumsi bahwa tidak ada yang mewarisi dari "SummingThread", maka salah satu berfungsi dengan baik; dalam kasus seperti itu super (SummingThread, self) hanyalah cara mewah untuk mencari kelas berikutnya dalam urutan resolusi metode (MRO), yang threading.Thread (dan kemudian memanggil init pada yang di kedua kasus). Anda benar, meskipun, dalam menggunakan super () adalah gaya yang lebih baik untuk Python saat ini. Super relatif baru pada saat saya memberikan jawaban ini, karenanya memanggil langsung ke kelas super daripada menggunakan super (). Saya akan memperbarui ini untuk menggunakan super.
Michael Aaron Safyan
14
PERINGATAN: Jangan gunakan multithreading dalam tugas seperti ini! Seperti yang ditunjukkan oleh Dave Beazley: dabeaz.com/python/NewGIL.pdf , 2 utas python pada 2 CPU menjalankan tugas yang berat CPU 2 kali lebih lambat dari 1 utas pada 1 CPU dan 1,5 kali lebih lambat dari 2 utas pada 1 CPU. Perilaku aneh ini adalah karena salah koordinasi upaya antara OS dan Python. Kasus penggunaan kehidupan nyata untuk utas adalah tugas berat I / O. Misalnya ketika Anda melakukan baca / tulis melalui jaringan, masuk akal untuk meletakkan utas, menunggu data untuk dibaca / ditulis, untuk latar belakang dan beralih CPU ke utas lain, yang perlu memproses data.
Boris Burkov
98

Seperti yang lain disebutkan, CPython dapat menggunakan utas hanya untuk I / O menunggu karena GIL .

Jika Anda ingin mendapat manfaat dari banyak inti untuk tugas yang terikat CPU, gunakan multiprosesor :

from multiprocessing import Process

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

if __name__ == '__main__':
    p = Process(target=f, args=('bob',))
    p.start()
    p.join()
Kai
sumber
33
dapatkah Anda menjelaskan sedikit apa yang dilakukannya?
pandita
5
@ Pandita: kode menciptakan proses, lalu memulainya. Jadi sekarang ada dua hal yang terjadi sekaligus: jalur utama program, dan proses yang dimulai dengan target, fberfungsi. Secara paralel, program utama sekarang hanya menunggu proses untuk keluar, joinmengikuti itu. Jika bagian utama baru saja keluar, subproses mungkin atau mungkin tidak berjalan sampai selesai, sehingga melakukan joinselalu dianjurkan.
johntellsall
1
Jawaban diperluas yang mencakup mapfungsi ada di sini: stackoverflow.com/a/28463266/2327328
philshem
2
@ philshem Hati-hati dengan tautan yang Anda posting menggunakan kumpulan utas (bukan proses) sebagaimana disebutkan di sini stackoverflow.com/questions/26432411/… . Namun, jawaban ini menggunakan proses. Saya baru dalam hal ini, tetapi sepertinya (karena GIL) Anda hanya akan mendapatkan keuntungan kinerja dalam situasi tertentu saat menggunakan multithreading dengan Python. Namun, menggunakan kumpulan proses dapat memanfaatkan prosesor multicore dengan memiliki lebih dari 1 pekerjaan inti pada suatu proses.
user3731622
3
Ini adalah jawaban terbaik untuk benar-benar melakukan sesuatu yang bermanfaat dan mengambil keuntungan dari banyak core CPU
Frobot
92

Hanya sebuah catatan: Antrian tidak diperlukan untuk threading.

Ini adalah contoh paling sederhana yang dapat saya bayangkan yang menunjukkan 10 proses berjalan secara bersamaan.

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"
Douglas Adams
sumber
3
Tambahkan kutipan terakhir ke "Selesai untuk membuatnya mencetak" Selesai "
iChux
1
Saya suka contoh ini lebih baik daripada Martelli, lebih mudah untuk dimainkan. Namun, saya akan merekomendasikan printNumber melakukan yang berikut, untuk membuatnya sedikit lebih jelas apa yang terjadi: itu harus menyimpan randint ke variabel sebelum tidur di atasnya, dan kemudian cetak harus diubah untuk mengatakan "Thread" + str ( angka) + "tidur untuk" + theRandintVariabel + "detik"
Nickolai
Apakah ada cara untuk mengetahui kapan setiap utas selesai, saat selesai?
Matt
1
@ Mat Ada beberapa cara untuk melakukan hal seperti itu, tetapi itu akan tergantung pada kebutuhan Anda. Salah satu caranya adalah memperbarui singleton atau variabel lain yang dapat diakses publik yang sedang ditonton dalam loop sementara dan diperbarui di akhir utas.
Douglas Adams
2
Tidak perlu untuk forloop kedua , Anda dapat memanggil thread.start()loop pertama.
Mark Mishyn
49

Jawaban dari Alex Martelli membantu saya. Namun, ini adalah versi modifikasi yang saya pikir lebih bermanfaat (setidaknya bagi saya).

Diperbarui: berfungsi di Python 2 dan Python 3

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()
Jimyty
sumber
6
Mengapa tidak berhenti saja pada pengecualian?
Stavros Korokithakis
1
Anda bisa, hanya preferensi pribadi
JimJty
1
Saya belum menjalankan kodenya, tetapi tidakkah Anda perlu mengunemonisasi utasnya? Saya pikir bahwa setelah for-loop terakhir, program Anda mungkin keluar - setidaknya seharusnya karena itulah cara kerja thread. Saya pikir pendekatan yang lebih baik adalah tidak menempatkan data pekerja dalam antrian, tetapi memasukkan output ke dalam antrian karena Anda dapat memiliki mainloop yang tidak hanya menangani informasi yang masuk ke antrian dari pekerja, tetapi sekarang juga tidak threading, dan Anda tahu itu tidak akan keluar sebelum waktunya.
dylnmc
1
@dnmnmc, itu di luar kasus penggunaan saya (antrian input saya sudah ditentukan sebelumnya). Jika Anda ingin pergi dengan rute Anda, saya sarankan melihat seledri
JimJty
@ Jimjty apakah Anda tahu mengapa saya mendapatkan kesalahan ini: import Queue ModuleNotFoundError: No module named 'Queue'Saya menjalankan python 3.6.5 beberapa posting menyebutkan bahwa dalam python 3.6.5 itu adalah queuetetapi bahkan setelah saya mengubahnya, masih tidak berfungsi
user9371654
25

Diberi fungsi f,, utas seperti ini:

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

Untuk meneruskan argumen f

threading.Thread(target=f, args=(a,b,c)).start()
starfry
sumber
Ini sangat mudah. Bagaimana Anda memastikan bahwa utas menutup ketika Anda selesai dengan mereka?
cameronroytaylor
Sejauh yang saya mengerti, ketika fungsi keluar Threadobjek membersihkan. Lihat dokumen . Ada is_alive()metode yang dapat Anda gunakan untuk memeriksa utas jika perlu.
Starfry
Saya melihat is_alivemetodenya, tetapi saya tidak tahu bagaimana menerapkannya pada utas. Saya mencoba menugaskan thread1=threading.Thread(target=f).start()dan kemudian memeriksanya thread1.is_alive(), tetapi thread1diisi dengan None, jadi tidak berhasil di sana. Apakah Anda tahu jika ada cara lain untuk mengakses utas?
cameronroytaylor
4
Anda perlu menetapkan objek utas ke suatu variabel dan kemudian memulainya menggunakan varaible itu: thread1=threading.Thread(target=f)diikuti oleh thread1.start(). Maka Anda bisa melakukannya thread1.is_alive().
Starfry
1
Itu berhasil. Dan ya, pengujian dengan thread1.is_alive()pengembalian Falsesegera setelah fungsi keluar.
cameronroytaylor
25

Saya menemukan ini sangat berguna: buat sebanyak utas sebagai inti dan biarkan mereka menjalankan banyak tugas (dalam hal ini, memanggil program shell):

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
lumba-lumba
sumber
@shavenwarthog yakin seseorang dapat menyesuaikan variabel "cpus" tergantung kebutuhan seseorang. Lagi pula, panggilan subproses akan menelurkan subproses dan ini akan dialokasikan CPU oleh OS ("proses induk" python tidak berarti "CPU yang sama" untuk subproses).
Lumba
2
Anda benar, komentar saya tentang "utas dimulai pada CPU yang sama dengan proses induk" salah. Terima kasih balasannya!
johntellsall
1
mungkin perlu dicatat bahwa tidak seperti multithreading yang menggunakan ruang memori yang sama, multiprocessing tidak dapat berbagi variabel / data dengan mudah. +1 sekalipun.
fantabolous
22

Python 3 memiliki fasilitas meluncurkan tugas paralel . Ini membuat pekerjaan kami lebih mudah.

Ini memiliki penyatuan benang dan proses penyatuan .

Berikut ini memberikan wawasan:

Contoh ThreadPoolExecutor ( sumber )

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

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()
Jeril
sumber
18

Menggunakan modul concurrent.futures baru yang terik

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

Pendekatan pelaksana mungkin tampak akrab bagi semua orang yang pernah mengotori Jawa sebelumnya.

Juga sebagai catatan: Untuk menjaga alam semesta tetap waras, jangan lupa untuk menutup kumpulan / pelaksana Anda jika Anda tidak menggunakan withkonteks (yang sangat mengagumkan sehingga ia melakukannya untuk Anda)

Shubham Chaudhary
sumber
17

Bagi saya, contoh sempurna untuk threading adalah memantau peristiwa asinkron. Lihatlah kode ini.

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

Anda dapat bermain dengan kode ini dengan membuka sesi IPython dan melakukan sesuatu seperti:

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

Tunggu beberapa menit

>>> a[0] = 2
Mon = 2
dvreed77
sumber
1
AttributeError: objek 'Monitor' tidak memiliki atribut 'berhenti'?
pandita
5
Apakah Anda tidak membuang siklus CPU sambil menunggu acara Anda terjadi? Tidak selalu hal yang sangat praktis untuk dilakukan.
mogul
3
Seperti kata mogul, ini akan terus dieksekusi. Minimal Anda bisa menambahkan dalam tidur singkat, katakan tidur (0,1), yang mungkin akan secara signifikan mengurangi penggunaan cpu pada contoh sederhana seperti ini.
fantabolous
3
Ini adalah contoh yang mengerikan, membuang satu inti. Tambahkan tidur setidaknya tetapi solusi yang tepat adalah dengan menggunakan beberapa mekanisme pensinyalan.
PureW
16

Sebagian besar dokumentasi dan tutorial menggunakan Python Threadingdan Queuemodul, dan itu bisa membuat kewalahan bagi pemula.

Mungkin mempertimbangkan concurrent.futures.ThreadPoolExecutormodul Python 3.

Dikombinasikan dengan with klausa dan daftar pemahaman itu bisa menjadi pesona nyata.

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()
Yibo
sumber
15

Saya melihat banyak contoh di sini di mana tidak ada pekerjaan nyata yang dilakukan, dan kebanyakan terikat CPU. Berikut adalah contoh tugas yang terikat CPU yang menghitung semua bilangan prima antara 10 juta dan 10,05 juta. Saya telah menggunakan keempat metode di sini:

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

Berikut adalah hasil pada mesin empat-core Mac OS X saya

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
PirateApp
sumber
1
@TheUnfunCat tidak ada pelaksana proses yang jauh lebih baik daripada threading untuk tugas cpu terikat
PirateApp
1
Bung jawaban yang bagus. Saya dapat mengonfirmasi bahwa dalam Python 3.6 pada Windows (setidaknya) ThreadPoolExecutor tidak melakukan hal yang baik untuk tugas-tugas berat CPU. Itu tidak menggunakan inti untuk perhitungan. Sedangkan ProcessPoolExecutor menyalin data ke dalam SETIAP proses yang ditimbulkannya, itu mematikan untuk matriks besar.
Anatoly Alekseev
1
Contoh yang sangat berguna, tetapi saya tidak mengerti bagaimana cara kerjanya. Kita perlu if __name__ == '__main__':sebelum panggilan utama, jika tidak menumbuhkan pengukuran itu sendiri dan cetakan Sebuah usaha telah dilakukan untuk memulai proses baru sebelum ... .
Stein
1
@ Sein Saya percaya itu hanya masalah pada Windows.
AMC
12

Berikut adalah contoh impor CSV yang sangat sederhana menggunakan threading. (Penyertaan perpustakaan mungkin berbeda untuk tujuan yang berbeda.)

Fungsi Helper:

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

Fungsi Pengemudi:

import_handler(csv_file_name)
Chirag Vora
sumber
9

Saya ingin berkontribusi dengan contoh sederhana dan penjelasan yang saya temukan berguna ketika saya harus mengatasi masalah ini sendiri.

Dalam jawaban ini Anda akan menemukan beberapa informasi tentang Python GIL (kunci juru bahasa global) dan contoh sederhana sehari-hari yang ditulis menggunakan multiprocessing.dummy plus beberapa tolok ukur sederhana.

Global Interpreter Lock (GIL)

Python tidak mengizinkan multi-threading dalam arti kata yang sebenarnya. Ini memiliki paket multi-threading, tetapi jika Anda ingin multi-thread untuk mempercepat kode Anda, maka biasanya bukan ide yang baik untuk menggunakannya.

Python memiliki konstruk yang disebut global interpreter lock (GIL). GIL memastikan bahwa hanya satu dari 'utas' Anda yang dapat dieksekusi pada satu waktu. Sebuah thread mendapatkan GIL, melakukan sedikit pekerjaan, lalu meneruskan GIL ke thread berikutnya.

Hal ini terjadi sangat cepat sehingga bagi mata manusia, sepertinya utas Anda berjalan paralel, tetapi mereka benar-benar hanya bergantian menggunakan inti CPU yang sama.

Semua operan GIL ini menambah biaya eksekusi. Ini berarti bahwa jika Anda ingin membuat kode Anda berjalan lebih cepat maka menggunakan paket threading sering bukan ide yang baik.

Ada alasan untuk menggunakan paket threading Python. Jika Anda ingin menjalankan beberapa hal secara bersamaan, dan efisiensi bukan masalah, maka itu benar-benar baik dan nyaman. Atau jika Anda menjalankan kode yang perlu menunggu sesuatu (seperti beberapa I / O) maka itu bisa masuk akal. Tetapi pustaka threading tidak akan membiarkan Anda menggunakan core CPU tambahan.

Multi-threading dapat dialihdayakan ke sistem operasi (dengan melakukan multi-pemrosesan), dan beberapa aplikasi eksternal yang memanggil kode Python Anda (misalnya, Spark atau Hadoop ), atau beberapa kode yang panggilan kode Python Anda (misalnya: Anda bisa minta kode Python Anda memanggil fungsi C yang melakukan hal-hal multi-threaded yang mahal).

Mengapa Ini Penting?

Karena banyak orang menghabiskan banyak waktu untuk mencari kemacetan dalam kode multi-threaded Python mewah mereka sebelum mereka mengetahui apa itu GIL.

Setelah informasi ini jelas, inilah kode saya:

#!/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)
Pitto
sumber
7

Berikut adalah multi threading dengan contoh sederhana yang akan sangat membantu. Anda dapat menjalankannya dan memahami dengan mudah bagaimana multi threading bekerja di Python. Saya menggunakan kunci untuk mencegah akses ke utas lain sampai utas sebelumnya menyelesaikan pekerjaan mereka. Dengan menggunakan baris kode ini,

tLock = threading.BoundedSemaphore (nilai = 4)

Anda dapat membiarkan sejumlah proses sekaligus dan tetap berpegang pada sisa utas yang akan berjalan nanti atau setelah proses sebelumnya selesai.

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()
carma
sumber
5

Dengan meminjam dari posting ini kita tahu tentang memilih antara multithreading, multiprocessing, dan async / asynciodan penggunaannya.

Python 3 memiliki perpustakaan bawaan baru untuk konkurensi dan paralelisme: concurrent.futures

Jadi saya akan menunjukkan melalui percobaan untuk menjalankan empat tugas (yaitu .sleep()metode) dengan Threading-Poolcara:

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)

Keluaran:

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

[ CATATAN ]:

  • Seperti yang Anda lihat dalam hasil di atas, kasus terbaik adalah 3 pekerja untuk keempat tugas tersebut.
  • Jika Anda memiliki tugas proses alih-alih I / O terikat atau memblokir ( multiprocessingvs threading) Anda bisa mengubah ThreadPoolExecutorke ProcessPoolExecutor.
Benyamin Jafari
sumber
4

Tidak ada solusi sebelumnya yang benar-benar menggunakan banyak core pada server GNU / Linux saya (di mana saya tidak memiliki hak administrator). Mereka hanya berlari pada satu inti.

Saya menggunakan os.forkantarmuka tingkat bawah untuk menelurkan banyak proses. Ini adalah kode yang berfungsi untuk saya:

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
David Schumann
sumber
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()
Skiller Dz
sumber
1
@ sP_ Saya menduga karena Anda memiliki objek utas sehingga Anda dapat menunggu sampai selesai.
Aleksandar Makragić
1
t = threading.Thread (target = send ()) harus t = threading.Thread (target = send)
TRiNE
Saya tidak memilih jawaban ini karena tidak memberikan penjelasan tentang cara meningkatkan jawaban yang ada, selain mengandung ketidakakuratan yang serius.
Jules