Dengan munculnya Python 3, ada sedikit desas-desus tentang "asinkronisme" dan "konkurensi", kita dapat mengasumsikan bahwa Python baru-baru ini memperkenalkan fitur / konsep ini. Tapi ini tidak benar. Kami telah menggunakan operasi ini berkali-kali. Selain itu, pemula mungkin berpikir bahwa asyncio adalah satu-satunya atau cara terbaik untuk membuat ulang dan menggunakan operasi asinkron / paralel. Dalam artikel ini, kita akan melihat berbagai cara untuk mencapai paralelisme, kelebihan dan kekurangannya.
Definisi istilah:
Sebelum kita mempelajari aspek teknis, penting untuk memiliki pemahaman dasar tentang istilah yang sering digunakan dalam konteks ini.
Sinkron dan asinkron:Dalam operasi
sinkron , tugas dilakukan satu demi satu. Dalam tugas-tugas tidak
sinkron dapat dimulai dan diselesaikan secara independen satu sama lain. Satu tugas tidak sinkron dapat mulai dan terus berjalan sementara eksekusi berpindah ke tugas baru. Tugas asinkron
tidak menghalangi (jangan memaksa untuk menunggu tugas selesai) operasi dan biasanya dilakukan di latar belakang.
Misalnya, Anda harus menghubungi agen perjalanan untuk merencanakan liburan Anda berikutnya. Anda perlu mengirim surat kepada penyelia Anda sebelum terbang. Dalam mode sinkron, Anda pertama kali memanggil agen perjalanan, dan jika Anda diminta untuk menunggu, Anda akan menunggu sampai mereka menjawab Anda. Kemudian Anda akan mulai menulis surat kepada pemimpin. Dengan demikian, Anda menyelesaikan tugas satu demi satu.
[eksekusi sinkron, kira-kira. penerjemah] Tetapi, jika Anda cerdas, mereka telah meminta Anda untuk menunggu
[tunggu telepon, kira-kira. penerjemah] Anda akan mulai menulis email dan ketika Anda berbicara lagi Anda akan berhenti menulis, berbicara, dan kemudian menambahkan surat itu. Anda juga dapat meminta teman untuk menghubungi agensi dan menulis sendiri surat. Ini asinkron, tugas tidak saling menghalangi.
Daya saing dan konkurensi:Daya saing menyiratkan bahwa dua tugas dilakukan
bersama . Dalam contoh kami sebelumnya, ketika kami mempertimbangkan contoh asinkron, kami secara bertahap berkembang dalam menulis surat, kemudian dalam percakapan dengan tur. agensi. Ini adalah
daya saing .
Ketika kami meminta untuk menelepon seorang teman, dan menulis surat sendiri, tugas dilakukan
secara paralel .
Concurrency pada dasarnya adalah bentuk kompetisi. Tetapi konkurensi bergantung pada perangkat keras. Misalnya, jika CPU hanya memiliki satu inti, maka dua tugas tidak dapat dijalankan secara paralel. Mereka hanya berbagi waktu prosesor di antara mereka sendiri. Maka ini adalah kompetisi, tetapi tidak konkurensi. Tetapi ketika kita memiliki beberapa inti
[sebagai teman dalam contoh sebelumnya, yang merupakan inti kedua, kira-kira. translator] kita dapat melakukan beberapa operasi (tergantung pada jumlah core) secara bersamaan.
Untuk meringkas:
- Sinkronisasi: memblokir operasi (pemblokiran)
- Asynchrony: tidak memblokir operasi (non-blocking)
- Daya saing: kemajuan bersama (joint)
- Konkurensi: kemajuan paralel (paralel)
Concurrency menyiratkan persaingan. Tetapi persaingan tidak selalu menyiratkan konkurensi.
Thread dan proses
Python telah mendukung utas untuk waktu yang sangat lama. Utas memungkinkan Anda untuk melakukan operasi secara kompetitif. Tetapi ada masalah dengan
Global Interpreter Lock (GIL) karena utas yang tidak dapat memberikan konkurensi sejati. Namun, dengan munculnya
multiprocessing, Anda dapat menggunakan beberapa core menggunakan Python.
UtasPertimbangkan sebuah contoh kecil. Dalam kode berikut, fungsi
pekerja akan dijalankan pada banyak utas secara serempak dan serempak.
import threading import time import random def worker(number): sleep = random.randrange(1, 10) time.sleep(sleep) print("I am Worker {}, I slept for {} seconds".format(number, sleep)) for i in range(5): t = threading.Thread(target=worker, args=(i,)) t.start() print("All Threads are queued, let's see when they finish!")
Dan berikut adalah contoh output:
$ python thread_test.py All Threads are queued, let's see when they finish! I am Worker 1, I slept for 1 seconds I am Worker 3, I slept for 4 seconds I am Worker 4, I slept for 5 seconds I am Worker 2, I slept for 7 seconds I am Worker 0, I slept for 9 seconds
Jadi, kami mulai 5 utas untuk kolaborasi dan setelah mulai (yaitu, setelah fungsi pekerja diluncurkan), operasi
tidak menunggu utas selesai sebelum beralih ke pernyataan cetak berikutnya. Ini adalah operasi yang tidak sinkron.
Dalam contoh kami, kami meneruskan fungsi ke konstruktor Thread. Jika kita mau, kita bisa mengimplementasikan subkelas dengan metode (gaya OOP).
Bacaan lebih lanjut:Untuk mempelajari lebih lanjut tentang stream, gunakan tautan di bawah ini:
Global Interpreter Lock (GIL)GIL diperkenalkan untuk mempermudah penanganan memori CPython dan memberikan integrasi terbaik dengan C (mis. Dengan ekstensi). GIL adalah mekanisme penguncian ketika interpreter Python hanya menjalankan satu utas pada satu waktu. Yaitu hanya satu utas yang dapat dieksekusi dalam bytecode Python sekaligus. GIL memastikan bahwa banyak utas tidak dijalankan
secara paralel .
Rincian Cepat GIL:
- Satu utas dapat berjalan sekaligus.
- Interpreter Python beralih di antara utas untuk mencapai daya saing.
- GIL berlaku untuk CPython (implementasi standar). Tetapi seperti, misalnya, Jython dan IronPython tidak memiliki GIL.
- GIL membuat program single-threaded cepat.
- GIL biasanya tidak mengganggu I / O.
- GIL memudahkan untuk mengintegrasikan pustaka thread-safe ke dalam C, berkat GIL kami memiliki banyak ekstensi / modul berkinerja tinggi yang ditulis dalam C.
- Untuk tugas-tugas yang bergantung pada CPU, juru bahasa memeriksa setiap kutu N dan mengganti untaian. Karenanya, satu utas tidak menghalangi yang lainnya.
Banyak yang melihat GIL sebagai kelemahan. Saya menganggap ini sebagai berkah, karena perpustakaan seperti NumPy, SciPy diciptakan, yang menempati posisi khusus dan unik dalam komunitas ilmiah.
Bacaan lebih lanjut:Sumber daya ini akan memungkinkan Anda mempelajari GIL:
ProsesUntuk mencapai konkurensi dalam Python, modul
multiprocessing telah ditambahkan yang menyediakan API dan terlihat sangat mirip jika Anda menggunakan
threading sebelumnya.
Mari kita pergi dan mengubah contoh sebelumnya. Sekarang versi yang dimodifikasi menggunakan
Proses alih-alih
Stream .
import multiprocessing import time import random def worker(number): sleep = random.randrange(1, 10) time.sleep(sleep) print("I am Worker {}, I slept for {} seconds".format(number, sleep)) for i in range(5): t = multiprocessing.Process(target=worker, args=(i,)) t.start() print("All Processes are queued, let's see when they finish!")
Apa yang berubah? Saya baru saja mengimpor modul
multiprosesing alih-alih
threading . Dan kemudian, alih-alih utas, saya menggunakan proses. Itu saja! Sekarang, alih-alih banyak utas, kami menggunakan proses yang berjalan pada core CPU yang berbeda (kecuali, tentu saja, prosesor Anda memiliki beberapa core).
Menggunakan kelas Pool, kita juga dapat mendistribusikan eksekusi satu fungsi antara beberapa proses untuk nilai input yang berbeda. Contoh dari dokumen resmi:
from multiprocessing import Pool def f(x): return x*x if __name__ == '__main__': p = Pool(5) print(p.map(f, [1, 2, 3]))
Di sini, alih-alih mengulangi daftar nilai dan memanggil fungsi satu per satu, kita sebenarnya menjalankan fungsi dalam proses yang berbeda. Satu proses tidak f (1), yang lain f (2), dan yang lain f (3). Akhirnya, hasilnya digabungkan lagi menjadi daftar. Ini memungkinkan kami untuk memecah perhitungan berat menjadi bagian-bagian yang lebih kecil dan menjalankannya secara paralel untuk perhitungan yang lebih cepat.
Bacaan lebih lanjut:Modul Concurrent.futuresModul concurrent.futures berukuran besar dan membuat penulisan kode asinkron sangat mudah. Favorit saya adalah
ThreadPoolExecutor dan
ProcessPoolExecutor . Seniman-seniman ini mendukung kumpulan benang atau proses. Kami mengirim tugas kami ke kumpulan, dan menjalankan tugas dalam utas / proses yang dapat diakses. Objek
Masa Depan dikembalikan, yang dapat digunakan untuk kueri dan mendapatkan hasil saat tugas selesai.
Dan berikut ini adalah contoh ThreadPoolExecutor:
from concurrent.futures import ThreadPoolExecutor from time import sleep def return_after_5_secs(message): sleep(5) return message pool = ThreadPoolExecutor(3) future = pool.submit(return_after_5_secs, ("hello")) print(future.done()) sleep(5) print(future.done()) print(future.result())
Saya punya artikel tentang concurrent.futures
masnun.com/2016/03/29/python-a-quick-introduction-to-the-concurrent-futures-module.html . Ini dapat bermanfaat untuk mempelajari modul ini lebih dalam.
Bacaan lebih lanjut:Asyncio - apa, bagaimana dan mengapa?
Anda mungkin memiliki pertanyaan yang dimiliki banyak orang di komunitas Python - apa yang dibawa asyncio baru? Mengapa ada cara lain untuk menggunakan I / O asinkron? Bukankah kita sudah memiliki utas dan proses? Ayo lihat!
Mengapa kita perlu asyncio?Prosesnya sangat mahal
[dalam hal konsumsi sumber daya, kira-kira. penerjemah] untuk membuat. Oleh karena itu, untuk operasi I / O, utas terutama dipilih. Kita tahu bahwa I / O tergantung pada hal-hal eksternal - drive lambat atau kelambatan jaringan yang tidak menyenangkan membuat I / O sering tidak dapat diprediksi. Sekarang anggaplah kita menggunakan utas untuk I / O. 3 utas melakukan berbagai tugas I / O. Penerjemah harus beralih di antara arus kompetitif dan memberi mereka masing-masing waktu. Sebut arus T1, T2, dan T3. Tiga utas memulai operasi I / O mereka. T3 menyelesaikannya terlebih dahulu. T2 dan T1 masih menunggu I / O. Penerjemah Python beralih ke T1, tetapi masih menunggu. Nah, penerjemah pindah ke T2, dan penerjemah masih menunggu, dan kemudian pindah ke T3, yang siap dan menjalankan kode. Apakah Anda melihat ini sebagai masalah?
T3 sudah siap, tetapi penerjemah pertama kali beralih antara T2 dan T1 - ini biaya pengalihan, yang bisa kita hindari jika penerjemah pertama kali beralih ke T3, kan?
Apa itu asynio?Asyncio menyediakan bagi kita loop acara bersama dengan hal-hal keren lainnya. Event loop memonitor I / O events dan mengganti tugas yang siap dan menunggu operasi I / O
[loop event adalah sebuah konstruksi perangkat lunak yang menunggu kedatangan dan mengirimkan acara atau pesan dalam program, kira-kira. penerjemah] .
Idenya sangat sederhana. Ada loop acara. Dan kami memiliki fungsi yang melakukan asynchronous I / O. Kami mentransfer fungsi kami ke loop acara dan memintanya untuk menjalankannya untuk kami. Loop acara mengembalikan kita objek Masa Depan, seperti janji bahwa di masa depan kita akan mendapatkan sesuatu. Kami berpegang pada janji, memeriksa dari waktu ke waktu apakah itu penting (kami benar-benar tidak bisa menunggu), dan akhirnya, ketika nilainya diterima, kami menggunakannya dalam beberapa operasi lain
[mis. kami mengirim permintaan, kami segera diberi tiket dan disuruh menunggu sampai hasilnya datang. Kami memeriksa hasilnya secara berkala dan begitu diterima, kami menerima tiket dan mendapatkan nilai, kira-kira. penerjemah] .
Asyncio menggunakan generator dan coroutine untuk berhenti dan melanjutkan tugas. Anda dapat membaca detailnya di sini:
Bagaimana cara menggunakan asyncio?Sebelum kita mulai, mari kita lihat sebuah contoh:
import asyncio import datetime import random async def my_sleep_func(): await asyncio.sleep(random.randint(0, 5)) async def display_date(num, loop): end_time = loop.time() + 50.0 while True: print("Loop: {} Time: {}".format(num, datetime.datetime.now())) if (loop.time() + 1.0) >= end_time: break await my_sleep_func() loop = asyncio.get_event_loop() asyncio.ensure_future(display_date(1, loop)) asyncio.ensure_future(display_date(2, loop)) loop.run_forever()
Perhatikan bahwa sintaks async / await hanya untuk Python 3.5 dan yang lebih baru. Mari kita lihat kodenya:
- Kami memiliki fungsi display_date asynchronous yang mengambil angka (sebagai pengidentifikasi) dan loop acara sebagai parameter.
- Fungsi ini memiliki loop tak terbatas, yang terputus setelah 50 detik. Tetapi selama periode ini, dia berulang kali mencetak waktu dan berhenti. Fungsi menunggu dapat menunggu untuk menyelesaikan fungsi asinkron lainnya (coroutine).
- Kami meneruskan fungsi ke loop acara (menggunakan metode sure_future).
- Kami memulai siklus acara.
Setiap kali menunggu dipanggil, asyncio menyadari bahwa fungsi tersebut mungkin akan memakan waktu. Dengan demikian, itu menjeda eksekusi, mulai memonitor semua peristiwa I / O yang terkait dengannya, dan memungkinkan Anda untuk menjalankan tugas. Ketika asyncio memperhatikan bahwa fungsi I / O yang dijeda sudah siap, ia melanjutkan fungsinya.
Membuat pilihan yang tepat.
Kami baru saja melalui bentuk daya saing yang paling populer. Tetapi pertanyaannya tetap - apa yang harus dipilih? Itu tergantung pada kasus penggunaan. Dari pengalaman saya, saya cenderung mengikuti pseudo-code ini:
if io_bound: if io_very_slow: print("Use Asyncio") else: print("Use Threads") else: print("Multi Processing")
- CPU Bound => Pemrosesan Multi
- I / O Bound, I / O Cepat, Jumlah Koneksi Terbatas => Multi Threading
- I / O Bound, I / O Lambat, Banyak koneksi => Asyncio
[Catatan penerjemah]