Pythonda asinxron dasturlashga kirish

Pythonda asinxron dasturlashga kirish

Dasturlash olamiga yangi kirganlar va ba’zan hatto anchagina tajribasi borlar uchun ham synchronous, asnchronous, multithreading, multiprocessing kabi so’zlar ba’zida tushunarsiz bo’ladi. Keling, bugun o’sha afsonaviy so’zlardan biri asinxron aslida nima ekani, qanday ishlashi, bizga nega kerakligi va uni Python tilida qanday ishlatish haqida ozgina o’rganamiz. Qani ketdik…

Pythonda asinxron dasturlashga kirish

Asinxron haqida tushunchaga ega bo’lish uchun avval thread(oqim) nima ekani haqida ozgina gaplashsak. Biz odatiy kod yozganimizda kod komputer tomonidan tepadan pastga qarab ketma-ket o’qiladi va bajariladi. Xuddi shu, ketma-ket bajariladigan operatsiyalar ketma-ketligi thread deyiladi. Boshqacha qilib aytadigan bo’lsam, thread bu bizning kodimizni bajarish uchun komputer tomonidan yollangan «ishchi». Uning vazifasi esa kodni o’qib, kerakli operatsiyalarni bajarish.

Thread kodlarni tepadan-pastga qarab bajaradi va qaysidir qismida kutish holatiga tushishi mumkin. Masalan, dastur bizdan input qiymat kiritishni so’raganda ishlashdan to’xtab turadi va qiymat kiritganimizda yana ishida davom etadi. Bu holatda threadni «blocked», ya’ni bloklangan deyishadi. Bunday holatni keltirib chiqaradigan operatsiyalar esa umumiy qilib «blocking call» deyiladi. Dasturning tashqaridan ma’lumot oladigan yoki ma’lumot uzatadigan qismi IO-bound workload deyiladi. Umuman olganda, har qanday turdagi IO-bound workload blocking callga misol bo’la oladi. Menimcha thread haqida eng asosiy narsalarni bilib oldik.

Deylik, dasturimiz qisqa vaqt ichida ko’p ishni bajarishi kerak. Mexanikada bir qoida bor: quvvat(ya’ni ishchi kuchi) qancha oshsa, ishni bajarishga ketadigan vaqt shuncha kamayadi. Masalan, ishni 1 ta ishchi 10 kunda tugatsa, 5 ta ishchi ikki kunda tugatadi.
Biz ham dasturimiz tezroq tugashi uchun ko’proq ishchi yollaymiz. Butun ishni bitta threadga yuklab qo’yish o’rniga bir nechta thread ochib, umumiy ishni ularga taqsimlab beramiz. Aynan mana shu narsa multithreading deyiladi. Oddiy matematika…
Bugungi mavzumiz multithreading bo’lmagani uchun bu haqida batafsil to’xtalmaymiz.

Dasturlash, umuman engineering sohasida biror usul yoki texnologiya ishlab chiqilsa, buning uchun albatta qandaydir sabab bo’ladi. O’sha yangi narsa qaysidir muammo(lar)ni hal qilishi kerak. Va yomon tarafi, o’sha yangi usulning o’zi ham boshqa, yangi muammolarni keltirib chiqaradi… Engineer sifatida vazifamiz ham muammolarga yechim topish. Multithreading orqali dastur bajarilishiga ko’p vaqt sarflash muammosiga qaysidir darajada yechim topdik. Xo’sh, bu yechim optimalmi? Asinxron bizga nega kerak?

Bu mavzuni tushuntirayotganimda ko’pincha bir xil misol keltiraman. Sababi, shu exampleda sabablar aniq ko’rinadi.
Tasavvur qiling, biz 100 ta URLga murojaat qilib ulardan ma’lumot olishimiz kerak. Deylik, har bitta URLdan ma’lumot olishga 1 soniya vaqt ketadi. Biz so’rov yuboramiz va 1 soniyadan keyin bizga javob keladi. Agar bitta thread ishlatsak, jarayon taxminan pastdagidek bo’ladi. javob kelishiga ketgan vaqtni o’xshatish uchun sleepdan foydalanamiz. Natijani to’liq ko’rish uchun taskni 100 marta emas, 5 marta bajaramiz:

