Mulailah dengan async dengan Python

Pemrograman asinkron, atau singkatnya async , adalah fitur dari banyak bahasa modern yang memungkinkan program untuk menyulap beberapa operasi tanpa menunggu atau terputus pada salah satu dari mereka. Ini adalah cara cerdas untuk menangani tugas-tugas seperti jaringan atau file I / O secara efisien, di mana sebagian besar waktu program dihabiskan untuk menunggu tugas selesai.

Pertimbangkan aplikasi web scraping yang membuka 100 koneksi jaringan. Anda bisa membuka satu koneksi, menunggu hasilnya, lalu membuka koneksi berikutnya dan menunggu hasilnya, dan seterusnya. Sebagian besar waktu program berjalan dihabiskan untuk menunggu respon jaringan, bukan melakukan pekerjaan yang sebenarnya.

Async memberi Anda metode yang lebih efisien: Buka semua 100 koneksi sekaligus, lalu beralih di antara setiap koneksi aktif saat mereka memberikan hasil. Jika satu koneksi tidak memberikan hasil, beralihlah ke yang berikutnya, dan seterusnya, sampai semua koneksi mengembalikan datanya.

Sintaks Async sekarang menjadi fitur standar di Python, tetapi Pythonista lama yang terbiasa melakukan satu hal pada satu waktu mungkin mengalami kesulitan membungkus kepalanya di sekitarnya. Pada artikel ini kita akan menjelajahi bagaimana pemrograman asinkron bekerja dengan Python, dan bagaimana menggunakannya.

Perhatikan bahwa jika Anda ingin menggunakan async dengan Python, yang terbaik adalah menggunakan Python 3.7 atau Python 3.8 (versi terbaru saat tulisan ini dibuat). Kami akan menggunakan sintaksis asinkron dan fungsi helper Python seperti yang didefinisikan dalam versi bahasa tersebut.

Kapan menggunakan pemrograman asynchronous

Secara umum, waktu terbaik untuk menggunakan asinkron adalah saat Anda mencoba melakukan pekerjaan yang memiliki ciri-ciri berikut:

  • Pekerjaan itu membutuhkan waktu lama untuk diselesaikan.
  • Penundaan ini melibatkan menunggu operasi I / O (disk atau jaringan), bukan komputasi.
  • Pekerjaan tersebut melibatkan banyak operasi I / O yang terjadi sekaligus, atau satu atau lebih operasi I / O yang terjadi saat Anda juga mencoba menyelesaikan tugas lain.

Async memungkinkan Anda menyiapkan banyak tugas secara paralel dan mengulanginya secara efisien, tanpa memblokir aplikasi Anda lainnya.

Beberapa contoh tugas yang berfungsi baik dengan asinkron:

  • Scraping web, seperti dijelaskan di atas.
  • Layanan jaringan (misalnya, server web atau kerangka kerja).
  • Program yang mengoordinasikan hasil dari berbagai sumber yang membutuhkan waktu lama untuk mengembalikan nilai (misalnya, kueri database simultan).

Penting untuk diperhatikan bahwa pemrograman asynchronous berbeda dengan multithreading atau multiprocessing. Semua operasi asinkron berjalan di utas yang sama, tetapi mereka menghasilkan satu sama lain sesuai kebutuhan, membuat asinkron lebih efisien daripada threading atau multiprosesing untuk berbagai jenis tugas. (Lebih lanjut tentang ini di bawah.)

Python asyncawaitdanasyncio

Python baru-baru ini menambahkan dua kata kunci, asyncdan await, untuk membuat operasi asinkron. Pertimbangkan skrip ini:

def get_server_status (server_addr) # Operasi yang berpotensi berjalan lama ... return server_status def server_ops () results = [] results.append (get_server_status ('addr1.server') results.append (get_server_status ('addr2.server') return hasil 

Versi asinkron dari skrip yang sama — tidak berfungsi, hanya cukup untuk memberi kita gambaran tentang cara kerja sintaks — mungkin terlihat seperti ini.

