Terjemahan Bab 13 Concurrency
dari buku 'Pemrograman Python Ahli',
Edisi kedua
Michał Jaworski & Tarek Ziadé, 2016
Pemrograman asinkron
Dalam beberapa tahun terakhir, pemrograman asinkron telah mendapatkan popularitas besar. Python 3.5 akhirnya mendapatkan beberapa fungsi sintaksis yang memperkuat konsep solusi asinkron. Tetapi ini tidak berarti bahwa pemrograman asinkron telah menjadi mungkin hanya sejak Python 3.5. Banyak pustaka dan kerangka kerja yang disediakan jauh lebih awal, dan sebagian besar berasal dari versi Python yang lebih lama 2. Bahkan ada implementasi alternatif seluruh Python yang disebut Stackless (lihat Bab 1, “Status Saat Ini dari Python”), yang berfokus pada pendekatan pemrograman tunggal ini. Untuk beberapa solusi, seperti
Twisted, Tornado, atau
Eventlet , komunitas aktif masih ada dan benar-benar layak untuk diketahui. Bagaimanapun, dimulai dengan Python 3.5, pemrograman asinkron telah menjadi lebih mudah daripada sebelumnya. Dengan demikian, diharapkan bahwa fungsi asinkron bawaannya akan menggantikan sebagian besar alat lama, atau proyek eksternal akan secara bertahap berubah menjadi semacam kerangka kerja tingkat tinggi berdasarkan pada Python bawaan.
Ketika mencoba menjelaskan apa pemrograman asinkron, paling mudah untuk menganggap pendekatan ini sebagai sesuatu yang mirip dengan utas, tetapi tanpa penjadwal sistem. Ini berarti bahwa program asinkron dapat memproses tugas secara bersamaan, tetapi konteksnya diaktifkan secara internal dan bukan oleh penjadwal sistem.
Tapi, tentu saja, kami tidak menggunakan utas untuk pemrosesan paralel tugas dalam program asinkron. Sebagian besar solusi menggunakan konsep yang berbeda dan, tergantung pada implementasinya, dipanggil secara berbeda. Beberapa contoh nama yang digunakan untuk menggambarkan objek program paralel tersebut adalah:
- Green threads - Green threads (proyek greenlet, gevent atau eventlet)
- Coroutines - coroutines (pemrograman asynchronous murni dalam Python 3.5)
- Tasklets (Stackless Python) Ini pada dasarnya adalah konsep yang sama, tetapi sering diimplementasikan dalam cara yang sedikit berbeda.
Untuk alasan yang jelas, di bagian ini kita akan fokus hanya pada coroutine yang awalnya didukung oleh Python, dimulai dengan versi 3.5.
Multitasking kolaboratif dan I / O yang tidak sinkron
Multitasking kolaboratif adalah inti dari pemrograman asinkron. Dalam hal ini, multitasking dalam sistem operasi tidak diperlukan untuk memulai pengalihan konteks (ke proses atau utas lainnya), tetapi sebaliknya, setiap proses secara sukarela melepaskan kontrol ketika berada dalam mode siaga untuk memastikan eksekusi beberapa program secara simultan. Itulah mengapa disebut kolaboratif. Semua proses harus bekerja bersama untuk memastikan multitasking berhasil.
Model multitasking kadang-kadang digunakan dalam sistem operasi, tetapi sekarang hampir tidak dapat ditemukan sebagai solusi tingkat sistem. Ini karena ada risiko bahwa satu layanan yang dirancang dengan buruk dapat dengan mudah mengganggu stabilitas seluruh sistem. Menjadwalkan utas dan proses menggunakan sakelar konteks yang dikendalikan langsung oleh sistem operasi saat ini merupakan pendekatan dominan untuk konkurensi di tingkat sistem. Tetapi multitasking kolaboratif masih merupakan alat konkurensi yang hebat di level aplikasi.
Berbicara tentang multitasking bersama di tingkat aplikasi, kami tidak berurusan dengan utas atau proses yang perlu melepaskan kontrol, karena semua eksekusi terkandung dalam satu proses dan utas. Sebagai gantinya, kami memiliki beberapa tugas (coroutine, tasklets, dan thread hijau) yang mentransfer kontrol ke fungsi tunggal yang mengontrol koordinasi tugas. Fungsi ini biasanya semacam loop acara.
Untuk menghindari kebingungan (karena terminologi Python), sekarang kita akan memanggil tugas paralel seperti coroutine. Masalah paling penting dalam multitasking kolaboratif adalah kapan harus mentransfer kontrol. Pada sebagian besar aplikasi asinkron, kontrol dilewatkan ke penjadwal atau loop peristiwa selama operasi I / O. Terlepas dari apakah program membaca data dari sistem file atau berkomunikasi melalui soket, operasi I / O seperti itu selalu dikaitkan dengan beberapa waktu tunggu ketika proses menjadi tidak aktif. Latensi tergantung pada sumber daya eksternal, jadi ini adalah kesempatan yang baik untuk membebaskan kontrol sehingga coroutine lain dapat melakukan pekerjaan mereka sampai mereka juga harus menunggu pendekatan ini agak mirip dalam perilaku dengan bagaimana multithreading diimplementasikan dalam Python. Kita tahu bahwa GIL membuat serial utas-utas Python, tetapi juga dibebaskan dengan setiap operasi I / O. Perbedaan utama adalah bahwa utas dalam Python diimplementasikan sebagai utas tingkat sistem, sehingga sistem operasi dapat membongkar utas yang saat ini berjalan kapan saja dan mentransfer kendali ke yang lain.
Dalam pemrograman asinkron, tugas tidak pernah terganggu oleh loop acara utama. Itu sebabnya gaya multitasking ini juga disebut multitasking non-prioritas.
Tentu saja, setiap aplikasi Python berjalan pada sistem operasi di mana ada proses lain yang bersaing untuk sumber daya. Ini berarti bahwa sistem operasi selalu memiliki hak untuk membongkar seluruh proses dan mentransfer kontrol ke yang lain. Tetapi ketika aplikasi asinkron kami mulai kembali, itu berlanjut dari tempat yang sama di mana ia ditangguhkan ketika penjadwal sistem mengintervensi. Itulah sebabnya coroutine dalam konteks ini dianggap tidak berdesakan.
Python async dan tunggu kata kunci
Kata kunci
async dan
menunggu adalah blok bangunan utama dalam pemrograman Python asinkron.
Kata
kunci async yang digunakan sebelum pernyataan
def mendefinisikan coroutine baru. Fungsi coroutine dapat ditangguhkan dan dilanjutkan dalam kondisi yang ditentukan secara ketat. Sintaks dan perilakunya sangat mirip dengan generator (lihat Bab 2, “Rekomendasi Sintaks,” di bawah level kelas). Bahkan, generator harus digunakan dalam versi Python yang lebih lama untuk mengimplementasikan coroutine. Berikut adalah contoh pernyataan fungsi yang menggunakan
kata kunci async :
async def async_hello(): print("hello, world!")
Fungsi yang didefinisikan menggunakan
kata kunci async adalah spesial. Saat dipanggil, mereka tidak mengeksekusi kode di dalam, tetapi mengembalikan objek coroutine:
>>>> async def async_hello(): ... print("hello, world!") ... >>> async_hello() <coroutine object async_hello at 0x1014129e8>
Objek coroutine tidak melakukan apa-apa sampai eksekusinya dijadwalkan di loop acara. Modul asyncio tersedia untuk menyediakan implementasi dasar dari loop acara, serta banyak utilitas asinkron lainnya:
>>> import asyncio >>> async def async_hello(): ... print("hello, world!") ... >>> loop = asyncio.get_event_loop() >>> loop.run_until_complete(async_hello()) hello, world! >>> loop.close()
Secara alami, hanya membuat satu coroutine sederhana, dalam program kami, kami tidak menerapkan paralelisme. Untuk melihat sesuatu yang benar-benar paralel, kita perlu membuat lebih banyak tugas yang akan dilakukan oleh loop peristiwa.
Tugas baru dapat ditambahkan ke loop dengan memanggil metode
loop.create_task () atau dengan menyediakan objek lain untuk menunggu penggunaan fungsi
asyncio.wait () . Kami akan menggunakan pendekatan yang terakhir dan mencoba untuk mencetak secara asinkron urutan angka yang dihasilkan menggunakan fungsi
range () :
import asyncio async def print_number(number): print(number) if __name__ == "__main__": loop = asyncio.get_event_loop() loop.run_until_complete( asyncio.wait([ print_number(number) for number in range(10) ]) ) loop.close()
Fungsi
asyncio.wait () menerima daftar objek coroutine dan segera kembali. Hasilnya adalah generator yang menghasilkan objek yang mewakili hasil masa depan (futures). Seperti namanya, itu digunakan untuk menunggu semua coroutine yang disediakan untuk menyelesaikan. Alasan mengembalikan generator alih-alih objek coroutine adalah karena itu kompatibel dengan versi Python sebelumnya, yang akan dijelaskan nanti. Hasil menjalankan skrip ini mungkin sebagai berikut:
$ python asyncprint.py 0 7 8 3 9 4 1 5 2 6
Seperti yang bisa kita lihat, angka tidak dicetak sesuai urutan kita membuat coroutine kita. Tapi inilah yang ingin kami capai.
Kata kunci penting kedua yang ditambahkan dalam Python 3.5 sedang
menunggu . Ini digunakan untuk menunggu hasil dari coroutine atau acara mendatang (dijelaskan nanti) dan melepaskan kontrol atas eksekusi di loop acara. Untuk lebih memahami bagaimana ini bekerja, kita perlu mempertimbangkan contoh kode yang lebih kompleks.
Misalkan kita ingin membuat dua coroutine yang akan melakukan beberapa tugas sederhana dalam satu lingkaran:
- Tunggu beberapa detik secara acak
- Cetak beberapa teks yang disediakan sebagai argumen, dan jumlah waktu yang dihabiskan untuk menunggu. Mari kita mulai dengan implementasi sederhana yang memiliki beberapa masalah konkurensi yang akan kami coba tingkatkan nanti dengan tambahan penggunaan menunggu:
import time import random import asyncio async def waiter(name): for _ in range(4): time_to_sleep = random.randint(1, 3) / 4 time.sleep(time_to_sleep) print( "{} waited {} seconds" "".format(name, time_to_sleep) ) async def main(): await asyncio.wait([waiter("foo"), waiter("bar")]) if __name__ == "__main__": loop = asyncio.get_event_loop() loop.run_until_complete(main()) loop.close()
Ketika dieksekusi di terminal (menggunakan perintah waktu untuk mengukur waktu), Anda dapat melihat:
$ time python corowait.py bar waited 0.25 seconds bar waited 0.25 seconds bar waited 0.5 seconds bar waited 0.5 seconds foo waited 0.75 seconds foo waited 0.75 seconds foo waited 0.25 seconds foo waited 0.25 seconds real 0m3.734s user 0m0.153s sys 0m0.028s
Seperti yang bisa kita lihat, kedua coroutine menyelesaikan eksekusi mereka, tetapi tidak secara sinkron. Alasannya adalah bahwa mereka berdua menggunakan fungsi
time.sleep () , yang mengunci tetapi tidak melepaskan kontrol di loop acara. Ini akan berfungsi lebih baik di pemasangan multi-utas, tetapi kami tidak ingin menggunakan streaming sekarang. Jadi bagaimana kita memperbaikinya?
Jawabannya adalah menggunakan
asyncio.sleep () , yang merupakan versi time.sleep () yang asinkron, dan mengharapkan hasilnya menggunakan kata kunci
tunggu . Kami sudah menggunakan pernyataan ini di versi
utama () , tetapi ini hanya untuk meningkatkan kejelasan kode. Ini jelas tidak membuat implementasi kami lebih paralel. Mari kita lihat versi perbaikan dari waitout () coroutine yang menggunakan wait asyncio.sleep ():
async def waiter(name): for _ in range(4): time_to_sleep = random.randint(1, 3) / 4 await asyncio.sleep(time_to_sleep) print( "{} waited {} seconds" "".format(name, time_to_sleep) )
Menjalankan skrip yang diperbarui, kita akan melihat bagaimana output dari dua fungsi saling bergantian:
$ time python corowait_improved.py bar waited 0.25 seconds foo waited 0.25 seconds bar waited 0.25 seconds foo waited 0.5 seconds foo waited 0.25 seconds bar waited 0.75 seconds foo waited 0.25 seconds bar waited 0.5 seconds real 0m1.953s user 0m0.149s sys 0m0.026s
Manfaat tambahan dari peningkatan sederhana ini adalah bahwa kode berjalan lebih cepat. Total waktu eksekusi kurang dari jumlah semua waktu tidur, karena coroutine mengambil kendali satu per satu.
Asyncio di versi Python sebelumnya
Modul asyncio muncul dalam Python 3.4. Jadi ini adalah satu-satunya versi Python yang memiliki dukungan serius untuk pemrograman asinkron sebelum Python 3.5. Sayangnya, tampaknya kedua versi berikut ini sudah cukup untuk menyajikan masalah kompatibilitas.
Bagaimanapun, inti pemrograman asinkron dalam Python diperkenalkan lebih awal dari elemen sintaks yang mendukung templat ini. Lebih baik terlambat daripada tidak sama sekali, tetapi ini menciptakan situasi di mana ada dua sintaks untuk bekerja dengan coroutine.
Dimulai dengan Python 3.5, Anda dapat menggunakan
async dan
menunggu :
async def main (): await asyncio.sleep(0)
Namun, dalam Python 3.4, Anda harus menerapkan dekorator asyncio.coroutine dan menghasilkan teks coroutine sebagai tambahan:
@asyncio.couroutine def main(): yield from asyncio.sleep(0)
Fakta lain yang bermanfaat adalah bahwa
hasil dari pernyataan diperkenalkan dalam Python 3.3, dan PyPI memiliki backport asinkron. Ini berarti bahwa Anda juga dapat menggunakan implementasi multitasking kolaboratif ini dengan Python 3.3.
Contoh praktis pemrograman asinkron
Seperti yang disebutkan berulang kali dalam bab ini, pemrograman asinkron adalah alat yang hebat untuk menangani I / O. Sudah waktunya untuk membuat sesuatu yang lebih praktis dari sekedar mencetak urutan atau menunggu tanpa sinkronisasi.
Untuk memastikan konsistensi, kami akan mencoba memecahkan masalah yang sama yang kami pecahkan dengan bantuan multithreading dan multiprocessing. Oleh karena itu, kami akan mencoba mengekstrak beberapa data dari sumber daya eksternal secara tidak sinkron melalui koneksi jaringan. Akan lebih bagus jika kita bisa menggunakan paket
python-gmaps yang sama seperti pada bagian sebelumnya. Sayangnya, kami tidak bisa.
Pencipta
python-gmaps agak malas dan hanya mengambil nama. Untuk menyederhanakan pengembangan, ia memilih paket permintaan sebagai pustaka klien HTTP-nya. Sayangnya, permintaan tidak mendukung I / O
asinkron dengan
async dan
menunggu . Ada beberapa proyek lain yang bertujuan untuk memberikan beberapa paralelisme untuk proyek permintaan, tetapi mereka juga mengandalkan
Gevent (
grequests , lihat
https://github.com/ kennethreitz / grequests ) atau menjalankan thread / proses pool (query-futures) lihat
github.com/ross/requests-futures ). Tak satu pun dari mereka memecahkan masalah kita.
Sebelum mencela diri sendiri karena telah memarahi pengembang open source yang tidak bersalah, tenanglah. Orang di belakang paket python-gmaps adalah saya. Pilihan dependensi yang buruk adalah salah satu masalah proyek ini. Saya hanya suka mengkritik diri sendiri di depan umum dari waktu ke waktu. Ini akan menjadi pelajaran pahit bagi saya, karena python-gmaps dalam versi terbarunya (0.3.1 pada saat menulis buku ini) tidak dapat dengan mudah diintegrasikan dengan Python I / O yang tidak sinkron. Bagaimanapun, ini mungkin berubah di masa depan, jadi tidak ada yang hilang.
Mengetahui keterbatasan perpustakaan, yang sangat mudah digunakan dalam contoh sebelumnya, kita perlu membuat sesuatu yang mengisi celah ini. Google MapsAPI sangat mudah digunakan, jadi kami akan menyiapkan utilitas asinkron hanya untuk ilustrasi. Pustaka standar Python 3.5 masih kekurangan pustaka yang dapat mengeksekusi permintaan HTTP asinkron semudah memanggil
urllib.urlopen () . Kami jelas tidak ingin membuat dukungan protokol penuh dari awal, jadi kami akan menggunakan sedikit bantuan dari paket
aiohttp yang tersedia di PyPI. Ini adalah perpustakaan yang sangat menjanjikan yang menambahkan implementasi klien dan server untuk HTTP asinkron. Berikut adalah modul kecil yang dibangun di atas
aiohttp yang membuat satu fungsi pembantu
geocode () yang mengeksekusi permintaan geocoding ke layanan Google Maps API:
import aiohttp session = aiohttp.ClientSession() async def geocode(place): params = { 'sensor': 'false', 'address': place } async with session.get( 'https://maps.googleapis.com/maps/api/geocode/json', params=params ) as response: result = await response.json() return result['results']
Mari kita asumsikan bahwa kode ini disimpan dalam modul bernama
asyncgmaps , yang akan kita gunakan nanti. Sekarang kita siap untuk menulis ulang contoh yang digunakan dalam diskusi multithreading dan multiprocessing. Sebelumnya, kami biasa memisahkan seluruh operasi menjadi dua tahap terpisah:
- Memenuhi semua permintaan ke layanan eksternal secara paralel menggunakan fungsi fetch_place () .
- Tampilkan semua hasil dalam satu lingkaran menggunakan fungsi present_result () .
Tetapi karena multitasking kolaboratif sama sekali berbeda dari menggunakan beberapa proses atau utas, kami dapat sedikit mengubah pendekatan kami. Sebagian besar masalah yang diangkat dalam Menggunakan satu utas per item tidak lagi menjadi perhatian kami.
Coroutine bukan preemptive, jadi kami dapat dengan mudah menampilkan hasilnya segera setelah menerima tanggapan HTTP. Ini akan menyederhanakan kode kami dan membuatnya lebih dimengerti:
import asyncio
Pemrograman asinkron sangat bagus untuk pengembang backend yang tertarik dalam membangun aplikasi yang dapat diskalakan. Dalam praktiknya, ini adalah salah satu alat paling penting untuk membuat server yang sangat kompetitif.
Tetapi kenyataannya menyedihkan. Banyak paket populer yang menangani masalah I / O tidak dimaksudkan untuk digunakan dengan kode asinkron. Alasan utama untuk ini adalah:
- Implementasi Python 3 masih rendah dan beberapa fitur canggihnya
- Pemahaman yang rendah tentang berbagai konsep konkurensi di kalangan pemula untuk belajar Python
Ini berarti bahwa sangat sering migrasi aplikasi dan paket multi-threaded sinkron yang ada tidak mungkin (karena batasan arsitektur) atau terlalu mahal. Banyak proyek dapat sangat diuntungkan dari penerapan gaya multitasking asinkron, tetapi hanya sedikit yang pada akhirnya akan melakukannya. Ini berarti bahwa saat ini Anda akan mengalami banyak kesulitan dalam mencoba membuat aplikasi asinkron sejak awal. Dalam kebanyakan kasus, ini akan mirip dengan masalah yang disebutkan di bagian "Contoh Praktis Pemrograman Asinkron" - antarmuka yang tidak kompatibel dan pemblokiran non-sinkron operasi I / O. Tentu saja, kadang-kadang Anda bisa menyerah menunggu ketika Anda mengalami ketidakcocokan tersebut dan hanya mendapatkan sumber daya yang diperlukan secara serempak. Tapi ini akan memblokir masing-masing coroutine lain dari mengeksekusi kodenya sementara Anda menunggu hasilnya. Secara teknis, ini berfungsi, tetapi juga menghancurkan semua manfaat pemrograman asinkron. Jadi, pada akhirnya, menggabungkan I / O asinkron dengan I / O sinkron bukanlah suatu pilihan. Ini semua atau tidak sama sekali game.
Masalah lain adalah operasi yang terikat prosesor yang panjang. Ketika Anda melakukan operasi I / O, tidak ada masalah melepaskan kontrol dari coroutine. Saat menulis / membaca dari sistem file atau soket, Anda akhirnya akan menunggu, jadi panggilan menggunakan menunggu adalah yang terbaik yang dapat Anda lakukan. Tetapi bagaimana jika Anda perlu menghitung sesuatu, dan Anda tahu itu akan memakan waktu? Tentu saja, Anda dapat membagi masalah menjadi beberapa bagian dan membatalkan kontrol setiap kali Anda sedikit memajukan pekerjaan. Tetapi segera Anda akan menemukan bahwa ini bukan model yang sangat baik. Hal seperti itu dapat membuat kode berantakan, dan juga tidak menjamin hasil yang baik.
Pengikatan temporal harus menjadi tanggung jawab penerjemah atau sistem operasi.
Menggabungkan Kode Asynchronous dengan Asynchronous Futures
Jadi apa yang harus dilakukan jika Anda memiliki kode yang melakukan I / O sinkron panjang sehingga Anda tidak dapat atau tidak ingin menulis ulang. Atau apa yang harus dilakukan ketika Anda harus melakukan beberapa operasi prosesor berat dalam aplikasi yang dirancang terutama untuk I / O asinkron? Nah ... Anda perlu mencari solusinya. Dan maksud saya multithreading atau multiprocessing.
Ini mungkin kedengarannya tidak terlalu bagus, tetapi kadang-kadang solusi terbaik mungkin adalah apa yang kami coba hindari. Pemrosesan paralel tugas intensif sumber daya dengan Python selalu dilakukan dengan lebih baik karena multi-pemrosesan. Dan multithreading dapat menangani operasi I / O dengan sama baiknya (dengan cepat dan tanpa banyak sumber daya), tidak sinkron dan menunggu jika dikonfigurasi dengan benar dan ditangani dengan hati-hati.
Jadi kadang-kadang, ketika Anda tidak tahu apa yang harus dilakukan ketika sesuatu tidak sesuai dengan aplikasi asinkron Anda, gunakan sepotong kode yang meletakkannya di utas atau proses terpisah. Anda bisa berpura-pura itu coroutine, melepaskan kontrol untuk loop acara, dan akhirnya memproses hasilnya ketika sudah siap.
Untungnya bagi kami, pustaka standar Python menyediakan modul
concurrent.futures , yang juga terintegrasi dengan modul
asyncio . Bersama-sama, kedua modul ini memungkinkan Anda untuk merencanakan fungsi pemblokiran yang dieksekusi di utas atau proses tambahan, seolah-olah mereka adalah coroutine non-blocking asinkron.
Pelaksana dan masa depan
Sebelum kita melihat bagaimana menanamkan utas atau proses dalam loop peristiwa asinkron, kita melihat lebih dekat pada modul
concurrent.futures , yang nantinya akan menjadi komponen utama dari apa yang disebut solusi kami.
Kelas yang paling penting dalam modul
concurrent.futures adalah
Pelaksana dan
Masa Depan .
Pelaksana adalah kumpulan sumber daya yang dapat memproses item kerja secara paralel. Ini mungkin terlihat sangat mirip dengan tujuan dari kelas-kelas dari modul multiprosesor -
Pool dan
dummy.Pool - tetapi memiliki antarmuka dan semantik yang sama sekali berbeda. Ini adalah kelas dasar yang tidak dimaksudkan untuk implementasi dan memiliki dua implementasi spesifik:
- ThreadPoolExecutor : yang mewakili kumpulan utas
- ProcessPoolExecutor : yang mewakili kumpulan proses
Setiap
pelaksana menyajikan tiga metode:
- submit (fn, * args, ** kwargs) : menjadwalkan fungsi fn untuk dieksekusi di pool sumber daya dan mengembalikan objek Future yang mewakili eksekusi objek yang dipanggil
- map (func, * iterables, timeout = Tidak ada, chunksize = 1) : fungsi func dieksekusi pada iterasi yang mirip dengan multiprocessing. Metode Pool.map ()
- shutdown (wait = True) : ini mematikan Executor dan membebaskan semua sumber dayanya.
Metode yang paling menarik adalah
submit () karena objek Future yang dikembalikannya. Ini mewakili eksekusi asynchronous dari yang dipanggil dan hanya secara tidak langsung mewakili hasilnya. Untuk mendapatkan nilai balik aktual dari objek yang dipanggil yang dikirim, Anda harus memanggil metode
Future.result () . Dan jika objek yang dipanggil sudah selesai, metode
result () tidak akan memblokirnya dan hanya akan mengembalikan output dari fungsi. Jika tidak, ia akan memblokirnya sampai hasilnya siap. Anggap saja sebagai janji hasil (itu sebenarnya konsep yang sama dengan janji dalam JavaScript). Anda tidak perlu membongkar segera setelah menerimanya (menggunakan metode
result () ), tetapi jika Anda mencoba melakukan ini, dijamin pada akhirnya akan mengembalikan sesuatu:
>>> def loudy_return(): ... print("processing") ... return 42 ... >>> from concurrent.futures import ThreadPoolExecutor >>> with ThreadPoolExecutor(1) as executor: ... future = executor.submit(loudy_return) ... processing >>> future <Future at 0x33cbf98 state=finished returned int> >>> future.result() 42
Jika Anda ingin menggunakan metode
Executor.map () , itu tidak berbeda dalam penggunaan dari metode
Pool.map () dari kelas
Pool dari modul multiprosesor:
def main(): with ThreadPoolExecutor(POOL_SIZE) as pool: results = pool.map(fetch_place, PLACES) for result in results: present_result(result)
Menggunakan Pelaksana dalam Lingkaran Peristiwa
Contoh-contoh dari kelas Future yang dikembalikan oleh metode
Executor.submit () secara konseptual sangat dekat dengan coroutine yang digunakan dalam pemrograman asinkron. Karena itulah kita dapat menggunakan artis untuk membuat hibrid antara multitasking kolaboratif dan multiprosesing atau multithreading.
Inti dari pemecahan masalah ini adalah metode
BaseEventLoop.run_in_executor (executor, func, * args) dari kelas loop
acara . Ini memungkinkan Anda untuk merencanakan eksekusi fungsi func dalam proses atau kumpulan utas yang diwakili oleh argumen pelaksana. Yang paling penting tentang metode ini adalah ia mengembalikan objek baru yang diharapkan (objek yang dapat diharapkan menggunakan operator menunggu). Dengan demikian, berkat ini, Anda dapat melakukan fungsi pemblokiran yang bukan coroutine persis seperti coroutine, dan itu tidak akan memblokir, tidak peduli berapa lama untuk menyelesaikannya. Ini akan berhenti hanya fungsi yang mengharapkan hasil dari panggilan seperti itu, tetapi seluruh siklus acara akan berlanjut.
Dan fakta yang berguna adalah bahwa Anda bahkan tidak perlu membuat instance dari eksekutor Anda sendiri. Jika Anda melewatkan
Tidak Ada sebagai argumen kepada
pelaksana , kelas
ThreadPoolExecutor akan digunakan dengan jumlah utas default (untuk Python 3.5, ini adalah jumlah prosesor yang dikalikan 5).
Jadi, mari kita asumsikan bahwa kita tidak ingin menulis ulang bagian bermasalah dari paket python-gmaps yang menyebabkan sakit kepala kita. Kita dapat dengan mudah menunda panggilan pemblokiran ke utas terpisah dengan memanggil
loop.run_in_executor () , sambil meninggalkan fungsi fetch_place () sebagai coroutine yang diharapkan:
async def fetch_place(place): coro = loop.run_in_executor(None, api.geocode, place) result = await coro return result[0]
Solusi semacam itu lebih buruk daripada memiliki perpustakaan yang sepenuhnya tidak sinkron untuk melakukan pekerjaan itu, tetapi Anda tahu bahwa setidaknya ada sesuatu yang lebih baik daripada tidak sama sekali.Setelah menjelaskan apa sebenarnya konkurensi, kami mengambil tindakan dan menganalisis salah satu masalah paralel yang khas dengan menggunakan multithreading. Setelah mengidentifikasi kekurangan utama dari kode kami dan memperbaikinya, kami beralih ke multi-pemrosesan untuk melihat bagaimana cara kerjanya dalam kasus kami.Setelah itu, kami menemukan bahwa dengan modul multiprosesor, menggunakan beberapa proses jauh lebih mudah daripada utas dasar dengan multithreading. Tetapi hanya setelah itu kami menyadari bahwa kami dapat menggunakan API yang sama dengan utas, berkat multiprocessing.dummy. Dengan demikian, pilihan antara multiprosesing dan multithreading sekarang hanya bergantung pada solusi mana yang lebih cocok dengan masalah, dan bukan solusi mana yang memiliki antarmuka terbaik.Berbicara tentang menyesuaikan masalah, kami akhirnya mencoba pemrograman asinkron, yang seharusnya menjadi solusi terbaik untuk aplikasi terkait I / O, hanya untuk memahami bahwa kami tidak dapat sepenuhnya melupakan thread dan proses. Jadi kami membuat lingkaran, kembali ke tempat kami mulai!Dan ini membawa kita pada kesimpulan akhir bab ini. Tidak ada solusi yang cocok untuk semua orang. Ada beberapa pendekatan yang Anda sukai atau sukai. Ada beberapa pendekatan yang lebih cocok untuk serangkaian masalah ini, tetapi Anda perlu tahu semuanya untuk menjadi sukses. Dalam skenario realistis, Anda dapat menggunakan seluruh gudang alat dan gaya paralelisme dalam satu aplikasi, dan ini tidak jarang.Kesimpulan sebelumnya adalah pengantar yang sangat baik untuk topik bab berikutnya, Bab 14 "Pola Desain Berguna". Karena tidak ada templat tunggal yang akan menyelesaikan semua masalah Anda. Anda harus tahu sebanyak mungkin, karena pada akhirnya Anda akan menggunakannya setiap hari.