67432b70f31fbeaf0ef32

5 ta taskni sinxron tarzda bajarish

Ko’rganingizdek, har safar faqat bitta kutish tugagandan keyingina keyingi qismga o’tayapmiz. Demak, blocking call bo’layapti. Umumiy natija esa 5 soniyadan ko’proq vaqt oldi. Tabiiyki 100 ta url uchun 100 soniyadan ko’proq vaqt ketadi(yana matematika).

Multithreading orqali bu vaqtni ancha kamaytirishimiz mumkin. Agar 10 ta threaddan foydalansak ular bir vaqtda 10 ta URL bilan ishlay oladi. Umumiy vaqt ham deyarli 10 marta kamaydi(matematika…). Umuman olganda, yaxshi natija.

Shu joyida Caleb Hattingh asynchronous executionni tushuntirgan misolni keltiraman:
Deylik, sizda kattagina restoran bor va shunga yarasha ish ham ko’p. Albatta, hamma ishga bir kishi ulgurolmaydi va shuning uchun siz o’nlab xizmatchilar oldingiz. Ixtiyoriy vaqt restoraniningizga kirganingizda shunga o’xshash vaziyatga duch kelasiz:
Bir ishchi ovqat uchun ko’kat to’g’rayapti, biri qozondagi ovqat pishishini kutib turibdi, yana biri barmen ichimlik berishini kutib turibdi, boshqasi mijozga menuni berib, buyurtma olishni kutib turibdi va h.k. Bir necha haftalik kuzatishdan so’ng shuni aniqladingizki, ishchilarning 90% dan ko’p vaqti kutish bilan o’tib ketayapti. Vaholanki, siz ularga bu vaqt uchun ham maosh to’laysiz. Bundan tashqari, xizmatchilar ko’pligidan ba’zida bir-biriga xalaqit berib qolishadi. Shunda ajoyib bir fikr kelib qoldi:
Modomiki, ular vaqtining katta qismini kutish bilan o’tkazayotgan ekan, butun restorandagi ishni kam sonli xizmatchilar bilan ham eplasa bo’lmasmikan?
Tavakkal qilib hamma xizmatchilarni ishdan bo’shatib, faqat 1 kishini qoldirdingiz.
U endi hamma ishni qiladi:
Ovqatni qozonga soladi va mijozga menuni beradi. Buyurtmani kutmasdan barmendan ichimliklarni oladi va egalariga yetkazadi. Keyin kelib boya menu bergan xo’randalardan buyurtmani oladi(agar tayyor bo’lsa) va shu orada ovqatdan ham xabar oladi. Qarabsizki, butun restoran ishlariga bir kishi ulgurayapti.

Asinxron dasturlash asosida ham xuddi shu g’oya yotadi — threadlar sonini oshirish o’rniga bitta threaddan effektivroq foydalanish. Thread bo’shmi? boshqa vazifa berish kerak.

Asinxron dasturlashda biz asosan ikki narsa — coroutine va event loopdan foydalanamiz. Coroutine bu bajarilishi kerak bo’lgan vazifa. Task deyishimiz ham mumkin. Event loop esa o’sha coroutinelarni aylanib chiqib, qaysi biri tayyor bo’lsa shunisini bajarib ketaveradigan thread.

e3c3c93b8b5982f31d3ec

Pythonda asinxron ishlash

Event loop — restorandagi xizmatchi, coroutine — bir vaqtda bajarilishi kerak bo’lgan vazifalar(buyurtma olish, ovqat tayyorlash, …).

Xuddi xizmatchi bir nechta vazifalarni bajarganidek, event loop ham bir vaqtda bir nechta coroutinelarni yurgizadi. Vaqtdan unumli foydalanish uchun esa vazifaning aynan qaysi qismi kutishni talab qilishini bilishimiz kerak. Shunda o’sha joyga kelganda hali kutib turishimiz kerakligini bilib, keyingi vazifaga o’tib ketaveramiz. Masalan, xizmatchi menuni mijozga berganidan keyin buyurtma olgunicha kutib turishi kerakligini biladi va shuning uchun vaqt sarflamasdan keyingi vazifaga o’tib ketaveradi va keyinroq yana aylanib keladi.