async def get_server_status (server_addr) # Operasi yang berpotensi berjalan lama ... return server_status async def server_ops () results = [] results.append (await get_server_status ('addr1.server') results.append (tunggu get_server_status ('addr2. server ') mengembalikan hasil 

Fungsi yang diawali dengan asynckata kunci menjadi fungsi asinkron, juga dikenal sebagai coroutine . Coroutine berperilaku berbeda dari fungsi biasa:

  • Coroutine dapat menggunakan kata kunci lain await, yang memungkinkan coroutine menunggu hasil dari coroutine lain tanpa memblokir. Sampai hasil keluar dari awaitcoroutine ed, Python beralih bebas di antara coroutine yang sedang berjalan lainnya.
  • Coroutine hanya dapat dipanggil dari asyncfungsi lain . Jika Anda menjalankan server_ops()atau get_server_status()apa adanya dari badan skrip, Anda tidak akan mendapatkan hasilnya; Anda akan mendapatkan objek coroutine Python, yang tidak dapat digunakan secara langsung.

Jadi jika kita tidak dapat memanggil asyncfungsi dari fungsi non-asinkron, dan kita tidak dapat menjalankan asyncfungsi secara langsung, bagaimana kita menggunakannya? Jawaban: Dengan menggunakan asyncioperpustakaan, yang menjembatani asyncdan sisa Python.

Python asyncawaitdan asynciocontoh

Berikut adalah contoh (sekali lagi, tidak fungsional tetapi ilustrasi) tentang bagaimana seseorang dapat menulis aplikasi web scraping menggunakan asyncdan asyncio. Skrip ini mengambil daftar URL dan menggunakan beberapa contoh asyncfungsi dari pustaka eksternal ( read_from_site_async()) untuk mengunduhnya dan menggabungkan hasilnya.

import asyncio dari web_scraping_library import read_from_site_async async def main (url_list): return await asyncio.gather (* [read_from_site_async (_) for _ in url_list]) urls = ['//site1.com','//othersite.com', '//newsite.com'] hasil = asyncio.run (main (url)) print (hasil) 

Dalam contoh di atas, kami menggunakan dua asynciofungsi umum :

  • asyncio.run()digunakan untuk meluncurkan asyncfungsi dari bagian non-asinkron dari kode kita, dan dengan demikian memulai semua aktivitas asinkron progam. (Beginilah cara kami berlari main().)
  • asyncio.gather()mengambil satu atau beberapa fungsi yang didekorasi dengan asinkron (dalam hal ini, beberapa contoh dari read_from_site_async()pustaka pengikisan web hipotetis kami), menjalankan semuanya, dan menunggu semua hasil masuk.

Idenya di sini adalah, kami memulai operasi baca untuk semua situs sekaligus, lalu mengumpulkan hasil saat mereka tiba (karenanya asyncio.gather()). Kami tidak menunggu satu operasi pun selesai sebelum melanjutkan ke operasi berikutnya.

Komponen aplikasi asinkron Python

Kami telah menyebutkan bagaimana aplikasi asinkron Python menggunakan coroutine sebagai bahan utamanya, menggambar di asyncioperpustakaan untuk menjalankannya. Beberapa elemen lain juga merupakan kunci untuk aplikasi asinkron di Python:

Perulangan acara

The asyncioperpustakaan menciptakan dan mengelola loop acara , mekanisme yang dijalankan coroutines sampai mereka menyelesaikan. Hanya satu event loop yang harus berjalan dalam satu waktu dalam proses Python, jika hanya untuk memudahkan programmer melacak apa yang masuk ke dalamnya.

Tugas

Saat Anda mengirimkan coroutine ke loop peristiwa untuk diproses, Anda bisa mendapatkan kembali Taskobjek, yang menyediakan cara untuk mengontrol perilaku coroutine dari luar loop peristiwa. Jika Anda perlu membatalkan tugas yang sedang berjalan, misalnya, Anda dapat melakukannya dengan memanggil metode tugas .cancel().

