19. Asinxron dasturlash

Zamonaviy dasturlarda bir vaqtning o‘zida ko‘plab ishlarni bajarish talab qilinadi.

Masalan, Softromeda dasturi bir vaqtning o‘zida:

API so‘rov yuborishi
fayl o‘qishi
ma’lumotlar bazasi bilan ishlashi
foydalanuvchidan ma’lumot kutishi
bir nechta server bilan aloqa qilishi

mumkin.

Agar dastur har bir ishni ketma-ket kutib bajarsa, vaqt behuda sarflanishi mumkin.

Bunday vaziyatlarda asinxron dasturlash juda foydali.

Bu bobda:

  • sinxron va asinxron dasturlash
  • async
  • await
  • asyncio
  • coroutine
  • task
  • parallelizm va concurrency
  • asinxron API bilan ishlash

mavzularini o‘rganamiz.


Sinxron dasturlash

Sinxron dasturlashda amallar odatda ketma-ket bajariladi.

Masalan:

import time


def birinchi_vazifa():

    print(
        "Birinchi vazifa boshlandi."
    )

    time.sleep(2)

    print(
        "Birinchi vazifa tugadi."
    )


def ikkinchi_vazifa():

    print(
        "Ikkinchi vazifa boshlandi."
    )

    time.sleep(2)

    print(
        "Ikkinchi vazifa tugadi."
    )


birinchi_vazifa()

ikkinchi_vazifa()

Bu dasturda:

birinchi vazifa
      ↓
kutish
      ↓
birinchi vazifa tugaydi
      ↓
ikkinchi vazifa
      ↓
kutish
      ↓
ikkinchi vazifa tugaydi

Agar har bir vazifa 2 soniya kutsa, umumiy vaqt taxminan:

4 soniya

bo‘ladi.


Asinxron dasturlash

Asinxron dasturlashda dastur kutish vaqtida boshqa ishni bajarishi mumkin.

Masalan:

Vazifa A → kutmoqda
              ↓
Vazifa B → bajarilmoqda
              ↓
Vazifa A → davom etmoqda

Bu ayniqsa:

tarmoq
API
fayl
ma’lumotlar bazasi
foydalanuvchi kiritishi

kabi kutish ko‘p bo‘ladigan operatsiyalarda foydali.


async

Python'da asinxron funksiya yaratish uchun:

async def

ishlatiladi.

Masalan:

async def salom_berish():

    print(
        "Salom, Softromeda!"
    )

Bu oddiy funksiya emas.

Bu coroutine function hisoblanadi.


Coroutine

async def yordamida yaratilgan funksiya chaqirilganda coroutine obyektini qaytaradi.

Masalan:

async def salom_berish():

    print(
        "Salom, Softromeda!"
    )


natija = salom_berish()

print(
    natija
)

Bu kod funksiyani oddiy def kabi darhol bajarmaydi.

Coroutine'ni bajarish uchun asinxron muhit kerak.


await

Coroutine ichidagi boshqa asinxron operatsiyani kutish uchun:

await

ishlatiladi.

Masalan:

import asyncio


async def kutish():

    print(
        "Kutish boshlandi."
    )

    await asyncio.sleep(2)

    print(
        "Kutish tugadi."
    )

Bu yerda:

await asyncio.sleep(2)

dasturga:

"2 soniya kut, lekin bu vaqt davomida event loop boshqa coroutine'larni ishlatishi mumkin"

degan imkoniyatni beradi.


asyncio

Python standart kutubxonasida asinxron dasturlash uchun:

asyncio

moduli mavjud.

Uni:

import asyncio

orqali ishlatamiz.


asyncio.run()

Asinxron dasturni ishga tushirishning qulay usullaridan biri:

asyncio.run()

Masalan:

import asyncio


async def asosiy():

    print(
        "Softromeda dasturi ishga tushdi."
    )


asyncio.run(
    asosiy()
)

Bu yerda:

asosiy()

coroutine yaratadi.

asyncio.run() esa uni bajaradi.


