Mulakan dengan async di Python

Pengaturcaraan tak segerak, atau ringkasnya asinkron , adalah ciri banyak bahasa moden yang membolehkan program menyusun banyak operasi tanpa menunggu atau menutup salah satu daripadanya. Ini adalah kaedah pintar untuk menangani tugas dengan cekap seperti rangkaian atau fail I / O, di mana sebahagian besar masa program dihabiskan untuk menunggu tugas selesai.

Pertimbangkan aplikasi pengikisan web yang membuka 100 sambungan rangkaian. Anda boleh membuka satu sambungan, tunggu hasilnya, kemudian buka yang berikutnya dan tunggu hasilnya, dan seterusnya. Sebilangan besar masa program dijalankan dihabiskan untuk menunggu tindak balas rangkaian, tidak melakukan kerja yang sebenarnya.

Async memberi anda kaedah yang lebih cekap: Buka semua 100 sambungan sekaligus, kemudian beralih di antara setiap sambungan aktif kerana mereka mengembalikan hasil. Sekiranya satu sambungan tidak mengembalikan hasil, beralih ke sambungan berikutnya, dan seterusnya, sehingga semua sambungan mengembalikan data mereka.

Sintaks Async kini menjadi ciri standard di Python, tetapi Pythonistas lama yang terbiasa melakukan satu perkara pada satu masa mungkin menghadapi masalah untuk membungkus kepala mereka di sekitarnya. Dalam artikel ini kita akan menerangkan bagaimana pengaturcaraan asinkron berfungsi di Python, dan cara menggunakannya.

Perhatikan bahawa jika anda ingin menggunakan async di Python, lebih baik menggunakan Python 3.7 atau Python 3.8 (versi terkini untuk penulisan ini). Kami akan menggunakan fungsi sintaks dan pembantu async Python seperti yang ditentukan dalam versi bahasa tersebut.

Bila hendak menggunakan pengaturcaraan tak segerak

Secara amnya, masa terbaik untuk menggunakan async adalah ketika anda berusaha melakukan pekerjaan yang mempunyai ciri-ciri berikut:

  • Kerja memerlukan masa yang lama untuk disiapkan.
  • Kelewatan itu melibatkan menunggu operasi I / O (cakera atau rangkaian), bukan pengiraan.
  • Kerja itu melibatkan banyak operasi I / O yang berlaku sekaligus, atau satu atau lebih operasi I / O berlaku ketika anda juga berusaha menyelesaikan tugas lain.

Async membolehkan anda mengatur beberapa tugas secara selari dan mengulanginya dengan cekap, tanpa menyekat aplikasi anda yang lain.

Beberapa contoh tugas yang berfungsi dengan baik dengan async:

  • Mengikis laman web, seperti yang dijelaskan di atas.
  • Perkhidmatan rangkaian (contohnya, pelayan web atau kerangka kerja).
  • Program yang menyelaraskan hasil dari pelbagai sumber yang memerlukan waktu yang lama untuk mengembalikan nilai (contohnya, pertanyaan pangkalan data serentak).

Penting untuk diperhatikan bahawa pengaturcaraan tak segerak berbeza dari multithreading atau multiprocessing. Operasi async semuanya berjalan dalam urutan yang sama, tetapi mereka saling memberi hasil yang diperlukan, menjadikan async lebih efisien daripada threading atau multiprocessing untuk pelbagai jenis tugas. (Lebih lanjut mengenai perkara ini di bawah.)

Python asyncawaitdanasyncio

Python baru-baru ini menambah dua kata kunci, asyncdan await, untuk membuat operasi tak segerak. 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 async dari skrip yang sama - tidak berfungsi, cukup untuk memberi kita idea bagaimana sintaks berfungsi - mungkin kelihatan seperti ini.

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

Fungsi yang diawali dengan asynckata kunci menjadi fungsi tak segerak, juga dikenali sebagai coroutine . Coroutine berkelakuan berbeza dari fungsi biasa:

  • Coroutine dapat menggunakan kata kunci lain await, yang membolehkan coroutine menunggu hasil dari coroutine lain tanpa menyekat. Sehingga hasilnya kembali dari awaitcoroutine ed, Python beralih secara bebas di antara coroutine yang lain.
  • Coroutine hanya boleh dipanggil dari asyncfungsi lain . Sekiranya anda menjalankan server_ops()atau get_server_status()sebagaimana mestinya dari badan skrip, anda tidak akan mendapat hasilnya; anda akan mendapat objek coroutine Python, yang tidak dapat digunakan secara langsung.