Agar asinxron dasturlash nima ekani tushunarli bo’lsa, keling endi u nimaga qodirligini ko’ramiz. Vazifa esa o’sha — URLdan ma’lumot olish:

5 ta taskni asinxron tarzda bajarish

5 ta taskni asinxron tarzda bajarish

5 ta task bo’lsa-da 1 soniyada? Ajoyib emasmi? Ishonavering 100 ta shunday task uchun ham deyarli shuncha vaqt ketadi.
E’tibor bering, 1-URLga request yuborilishi bilan event loop coroutineni kutmasdan keyingisiga o’tib ketayapti. Xo’sh, coroutine qayerda keyingi taskka o’tishni qanday bilayapti? Javob — await yordamida.

Demak, async asinxron funksiya(task)ni, await esa o’sha taskning aynan qaysi qism(lar)ida boshqa taskka o’tish mumkinligini bildiradi.

main funksiyasi orqali esa biz bajarishimiz kerak bo’lgan tasklarni yig’ib oldik. asyncio.gather(*coro) funksiyasi coroutinelarni yig’ib olib, bitta event loopda yurgizadi va natijalarni oladi. Biz return ishlatmaganimiz uchun natijalarni olib o’tirmadik.

Asyncio bu python tomonidan asinxron dasturlashni support qiladigan standart library. Lekin bu asinxron kod yozishning yagona usuli emas. Xohlasangiz, boshqa liblardan foydalanishingiz yoki o’zingiz yangisini yozib chiqishingiz mumkin. Hammasining asosida bir narsa yotadi.

Maqola boshida aytilganidek, hech bir usul yoki texnologiya mukammal emas. Asinxron dasturlashda ham yetarlicha kamchiliklari bor. Bularga race condition, memory leak, debug qilish qiyinligi va h.k.lar misol bo’la oladi. Eng muhimi, har bir usulning o’z o’rni bor va faqat ana shu holatdagina bizga yordam beradi. O’z o’rnida qo’llanilmasa, nafaqat effekt bermasligi, balki teskari effekt berishi ham mumkin. Asinxron dasturlash kodingiz ishlashini tezlashtirmaydi, balki kutish uchun ketadigan vaqtdan unumli foydalanishga yordam beradi. O’z-o’zidan, asinxron kod CPU-bound workloadda yordam bermaydi. Bu holda multithreading/multiprocessing ishlatish yaxshiroq fikr bo’lardi.

IO-bound workload — dasturning boshqa qismlar(internet, qattiq disk, …) bilan ma’lumot almashishi sababli threadning asosiy vaqti kutishda o’tadigan qismi.
CPU-bound workload — dasturning murakkab hisob-kitoblar yoki katta hajmdagi ma’lumotlar bilan aktiv ishlaydigan, CPU va RAMga katta yuk tushadigan qismi.

Mana bu kodda CPU-bound workloadda sinxron va asinxron deyarli bir xil vaqt sarflaganini ko’rish mumkin(sababi, ikki holatda ham thread har doim band):

CPU bound workload sinxron va asinxron funksiyada

CPU-bound workload sinxron va asinxron funksiyada

Bu maqola asinxron dasturlash haqida umumiy tasavvurga ega bo’lish uchun yozildi.

Bu hali boshlanishi xolos. Asinxron dasturlash juda katta va murakkab mavzu. Internetda har xil manbaalarda har xil talqin qilinganligi uchun o’rganish biroz qiyin. Official documentation esa siz va biz kabi end-userlar uchun emas, ko’proq framework developerlar uchun mo’ljallab yozilgan.

Shu yergacha o’qib kelgan bo’lsangiz, maqola foydali bo’libdi deb o’ylayman. Maqola bo’yicha savol va taklif/kamchiliklar bo’lsa telegramda shaxsiy chat orqali yoki Python uz guruhida yozavering.

Muallif: Bobosher Musurmonov.