Python 3.5 memperkenalkan I/O tak segerak sebagai alternatif kepada utas untuk mengendalikan konkurensi. Kelebihan I/O asynchronous dan pelaksanaan asyncio dalam Python ialah dengan tidak menghasilkan rangkaian sistem pengendalian intensif memori, sistem menggunakan lebih sedikit sumber dan lebih berskala. Tambahan pula, dalam asyncio, titik penjadualan ditakrifkan dengan jelas melalui sintaks await
, manakala dalam konkurensi berasaskan benang, GIL mungkin dikeluarkan pada titik kod yang tidak dapat diramalkan. Akibatnya, sistem konkurensi berasaskan asyncio lebih mudah difahami dan nyahpepijat. Akhirnya, tugas asyncio boleh dibatalkan, yang tidak mudah dilakukan apabila menggunakan benang.
Walau bagaimanapun, untuk benar-benar mendapat manfaat daripada kelebihan ini, adalah penting untuk mengelak daripada menyekat panggilan dalam coroutine async. Menyekat panggilan boleh menjadi panggilan rangkaian, panggilan sistem fail, sleep
panggilan, dsb. Panggilan menyekat ini berbahaya kerana, di bawah hud, asyncio menggunakan gelung acara satu benang untuk menjalankan coroutine secara serentak. Jadi, jika anda membuat panggilan menyekat dalam coroutine, ia menyekat keseluruhan gelung acara dan semua coroutine, yang menjejaskan prestasi keseluruhan aplikasi anda.
Berikut ialah contoh panggilan menyekat yang menghalang kod daripada dilaksanakan serentak:
<code class="language-python">import asyncio import datetime import time async def example(name): print(f"{datetime.datetime.now()}: {name} start") time.sleep(1) # time.sleep 是一个阻塞函数 print(f"{datetime.datetime.now()}: {name} stop") async def main(): await asyncio.gather(example("1"), example("2")) asyncio.run(main())</code>
Hasil larian adalah serupa dengan:
<code>2025-01-07 18:50:15.327677: 1 start 2025-01-07 18:50:16.328330: 1 stop 2025-01-07 18:50:16.328404: 2 start 2025-01-07 18:50:17.333159: 2 stop</code>
Seperti yang anda lihat, kedua-dua coroutine tidak berjalan serentak.
Untuk mengatasi masalah ini, anda perlu menggunakan setara tanpa menyekat atau menangguhkan pelaksanaan ke kumpulan benang:
<code class="language-python">import asyncio import datetime import time async def example(name): print(f"{datetime.datetime.now()}: {name} start") await asyncio.sleep(1) # 将阻塞的 time.sleep 调用替换为非阻塞的 asyncio.sleep 协程 print(f"{datetime.datetime.now()}: {name} stop") async def main(): await asyncio.gather(example("1"), example("2")) asyncio.run(main())</code>
Hasil larian adalah serupa dengan:
<code>2025-01-07 18:53:53.579738: 1 start 2025-01-07 18:53:53.579797: 2 start 2025-01-07 18:53:54.580463: 1 stop 2025-01-07 18:53:54.580572: 2 stop</code>
Di sini dua coroutine dijalankan serentak.
Kini masalahnya ialah bukan selalu mudah untuk mengenal pasti sama ada sesuatu kaedah menyekat atau tidak. Terutama jika pangkalan kod besar atau menggunakan perpustakaan pihak ketiga. Kadangkala, menyekat panggilan dibuat di bahagian dalam kod.
Sebagai contoh, adakah kod ini menyekat?
<code class="language-python">import blockbuster from importlib.metadata import version async def get_version(): return version("blockbuster")</code>
Adakah Python memuatkan metadata pakej ke dalam memori semasa permulaan? Adakah ia dilakukan apabila modul blockbuster
dimuatkan? Atau apabila kita memanggil version()
? Adakah keputusan dicache dan adakah panggilan berikutnya akan tidak disekat? Jawapan yang betul dilakukan apabila memanggil version()
, yang melibatkan membaca fail METADATA pakej yang dipasang. Dan hasilnya tidak di-cache. Oleh itu, version()
ialah panggilan menyekat dan harus sentiasa ditangguhkan ke urutan. Sukar untuk mengetahui fakta ini tanpa menggali kod importlib
.
Satu cara untuk mengesan panggilan menyekat ialah mengaktifkan mod nyahpepijat asyncio untuk mengelog panggilan menyekat yang mengambil masa terlalu lama. Tetapi ini bukan pendekatan yang paling cekap, kerana banyak masa sekatan yang lebih pendek daripada tamat masa pencetus masih akan menjejaskan prestasi, dan masa sekatan dalam ujian/pembangunan mungkin berbeza daripada dalam pengeluaran. Sebagai contoh, panggilan pangkalan data mungkin mengambil masa yang lebih lama dalam persekitaran pengeluaran jika pangkalan data mesti mengambil sejumlah besar data.
Di sinilah BlockBuster masuk! Apabila diaktifkan, BlockBuster akan menampal beberapa kaedah rangka kerja Python menyekat yang akan membuang ralat jika ia dipanggil dari gelung acara asyncio. Kaedah tampalan lalai termasuk kaedah modul os
, io
, time
, socket
dan sqlite
. Untuk senarai lengkap kaedah yang dikesan oleh BlockBuster, lihat readme projek. Anda kemudian boleh mengaktifkan BlockBuster dalam ujian unit atau mod pembangunan untuk menangkap sebarang panggilan yang menyekat dan membetulkannya. Jika anda tahu pustaka BlockHound yang hebat untuk JVM, ia adalah prinsip yang sama, tetapi untuk Python. BlockHound merupakan sumber inspirasi yang hebat untuk BlockBuster, terima kasih kepada pencipta.
Mari lihat cara menggunakan BlockBuster pada coretan kod sekatan di atas.
Pertama, kita perlu memasang pakej blockbuster
<code class="language-python">import asyncio import datetime import time async def example(name): print(f"{datetime.datetime.now()}: {name} start") time.sleep(1) # time.sleep 是一个阻塞函数 print(f"{datetime.datetime.now()}: {name} stop") async def main(): await asyncio.gather(example("1"), example("2")) asyncio.run(main())</code>
Kami kemudiannya boleh menggunakan lekapan pytest dan kaedah blockbuster_ctx()
untuk mengaktifkan BlockBuster pada permulaan setiap ujian dan menyahaktifkannya semasa teardown.
<code>2025-01-07 18:50:15.327677: 1 start 2025-01-07 18:50:16.328330: 1 stop 2025-01-07 18:50:16.328404: 2 start 2025-01-07 18:50:17.333159: 2 stop</code>
Jika anda menjalankan ini dengan pytest anda akan mendapat
<code class="language-python">import asyncio import datetime import time async def example(name): print(f"{datetime.datetime.now()}: {name} start") await asyncio.sleep(1) # 将阻塞的 time.sleep 调用替换为非阻塞的 asyncio.sleep 协程 print(f"{datetime.datetime.now()}: {name} stop") async def main(): await asyncio.gather(example("1"), example("2")) asyncio.run(main())</code>
Nota: Biasanya, dalam projek sebenar, lekapan
blockbuster()
akan disediakan dalam failconftest.py
.
Saya percaya BlockBuster sangat berguna dalam projek asyncio. Ia telah membantu saya mengesan banyak isu panggilan menyekat dalam projek yang telah saya usahakan. Tetapi ia bukan ubat penawar. Khususnya, sesetengah perpustakaan pihak ketiga tidak menggunakan kaedah rangka kerja Python untuk berinteraksi dengan rangkaian atau sistem fail, sebaliknya membungkus perpustakaan C. Untuk perpustakaan ini, anda boleh menambah peraturan dalam persediaan ujian anda untuk mencetuskan panggilan menyekat ke perpustakaan ini. BlockBuster juga merupakan sumber terbuka: sumbangan amat dialu-alukan untuk menambah peraturan untuk perpustakaan kegemaran anda dalam projek teras. Jika anda melihat isu dan bidang untuk penambahbaikan, saya ingin menerima maklum balas anda dalam penjejak isu projek.
Beberapa pautan:
Atas ialah kandungan terperinci Memperkenalkan BlockBuster: adakah gelung acara asyncio saya disekat?. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!