Berikut adalah versi yang sedikit berbeda dari skrip pengikis situs yang menunjukkan loop peristiwa dan tugas yang sedang bekerja:

impor asyncio dari web_scraping_library import read_from_site_async tugas = [] async def main (url_list): untuk n di url_list: tugas.append (asyncio.create_task (read_from_site_async (n))) cetak (tugas) kembali menunggu asyncio.gather (* tugas) urls = ['//site1.com','//othersite.com','//newsite.com'] loop = asyncio.get_event_loop () results = loop.run_until_complete (main (url)) print (hasil) 

Skrip ini menggunakan event loop dan objek tugas secara lebih eksplisit.

  • The .get_event_loop() method provides us with an object that lets us control the event loop directly, by submitting async functions to it programmatically via .run_until_complete(). In the previous script, we could only run a single top-level async function, using asyncio.run(). By the way, .run_until_complete() does exactly what it says: It runs all of the supplied tasks until they’re done, then returns their results in a single batch.
  • The .create_task() method takes a function to run, including its parameters, and gives us back a Task object to run it. Here we submit each URL as a separate Task to the event loop, and store the Task objects in a list. Note that we can only do this inside the event loop—that is, inside an async function.

How much control you need over the event loop and its tasks will depend on how complex the application is that you’re building. If you just want to submit a set of fixed jobs to run concurrently, as with our web scraper, you won’t need a whole lot of control—just enough to launch jobs and gather the results. 

By contrast, if you’re creating a full-blown web framework, you’ll want far more control over the behavior of the coroutines and the event loop. For instance, you may need to shut down the event loop gracefully in the event of an application crash, or run tasks in a threadsafe manner if you’re calling the event loop from another thread.

Async vs. threading vs. multiprocessing

At this point you may be wondering, why use async instead of threads or multiprocessing, both of which have been long available in Python?

First, there is a key difference between async and threads or multiprocessing, even apart from how those things are implemented in Python. Async is about concurrency, while threads and multiprocessing are about parallelism. Concurrency involves dividing time efficiently among multiple tasks at once—e.g., checking your email while waiting for a register at the grocery store. Parallelism involves multiple agents processing multiple tasks side by side—e.g., having five separate registers open at the grocery store.

Most of the time, async is a good substitute for threading as threading is implemented in Python. This is because Python doesn’t use OS threads but its own cooperative threads, where only one thread is ever running at a time in the interpreter. In comparison to cooperative threads, async provides some key advantages:

  • Async functions are far more lightweight than threads. Tens of thousands of asynchronous operations running at once will have far less overhead than tens of thousands of threads.
  • The structure of async code makes it easier to reason about where tasks pick up and leave off. This means data races and thread safety are less of an issue. Because all tasks in the async event loop run in a single thread, it’s easier for Python (and the developer) to serialize how they access objects in memory.
  • Async operations can be cancelled and manipulated more readily than threads. The Task object we get back from asyncio.create_task() provides us with a handy way to do this.

Multiprocessing in Python, on the other hand, is best for jobs that are heavily CPU-bound rather than I/O-bound. Async actually works hand-in-hand with multiprocessing, as you can use asyncio.run_in_executor() to delegate CPU-intensive jobs to a process pool from a central process, without blocking that central process.

Next steps with Python async

The best first thing to do is build a few, simple async apps of your own. Good examples abound now that asynchronous programming in Python has undergone a few versions and had a couple of years to settle down and become more widely used. The official documentation for asyncio is worth reading over to see what it offers, even if you don’t plan to make use of all of its functions.

Anda juga dapat menjelajahi semakin banyak pustaka dan middleware yang didukung asinkron, banyak di antaranya menyediakan versi konektor basis data asinkron, protokol jaringan, dan sejenisnya. The aio-libsrepositori memiliki beberapa yang utama, seperti aiohittpperpustakaan untuk akses web. Layak juga mencari Indeks Paket Python untuk pustaka dengan asynckata kunci. Dengan sesuatu seperti pemrograman asynchronous, cara terbaik untuk belajar adalah dengan melihat bagaimana orang lain memanfaatkannya.