Oleh itu, jika kita tidak dapat memanggil asyncfungsi dari fungsi bukan asinkron, dan kita tidak dapat menjalankan asyncfungsi secara langsung, bagaimana kita menggunakannya? Jawapan: Dengan menggunakan asyncioperpustakaan, yang menghubungkan asyncdan seluruh Python.

Python asyncawaitdan asynciocontoh

Berikut adalah contoh (sekali lagi, tidak berfungsi tetapi ilustratif) bagaimana seseorang boleh menulis aplikasi mengikis web menggunakan asyncdan asyncio. Skrip ini mengambil senarai URL dan menggunakan beberapa asyncfungsi dari perpustakaan luaran ( read_from_site_async()) untuk memuat turunnya dan mengumpulkan hasilnya.

import asyncio dari web_scraping_library import read_from_site_async def 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 (utama (url)) cetak (hasil) 

Dalam contoh di atas, kami menggunakan dua asynciofungsi umum :

  • asyncio.run()digunakan untuk melancarkan asyncfungsi dari bahagian non-asinkron dari kod kami, dan dengan itu memulakan semua aktiviti async progam. (Ini adalah bagaimana kita menjalankan main().)
  • asyncio.gather()mengambil satu atau lebih fungsi yang dihiasi asinkron (dalam kes ini, beberapa contoh read_from_site_async()dari perpustakaan pengikis web hipotesis kami), menjalankan semuanya, dan menunggu semua hasil masuk.

Ideanya di sini adalah, kita memulakan operasi membaca untuk semua laman web sekaligus, kemudian mengumpulkan hasilnya ketika mereka tiba (oleh itu asyncio.gather()). Kami tidak menunggu satu operasi selesai sebelum beralih ke operasi berikutnya.

Komponen aplikasi async Python

Kami telah menyebut bagaimana aplikasi Python async menggunakan coroutine sebagai bahan utama mereka, menggunakan asyncioperpustakaan untuk menjalankannya. Beberapa elemen lain juga merupakan kunci aplikasi asinkron di Python:

Gelung acara

The asyncioperpustakaan mencipta dan menguruskan gelung acara , mekanisme yang berjalan coroutines sehingga mereka selesai. Hanya satu gelung acara yang harus dijalankan pada satu masa dalam proses Python, jika hanya untuk memudahkan pengaturcara melacak apa yang masuk ke dalamnya.

Tugas

Apabila anda menyerahkan coroutine ke gelung peristiwa untuk diproses, anda dapat mendapatkan kembali Taskobjek, yang menyediakan cara untuk mengawal tingkah laku coroutine dari luar gelung peristiwa. Sekiranya anda perlu membatalkan tugas yang sedang berjalan, misalnya, anda dapat melakukannya dengan memanggil kaedah tugas .cancel().

Berikut adalah versi skrip pengikis laman yang sedikit berbeza yang menunjukkan gelung acara dan tugas di tempat kerja:

import asyncio dari web_scraping_library import read_from_site_async task = [] async def main (url_list): for n in url_list: tasks.append (asyncio.create_task (read_from_site_async (n))) cetak (tugas) kembali tunggu tugas asyncio.gather (* = ['//site1.com','//othersite.com','//newsite.com'] loop = asyncio.get_event_loop () hasil = loop.run_until_complete (utama (url)) cetakan (hasil) 

Skrip ini menggunakan gelung peristiwa dan objek tugas dengan lebih jelas.

  • 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 boleh meneroka semakin banyak perpustakaan dan perisian tengah yang disokong oleh async, yang mana banyak menyediakan versi penyambung pangkalan data, protokol rangkaian dan sejenisnya yang tidak segerak. The aio-libsrepositori mempunyai beberapa orang-orang yang utama, seperti aiohittpperpustakaan untuk akses web. Ia juga bernilai mencari Python Package Index untuk perpustakaan dengan asynckata kunci. Dengan sesuatu seperti pengaturcaraan tak segerak, kaedah terbaik untuk belajar adalah dengan melihat bagaimana orang lain menggunakannya.