Asinxron kutish

import asyncio


async def vazifa():

    print(
        "Vazifa boshlandi."
    )

    await asyncio.sleep(2)

    print(
        "Vazifa tugadi."
    )


asyncio.run(
    vazifa()
)

Bu yerda:

await asyncio.sleep(2)

asinxron kutish hisoblanadi.


time.sleep() va asyncio.sleep()

Bu ikkisini farqlash juda muhim.

Sinxron:

time.sleep(2)

Asinxron:

await asyncio.sleep(2)

time.sleep() event loop'ni bloklaydi.

asyncio.sleep() esa boshqa coroutine'larning ishlashiga imkon beradi.

Shuning uchun asinxron dasturda odatda:

await asyncio.sleep(...)

ishlatiladi.


Bir nechta coroutine

Endi ikkita coroutine yaratamiz:

import asyncio


async def birinchi_vazifa():

    print(
        "Birinchi vazifa boshlandi."
    )

    await asyncio.sleep(2)

    print(
        "Birinchi vazifa tugadi."
    )


async def ikkinchi_vazifa():

    print(
        "Ikkinchi vazifa boshlandi."
    )

    await asyncio.sleep(2)

    print(
        "Ikkinchi vazifa tugadi."
    )


async def asosiy():

    await birinchi_vazifa()

    await ikkinchi_vazifa()


asyncio.run(
    asosiy()
)

Bu kod hali ham ketma-ket ishlaydi.

Chunki:

await birinchi_vazifa()

tugagandan keyin:

await ikkinchi_vazifa()

boshlanadi.


gather()

Bir nechta coroutine'ni birgalikda bajarish uchun:

asyncio.gather()

ishlatiladi.

import asyncio


async def birinchi_vazifa():

    print(
        "Birinchi vazifa boshlandi."
    )

    await asyncio.sleep(2)

    print(
        "Birinchi vazifa tugadi."
    )


async def ikkinchi_vazifa():

    print(
        "Ikkinchi vazifa boshlandi."
    )

    await asyncio.sleep(2)

    print(
        "Ikkinchi vazifa tugadi."
    )


async def asosiy():

    await asyncio.gather(
        birinchi_vazifa(),
        ikkinchi_vazifa()
    )


asyncio.run(
    asosiy()
)

Bu holatda ikkala vazifaning kutish vaqti bir-birining ustiga tushishi mumkin.

Shuning uchun umumiy vaqt taxminan:

2 soniya

atrofida bo‘lishi mumkin.


Coroutine'larni parallel rejalashtirish

Bu yerda muhim tushuncha mavjud.

asyncio odatda haqiqiy CPU parallelizmini ta’minlamaydi.

U concurrencyni ta’minlaydi.

Ya’ni:

Vazifa A kutmoqda
       ↓
Vazifa B ishlaydi
       ↓
Vazifa B kutmoqda
       ↓
Vazifa A davom etadi

Bu CPU hisoblashlarini bir nechta yadroda bir vaqtning o‘zida bajarish degani emas.


Concurrency va parallelizm

Concurrency — bir nechta vazifaning bajarilishini muvofiqlashtirish.

Masalan:

A kutmoqda
B ishlamoqda
B kutmoqda
C ishlamoqda
A davom etmoqda

Parallelizm esa bir nechta hisoblashning haqiqatan bir vaqtning o‘zida bajarilishi.

Masalan:

Yadro 1 → A vazifa
Yadro 2 → B vazifa

asyncio asosan:

concurrency

uchun ishlatiladi.


create_task()

Coroutine'ni task sifatida rejalashtirish uchun:

asyncio.create_task()

ishlatiladi.

Masalan:

import asyncio


async def xabar_yuborish():

    print(
        "Xabar yuborilmoqda."
    )

    await asyncio.sleep(2)

    print(
        "Xabar yuborildi."
    )


async def ma'lumot_yuklash():

    print(
        "Ma’lumot yuklanmoqda."
    )

    await asyncio.sleep(3)

    print(
        "Ma’lumot yuklandi."
    )


async def asosiy():

    xabar_vazifasi = asyncio.create_task(
        xabar_yuborish()
    )

    ma'lumot_vazifasi = asyncio.create_task(
        ma'lumot_yuklash()
    )

    await xabar_vazifasi

    await ma'lumot_vazifasi


asyncio.run(
    asosiy()
)

Bu yerda ikkala vazifa oldindan rejalashtiriladi.


Task nima?

Task — coroutine'ni event loop tomonidan bajarish uchun rejalashtirilgan obyekt.

Masalan:

vazifa = asyncio.create_task(
    ma'lumot_yuklash()
)

Bu coroutine'ni task sifatida rejalashtiradi.

Keyinchalik:

await vazifa

orqali uning tugashini kutish mumkin.


Task natijasini olish

Coroutine qiymat qaytarishi mumkin.

import asyncio


async def hisoblash():

    await asyncio.sleep(1)

    return 100


async def asosiy():

    vazifa = asyncio.create_task(
        hisoblash()
    )

    natija = await vazifa

    print(
        f"Natija: {natija}"
    )


asyncio.run(
    asosiy()
)

Natija:

Natija: 100

gather() natijalarni qaytarishi

Bir nechta coroutine natijasini olish mumkin:

import asyncio


async def birinchi():

    await asyncio.sleep(1)

    return "Birinchi"


async def ikkinchi():

    await asyncio.sleep(2)

    return "Ikkinchi"


async def asosiy():

    natijalar = await asyncio.gather(
        birinchi(),
        ikkinchi()
    )

    print(
        natijalar
    )


asyncio.run(
    asosiy()
)

Natija:

['Birinchi', 'Ikkinchi']

Asinxron funksiya argumentlari

Asinxron funksiyalar oddiy funksiyalar kabi argument qabul qiladi.

import asyncio


async def foydalanuvchini_olish(
    foydalanuvchi_id
):

    await asyncio.sleep(1)

    return {
        "id": foydalanuvchi_id,
        "ism": "Ali"
    }


async def asosiy():

    foydalanuvchi = await (
        foydalanuvchini_olish(1)
    )

    print(
        foydalanuvchi
    )


asyncio.run(
    asosiy()
)

Asinxron API bilan ishlash

Oldingi bobda HTTP API bilan ishlashni o‘rgandik.

Oddiy requests kutubxonasi sinxron ishlaydi.

Masalan:

import requests

javob = requests.get(
    "https://example.com/api"
)

Agar ko‘plab API so‘rovlarini bajarish kerak bo‘lsa, asinxron HTTP mijozdan foydalanish foydali bo‘lishi mumkin.

Mashhur kutubxonalardan biri:

aiohttp

aiohttp

O‘rnatish:

pip install aiohttp

Keyin:

import aiohttp

deb ishlatish mumkin.


Asinxron GET so‘rovi

import asyncio
import aiohttp


async def ma'lumot_olish():

    manzil = (
        "https://example.com/api"
    )

    async with aiohttp.ClientSession() as sessiya:

        async with sessiya.get(
            manzil
        ) as javob:

            ma'lumot = await javob.json()

            print(
                ma'lumot
            )


asyncio.run(
    ma'lumot_olish()
)

Bu yerda:

await javob.json()

JSON ma’lumotini asinxron tarzda olishga imkon beradi.


Bir nechta API so‘rovi

Asinxron dasturlashning kuchli tomonlaridan biri — bir nechta tarmoq so‘rovlarini birgalikda bajarish.

import asyncio
import aiohttp


async def ma'lumot_olish(
    sessiya,
    manzil
):

    async with sessiya.get(
        manzil
    ) as javob:

        return await javob.json()


async def asosiy():

    manzillar = [
        "https://example.com/api/birinchi",
        "https://example.com/api/ikkinchi",
        "https://example.com/api/uchinchi"
    ]

    async with aiohttp.ClientSession() as sessiya:

        vazifalar = [
            ma'lumot_olish(
                sessiya,
                manzil
            )
            for manzil in manzillar
        ]

        natijalar = await asyncio.gather(
            *vazifalar
        )

        for natija in natijalar:

            print(
                natija
            )


asyncio.run(
    asosiy()
)

Bu yerda bir nechta API so‘rovi birgalikda rejalashtiriladi.


async with

Asinxron resurslarni boshqarishda:

async with

ishlatiladi.

Masalan:

async with aiohttp.ClientSession() as sessiya:
    ...

Bu sessiyani to‘g‘ri ochish va yopishga yordam beradi.


Asinxron kontekst menejeri

Oddiy Python'da:

with

ishlatiladi.

Asinxron kodda:

async with

ishlatilishi mumkin.

Masalan:

async with sessiya.get(
    manzil
) as javob:

    ma'lumot = await javob.json()

Asinxron iteratorlar

Asinxron dasturlashda asinxron iteratorlardan ham foydalanish mumkin.

Buning uchun:

async for

ishlatiladi.

Masalan:

import asyncio


async def ma'lumotlar():

    for son in range(5):

        await asyncio.sleep(1)

        yield son


async def asosiy():

    async for son in ma'lumotlar():

        print(
            f"Son: {son}"
        )


asyncio.run(
    asosiy()
)

Bu yerda ma’lumotlar bosqichma-bosqich olinadi.


Asinxron generator

async def va yield birgalikda ishlatilsa, asinxron generator hosil bo‘ladi.

async def sonlarni_yuborish():

    for son in range(5):

        await asyncio.sleep(1)

        yield son

Uni:

async for

bilan ishlatamiz.


asyncio.wait()

Task'larni kutish uchun:

asyncio.wait()

ham ishlatilishi mumkin.

import asyncio


async def birinchi():

    await asyncio.sleep(1)

    print(
        "Birinchi tugadi."
    )


async def ikkinchi():

    await asyncio.sleep(2)

    print(
        "Ikkinchi tugadi."
    )


async def asosiy():

    vazifalar = {
        asyncio.create_task(
            birinchi()
        ),
        asyncio.create_task(
            ikkinchi()
        )
    }

    await asyncio.wait(
        vazifalar
    )


asyncio.run(
    asosiy()
)

Vazifani bekor qilish

Task'ni bekor qilish mumkin:

vazifa.cancel()

Masalan:

import asyncio


async def uzoq_vazifa():

    print(
        "Uzoq vazifa boshlandi."
    )

    try:

        await asyncio.sleep(10)

    except asyncio.CancelledError:

        print(
            "Uzoq vazifa bekor qilindi."
        )

        raise


async def asosiy():

    vazifa = asyncio.create_task(
        uzoq_vazifa()
    )

    await asyncio.sleep(2)

    vazifa.cancel()

    try:

        await vazifa

    except asyncio.CancelledError:

        print(
            "Vazifa bekor qilindi."
        )


asyncio.run(
    asosiy()
)

CancelledError

Bekor qilingan task:

asyncio.CancelledError

istisnosini keltirib chiqarishi mumkin.

Shuning uchun resurslarni tozalash yoki maxsus ish bajarish kerak bo‘lsa:

try:

    ...

except asyncio.CancelledError:

    ...

    raise

ko‘rinishidan foydalanish mumkin.


Timeout

Asinxron operatsiya juda uzoq davom etmasligi uchun timeout qo‘yish mumkin.

import asyncio


async def uzoq_vazifa():

    await asyncio.sleep(10)

    return "Tayyor"


async def asosiy():

    try:

        natija = await asyncio.wait_for(
            uzoq_vazifa(),
            timeout=2
        )

        print(
            natija
        )

    except asyncio.TimeoutError:

        print(
            "Vazifa uchun vaqt tugadi."
        )


asyncio.run(
    asosiy()
)

Bu yerda vazifa 10 soniya davom etmoqchi, lekin:

2 soniyalik timeout

berilgan.


Semaphore

Bir vaqtning o‘zida juda ko‘p API so‘rovi yuborish serverga ortiqcha yuk berishi mumkin.

Bunday vaziyatda:

asyncio.Semaphore

ishlatiladi.

Masalan, bir vaqtning o‘zida faqat 3 ta vazifaga ruxsat berish:

import asyncio


cheklov = asyncio.Semaphore(3)


async def vazifa(raqam):

    async with cheklov:

        print(
            f"{raqam}-vazifa boshlandi."
        )

        await asyncio.sleep(2)

        print(
            f"{raqam}-vazifa tugadi."
        )


async def asosiy():

    vazifalar = [
        vazifa(raqam)
        for raqam in range(10)
    ]

    await asyncio.gather(
        *vazifalar
    )


asyncio.run(
    asosiy()
)

Bu usul ko‘plab tarmoq so‘rovlarini boshqarishda foydali.


Asinxron dasturlash qachon foydali?

Asinxron dasturlash ayniqsa quyidagi vaziyatlarda foydali:

HTTP so‘rovlari
API
websocket
tarmoq dasturlari
ko‘plab I/O operatsiyalari
server dasturlari
bir nechta tashqi xizmat bilan ishlash

Qachon asyncio ishlatmaslik kerak?

Agar dastur asosan og‘ir CPU hisoblashlaridan iborat bo‘lsa:

katta matematik hisoblashlar
rasmni og‘ir qayta ishlash
video kodlash
katta ma’lumotlarni hisoblash

asyncio har doim ham yaxshi yechim emas.

Chunki asyncioning asosiy kuchi:

I/O-bound

vazifalarda ko‘rinadi.

CPU-bound vazifalar uchun boshqa yondashuvlar, masalan:

multiprocessing
ProcessPoolExecutor

ko‘proq mos kelishi mumkin.


I/O-bound va CPU-bound

I/O-bound — dastur ko‘p vaqtni tashqi operatsiyani kutishga sarflaydi.

Masalan:

API
internet
fayl
ma’lumotlar bazasi

CPU-bound — dastur ko‘p vaqtni protsessorda hisoblashga sarflaydi.

Masalan:

katta matematik hisoblash
murakkab algoritm
video qayta ishlash

Asinxron dasturlash:

I/O-bound

vazifalarda ayniqsa foydali.


Amaliy loyiha — Softromeda asinxron API mijoz

Endi bir nechta API manzillariga asinxron ravishda so‘rov yuboradigan dastur yaratamiz.

import asyncio
import aiohttp


class SoftromedaAPI:

    def __init__(
        self,
        asosiy_manzil
    ):

        self.asosiy_manzil = (
            asosiy_manzil.rstrip("/")
        )

    async def ma'lumot_olish(
        self,
        sessiya,
        yo'l
    ):

        manzil = (
            f"{self.asosiy_manzil}{yo'l}"
        )

        try:

            async with sessiya.get(
                manzil,
                timeout=aiohttp.ClientTimeout(
                    total=10
                )
            ) as javob:

                javob.raise_for_status()

                return await javob.json()

        except (
            aiohttp.ClientError,
            asyncio.TimeoutError
        ):

            print(
                f"So‘rov bajarilmadi: "
                f"{manzil}"
            )

            return None

    async def bir nechta_ma'lumot_olish(
        self,
        yo'llar
    ):

        async with aiohttp.ClientSession() as sessiya:

            vazifalar = [
                self.ma'lumot_olish(
                    sessiya,
                    yo'l
                )
                for yo'l in yo'llar
            ]

            natijalar = await asyncio.gather(
                *vazifalar
            )

            return natijalar


async def asosiy():

    api = SoftromedaAPI(
        "https://example.com/api"
    )

    yo'llar = [
        "/foydalanuvchilar",
        "/mahsulotlar",
        "/buyurtmalar"
    ]

    natijalar = (
        await api.bir nechta_ma'lumot_olish(
            yo'llar
        )
    )

    for indeks, natija in enumerate(
        natijalar,
        start=1
    ):

        print(
            f"{indeks}-natija:"
        )

        print(
            natija
        )

        print(
            "--------------------"
        )


asyncio.run(
    asosiy()
)

Bu loyiha quyidagi jarayonni ko‘rsatadi:

Softromeda API mijoz
        ↓
uchta API manzili
        ↓
uchta coroutine
        ↓
asyncio.gather()
        ↓
birgalikda bajarilish
        ↓
JSON natijalar

Asinxron kodni loyihalash

Katta dasturda quyidagi qatlamlarni ajratish yaxshi:

API qatlami
    ↓
ma’lumotlar qatlami
    ↓
biznes mantiq
    ↓
foydalanuvchi interfeysi

Masalan:

class SoftromedaAPI:
    ...

faqat API bilan ishlashi mumkin.

Boshqa funksiyalar esa:

foydalanuvchilarni_qayta_ishlash()

kabi ma’lumotlarni qayta ishlashi mumkin.


Asinxron dasturlashning asosiy tushunchalari

Bu bobdagi eng muhim tushunchalar:

async
await
coroutine
task
event loop
asyncio
gather
create_task
async with
async for
timeout
semaphore
concurrency

Ularning qisqacha vazifasi:

async
    asinxron funksiya yaratadi

await
    asinxron operatsiyani kutadi

coroutine
    asinxron bajarilishi mumkin bo‘lgan obyekt

task
    coroutine'ni bajarish uchun rejalashtirilgan obyekt

gather
    bir nechta coroutine'ni birgalikda kutadi

create_task
    coroutine'ni task sifatida rejalashtiradi

async with
    asinxron kontekst menejeri

async for
    asinxron iterator bilan ishlaydi

Amaliy mashqlar

Mashq 1

asyncio yordamida 3 ta asinxron funksiya yarating.

Har bir funksiya turli vaqt kutib, o‘z natijasini chiqarsin.

Mashq 2

Uchta coroutine'ni:

asyncio.gather()

yordamida birgalikda ishga tushiring.

Mashq 3

asyncio.create_task() yordamida ikkita task yarating.

Mashq 4

Task'ning qaytargan natijasini oling.

Mashq 5

asyncio.wait_for() yordamida timeout yarating.

Mashq 6

Uzoq davom etadigan task'ni cancel() yordamida bekor qiling.

Mashq 7

aiohttp yordamida API'ga asinxron GET so‘rovi yuboring.

Mashq 8

Bir vaqtning o‘zida 5 ta API so‘rovini bajaring.

Mashq 9

asyncio.Semaphore yordamida bir vaqtning o‘zida faqat 2 ta so‘rov bajarilishini ta’minlang.

Mashq 10

Softromeda uchun asinxron API mijoz yarating.

Dastur:

API bilan asinxron aloqa qilsin
bir nechta so‘rovni birgalikda bajarsin
timeout ishlatsin
xatolarni boshqarsin
tasklardan foydalansin
semaphore yordamida so‘rovlar sonini cheklasin
JSON ma’lumotlarini qayta ishlasin

Bob xulosasi

Bu bobda Python'da asinxron dasturlashni o‘rgandik.

Asinxron funksiya:

async def

orqali yaratiladi.

Asinxron operatsiyani kutish:

await

orqali bajariladi.

Asosiy modul:

import asyncio

Asosiy vositalar:

asyncio.run()
asyncio.gather()
asyncio.create_task()
asyncio.wait()
asyncio.wait_for()
asyncio.Semaphore

Tarmoq bilan asinxron ishlash uchun:

aiohttp

kabi kutubxonalardan foydalanish mumkin.

Eng muhim fikr:

asyncio
    ↓
concurrency
    ↓
I/O-bound vazifalar
    ↓
API va tarmoq operatsiyalari

Asinxron dasturlashni yaxshi tushunish Python'da zamonaviy serverlar, API mijozlari va tarmoq dasturlarini yaratishda juda muhim.

Keyingi bobda 20. Professional Python dasturlash va loyiha yaratish mavzusini o‘rganamiz.