# Python-இல் Asyncio: async/await, TaskGroup, இழைகள், multiprocessing மற்றும் GIL

URL: https://skillaria.top/ta/guides/python/asyncio-python

Asyncio என்பது Python-இன் உள்ளமைவு நூலகம்: async def செயல்பாடுகள் (coroutine-கள்) await-இல் இடைநிறுத்தப்படுகின்றன, அப்போது நிகழ்வுச் சுழற்சி (event loop) மற்ற பணிகளை இயக்குகிறது; அதனால் நூற்றுக்கணக்கான பிணையக் கோரிக்கைகள் ஒரே இழையில் ஒரே நேரத்தில் ஓடுகின்றன. பிணையம் அல்லது வட்டுக்காகக் காத்திருக்க asyncio அல்லது இழைகள் (threading) ஏற்றவை; கனமான கணக்கீடுகளுக்கு செயல்முறைகள் (multiprocessing) — ஏனெனில் சாதாரண CPython-இல் GIL இழைகளை ஒரே நேரத்தில் கணக்கிட விடுவதில்லை.

Asyncio என்பது Python-இன் ஒத்திசைவற்ற குறியீட்டுக்கான உள்ளமைவு நூலகம். `async def` மூலம் அறிவிக்கப்பட்ட செயல்பாடுகள் `await`-இல் இடைநிறுத்தப்படலாம்; ஒரு பணி பிணைய பதிலுக்காகக் காத்திருக்கும்போது நிகழ்வுச் சுழற்சி மற்றவற்றை இயக்கும். அதனால் நூற்றுக்கணக்கான கோரிக்கைகள் ஒரே இழையில் ஒரே நேரத்தில் செல்கின்றன. ஆனால் asyncio மூன்று கருவிகளில் ஒன்று மட்டுமே: இன்னும் இழைகள் (`threading`) மற்றும் செயல்முறைகள் (`multiprocessing`) உண்டு. asyncio python எப்படி வேலை செய்கிறது, உங்கள் பணிக்கு எதைத் தேர்ந்தெடுப்பது, GIL-க்கு இதில் என்ன பங்கு என்று பார்ப்போம்.

## காத்திருப்பதும் கணக்கிடுவதும் வேறு வேலைகள்

இந்த வேறுபாடு இல்லாமல் மேலே எதையும் புரிந்துகொள்ள முடியாது. நிரல் இரண்டு காரணங்களில் ஒன்றால் மெதுவாகிறது:

- **I/O-bound** — உள்ளீடு-வெளியீட்டுக்காகக் காத்திருக்கிறது: API பதில், தரவுத்தளம், வட்டிலிருந்து படித்தல். அந்நேரம் செயலி சும்மா இருக்கும்.
- **CPU-bound** — கணக்கிடுகிறது: படங்களைச் செயலாக்குவது, மில்லியன் கணக்கான பதிவுகளின் மீது சுழல்கள். செயலி முழு வேகத்தில் உழைக்கும்.

காத்திருப்புக்கு asyncio அல்லது இழைகள் பொருந்தும்: ஒரு செயல்பாடு காத்திருக்கும்போது மற்றொன்று ஓடும். கணக்கீடுகளுக்கு பல கோர்களில் உண்மையான இணைகணக்கீடு தேவை — அதற்கு செயல்முறைகள். இவற்றைக் குழப்புவதே முக்கியப் பிழை: சுத்தமான Python-இல் இழைகள் கணக்கீட்டை வேகப்படுத்தாது; மூன்று HTTP கோரிக்கைகளுக்கு செயல்முறைகளை எடுப்பது சிட்டுக்குருவியை சுட பீரங்கியைப் பயன்படுத்துவது.

## Asyncio என்றால் என்ன: coroutine-களும் நிகழ்வுச் சுழற்சியும்

ஒத்திசைவற்ற குறியீடு இரண்டு சொற்களின் மேல் கட்டப்பட்டுள்ளது. `async def` ஒரு coroutine-ஐ அறிவிக்கிறது — இடைநிறுத்தக்கூடிய செயல்பாடு. அதை அழைப்பது உடலை இயக்காது; coroutine பொருளை உருவாக்குகிறது. உள்ளே உள்ள `await` சொல்கிறது: "இங்கே நான் காத்திருப்பேன், நீ மற்ற பணிகளைப் பார்". அனைத்தையும் நிகழ்வுச் சுழற்சி (event loop) நிர்வகிக்கிறது; அது `asyncio.run()` மூலம் தொடங்கப்படுகிறது.

```
import asyncio

async def fetch_rate(currency):
    await asyncio.sleep(1)          # API கோரிக்கையின் போலி வடிவம்
    return {"USD": 92.5, "EUR": 100.1, "CNY": 12.7}[currency]

async def main():
    rate = await fetch_rate("USD")
    print(rate)

asyncio.run(main())  # 92.5
```

`asyncio.sleep()` ஒத்திசைவற்ற இடைவேளை: அது கட்டுப்பாட்டை நிகழ்வுச் சுழற்சிக்குத் தருகிறது. Coroutine-க்குள் சாதாரண `time.sleep()` முழு சுழற்சியையும் தடுக்கும்; மற்ற பணிகள் நின்றுவிடும்.

### ஒன்றன் பின் ஒன்றா, ஒரே நேரத்திலா

சுழலில் `await` எழுதினால் கோரிக்கைகள் ஒவ்வொன்றாகச் செல்லும் — மூன்று நாணயங்களுக்கு மூன்று வினாடிகள் ஆகும். அவை ஒரே நேரத்தில் செல்ல வேண்டுமென்றால் பணிகளாகத் தொடங்க வேண்டும்.

```
import asyncio, time

async def main():
    start = time.perf_counter()
    for cur in ("USD", "EUR", "CNY"):
        await fetch_rate(cur)                       # ஒவ்வொன்றாக
    print(f"{time.perf_counter() - start:.1f} வி")   # 3.0 வி

asyncio.run(main())
```

## TaskGroup: ஒன்றாகத் தொடங்கி எல்லாவற்றுக்காகவும் காத்திருப்பது

நவீன வழி (Python 3.11+) — பணிக் குழு. `async with` தொகுதிக்குள் ஒவ்வொரு பணியும் `tg.create_task()` மூலம் உடனே தொடங்கும்; எல்லாம் முடிந்ததும்தான் தொகுதியிலிருந்து வெளியேறுவோம்.

```
async def main():
    start = time.perf_counter()
    async with asyncio.TaskGroup() as tg:
        tasks = {cur: tg.create_task(fetch_rate(cur)) for cur in ("USD", "EUR", "CNY")}
    rates = {cur: t.result() for cur, t in tasks.items()}
    print(rates, f"{time.perf_counter() - start:.1f} வி")   # ... 1.0 வி

asyncio.run(main())
```

TaskGroup கண்டிப்பாக வேலை செய்கிறது: ஒரு பணி விழுந்தால் மற்றவை ரத்து செய்யப்படும், எல்லாப் பிழைகளும் `ExceptionGroup`-ஆகச் சேகரிக்கப்படும். அவற்றை `except*` மூலம் பிடிக்கிறோம்:

```
async def main():
    try:
        async with asyncio.TaskGroup() as tg:
            tg.create_task(fetch_rate("USD"))
            tg.create_task(fetch_rate("XXX"))   # KeyError
    except* KeyError as eg:
        print("தெரியாத நாணயம்:", eg.exceptions)
```

### asyncio.gather மற்றும் create_task

TaskGroup-க்கு முன் நிலையான வழி `asyncio.gather()` ஆக இருந்தது: அது coroutine-களை ஒரே நேரத்தில் இயக்கி, முடிவுகளை அதே வரிசையில் திருப்பும். இன்றும் ஏற்கனவே உள்ள குறியீட்டில் அது நிறைய உள்ளது.

```
async def main():
    usd, eur, cny = await asyncio.gather(
        fetch_rate("USD"), fetch_rate("EUR"), fetch_rate("CNY")
    )
    # return_exceptions=True எனில் பிழைகள் பட்டியலில் திரும்பும், எல்லாவற்றையும் நிறுத்தாது
```

`asyncio.create_task()` ஒரு coroutine-ஐ குழு இல்லாமல் பின்னணியில் தொடங்குகிறது. பணிக்கான குறிப்பைச் சேமிக்க வேண்டும்; இல்லையென்றால் குப்பை சேகரிப்பான் அதை நீக்கிவிடலாம், பிழைகளையும் யாரும் பார்க்க மாட்டார்கள். அதனால் புதிய திட்டங்களுக்கு TaskGroup நம்பகமானது.

### பணிகளை ரத்து செய்தல் மற்றும் நேர வரம்புகள்

```
async def main():
    task = asyncio.create_task(fetch_rate("USD"))
    task.cancel()                     # பணிக்குள் CancelledError எழும்
    try:
        await task
    except asyncio.CancelledError:
        print("ரத்து செய்யப்பட்டது")

    try:
        async with asyncio.timeout(0.5):   # Python 3.11+
            await fetch_rate("EUR")
    except TimeoutError:
        print("மிக நேரம் ஆனது")
```

பழைய பதிப்புகளில் `asyncio.timeout()`-க்குப் பதிலாக `asyncio.wait_for(coro, timeout=0.5)` பயன்படுத்தப்படுகிறது. இன்னும் `asyncio.wait()` உண்டு — அது முடிந்த மற்றும் முடியாத பணிகளின் தொகுப்புகளைத் தரும்; முதலில் முடிவதற்காகக் காத்திருக்கவும் முடியும் (`return_when=asyncio.FIRST_COMPLETED`).

பாடநெறியின்படி Python 3.15-இல் TaskGroup-க்கு `tg.cancel()` முறை வந்தது: அது குழுவின் எல்லாப் பணிகளையும் விதிவிலக்கின்றி ரத்து செய்கிறது. இது "யார் முதலில்" சூழ்நிலையைத் தீர்க்கிறது: மூன்று மாற்று சேவைகளிடம் கேட்டு, முதல் பதிலை எடுத்து, மற்றவற்றை ரத்து செய்வது. முன்பு இதற்கு தனிச் சமிக்ஞை விதிவிலக்கை எழுப்ப வேண்டியிருந்தது அல்லது பணிகளை கைமுறையாக ரத்து செய்ய வேண்டியிருந்தது.

## ஒத்திசைவு: Semaphore, Lock, Event, Queue

asyncio-க்கு தனியான கருவிகள் உள்ளன; அவை `await`-உடன் வேலை செய்கின்றன. மிகப் பயனுள்ளது `Semaphore`: ஒரேநேர செயல்பாடுகளின் எண்ணிக்கையைக் கட்டுப்படுத்துகிறது — பிறர் API-ஐ ஆயிரம் கோரிக்கைகளால் வீழ்த்தாமல் இருக்க.

```
sem = asyncio.Semaphore(10)

async def limited(url):
    async with sem:              # ஒரே நேரத்தில் 10-க்கு மேல் இல்லை
        await asyncio.sleep(0.1) # இங்கே கோரிக்கை இருக்கும்
        return url
```

- `asyncio.Lock` — முக்கியப் பகுதிக்குள் ஒரே ஒரு coroutine மட்டும்.
- `asyncio.Event` — ஒரு coroutine `await event.wait()`-க்காகக் காத்திருக்கும், மற்றொன்று `event.set()` மூலம் சமிக்ஞை செய்யும்.
- `asyncio.Queue` — தயாரிப்பாளர்–நுகர்வோர் வரிசை: `await q.put()`, `await q.get()`, `q.task_done()`.

## பொறி: await மறந்துவிட்டது

மிகவும் பொதுவான தவறு — `await` இல்லாமல் coroutine-ஐ அழைப்பது:

```
async def main():
    rate = fetch_rate("USD")    # await மறந்தது
    print(rate * 2)             # TypeError
# RuntimeWarning: coroutine 'fetch_rate' was never awaited
```

Python செயல்பாட்டை இயக்கவில்லை, coroutine பொருளை மட்டுமே உருவாக்கியது. பதிவுகளில் "was never awaited" தோன்றினால் விடுபட்ட `await`-ஐத் தேடுங்கள். இரண்டாவது பொதுவான பிழை — coroutine-க்குள் தடுக்கும் அழைப்பு (`time.sleep`, `requests.get`). HTTP-க்கு ஒத்திசைவற்ற நூலகங்களை (httpx, aiohttp) எடுங்கள்; தவிர்க்க முடியாத ஒத்திசைவான குறியீட்டை `await asyncio.to_thread(func, arg)` மூலம் ஓர் இழைக்கு அனுப்புங்கள்.

## Python-இல் பல இழைகள்: threading தொகுதி

இழை (thread) — ஒரே செயல்முறைக்குள் சுயாதீனமான இயக்கக் கோடு; இழைகள் பொதுவான நினைவகத்தைப் பகிர்கின்றன. பிணைய காத்திருப்புக்கு அவை சிறப்பாக வேலை செய்கின்றன; குறியீட்டை async-ஆக மாற்றவும் தேவையில்லை.

```
import threading, time

def download(name):
    time.sleep(1)               # பிணைய காத்திருப்பு
    print(name, "தயார்")

threads = [threading.Thread(target=download, args=(n,)) for n in ("a", "b", "c")]
for t in threads:
    t.start()
for t in threads:
    t.join()                    # எல்லாம் சுமார் 1 வினாடியில்
```

பொதுவான நினைவகம் நன்மையும் ஆதாரப் பிழைகளின் மூலமுமாகும்: இரண்டு இழைகள் ஒரே மாறியை மாற்றினால் `threading.Lock` தேவை. பொதுவாக இழைகளை கைமுறையாகக் கையாளாமல், ஒரு குளத்தை (pool) எடுப்பது எளிது.

### ThreadPoolExecutor

`concurrent.futures.ThreadPoolExecutor` இழைகளின் குளத்தை உருவாக்கி அவற்றுக்குப் பணிகளை வழங்குகிறது. `map` முடிவுகளை மூல வரிசையில் திருப்பும்.

```
from concurrent.futures import ThreadPoolExecutor
import time

def fetch(url):
    time.sleep(1)
    return f"{url}: 200"

urls = [f"https://example.com/{i}" for i in range(5)]
with ThreadPoolExecutor(max_workers=5) as pool:
    for result in pool.map(fetch, urls):
        print(result)           # எல்லாம் சுமார் 1 வினாடியில்
```

## GIL என்றால் என்ன, இழைகள் ஏன் கணக்கீட்டை வேகப்படுத்துவதில்லை

GIL (Global Interpreter Lock) — CPython மொழிபெயர்ப்பியின் பொதுப் பூட்டு. எந்த நேரத்திலும் Python பைட்குறியீட்டை ஒரே ஒரு இழை மட்டுமே இயக்குகிறது. ஒரு இழை உள்ளீடு-வெளியீட்டுக்காகக் காத்திருக்கும்போது அது GIL-ஐ விடுவிக்கும்; அதனால் பிணையத்துக்கு இழைகள் பயனுள்ளவை. ஆனால் சுத்தக் கணக்கீட்டில் நான்கு இழைகளும் ஒரு இழையைவிட வேகமாக ஓடாது: அவை பூட்டை முறைக்கு ஒன்றாக கைமாற்றிக் கொள்கின்றன.

```
from concurrent.futures import ThreadPoolExecutor

def heavy(n):
    return sum(i * i for i in range(n))

with ThreadPoolExecutor(4) as pool:
    list(pool.map(heavy, [5_000_000] * 4))   # ஏறக்குறைய வரிசையாக ஓடுவது போலவே
```

C-இல் எழுதப்பட்ட நூலகங்கள் (NumPy போன்றவை) கனமான செயல்பாடுகளின்போது GIL-ஐ பெரும்பாலும் விடுவிக்கும்; அதனால் அவற்றுக்கு இழைகள் வேகம் தருகின்றன.

## multiprocessing: உண்மையான இணைச் செயல்பாடு

ஒவ்வொரு செயல்முறைக்கும் தனி மொழிபெயர்ப்பி மற்றும் தனி GIL உண்டு; அதனால் செயல்முறைகள் வெவ்வேறு கோர்களில் ஒரே நேரத்தில் கணக்கிடுகின்றன. விலை — தனி நினைவகம்: செயல்முறைகளுக்கிடையே தரவு தொடராக்கம் (pickle) மூலம் அனுப்பப்படுகிறது, செயல்முறை தொடங்குவது இழையைவிடச் செலவானது.

```
from concurrent.futures import ProcessPoolExecutor

def heavy(n):
    return sum(i * i for i in range(n))

if __name__ == "__main__":      # Windows மற்றும் macOS-இல் கட்டாயம்
    with ProcessPoolExecutor() as pool:
        print(list(pool.map(heavy, [5_000_000] * 4)))
```

`if __name__ == "__main__"` சோதனை இல்லாவிட்டால் குழந்தை செயல்முறைகள் தொகுதியை இறக்குமதி செய்யும்போது புதிய செயல்முறைகளைத் தொடங்கத் தொடங்கும். `Process`, `Pool`, `Queue` போன்றவற்றுடன் கீழ்நிலை `multiprocessing` தொகுதியும் உண்டு; ஆனால் `ProcessPoolExecutor` எளிதானது, இழைக் குளத்தின் அதே இடைமுகத்தைக் கொண்டது.

## எதைத் தேர்ந்தெடுப்பது: asyncio, threading அல்லது multiprocessing

| பணி | கருவி | ஏன் |
| --- | --- | --- |

| 200 பக்கங்களைப் பதிவிறக்குவது, பல API கோரிக்கைகள் | asyncio (httpx/aiohttp) அல்லது ThreadPoolExecutor | பிணைய காத்திருப்பு, செயலி சும்மா |

| ஏற்கனவே ஒத்திசைவற்ற நிரலுக்குள் பல கோரிக்கைகள் | TaskGroup | குறியீடு ஏற்கனவே async |

| ஒத்திசைவற்ற பதிப்பு இல்லாத ஒத்திசைவான நூலகம் | இழைகள், `asyncio.to_thread` | குறியீட்டை மீண்டும் எழுத வேண்டியதில்லை |

| சுத்த Python-இல் கனமான கணக்கீடு | ProcessPoolExecutor | GIL-ஐத் தவிர்க்கும், எல்லாக் கோர்களும் |

## GIL இல்லாத Python மற்றும் துணை மொழிபெயர்ப்பிகள்: புதியவை

Python 3.13 முதல் GIL இல்லாத தனி உருவாக்கம் உண்டு (free-threading, PEP 703) — அதன் பெயரில் t எழுத்து இருக்கும், உதாரணமாக `python3.15t`. 3.13-இல் அது சோதனை நிலையில் இருந்தது; 3.14-இலிருந்து (PEP 779) அது அதிகாரப்பூர்வமாக ஆதரிக்கப்படுகிறது. அதில் இழைகள் பல கோர்களில் ஒரே நேரத்தில் கணக்கிடுகின்றன. ஆனால் இயல்பாக நீங்கள் இன்னும் GIL கொண்ட சாதாரண Python-ஐயே பெறுவீர்கள்: ஒரு இழை குறியீடு free-threaded உருவாக்கத்தில் சற்று மெதுவாக ஓடும், எல்லா நூலகங்களும் அதற்குத் தயாராக இல்லை.

```
import sys
print(sys._is_gil_enabled())   # free-threaded உருவாக்கத்தில் False
```

இணைக் கணக்கீட்டுக்கான இரண்டாவது வழி 3.14-இல் வந்தது (PEP 734) — `concurrent.interpreters` தொகுதியின் துணை மொழிபெயர்ப்பிகள்: ஒரே செயல்முறைக்குள் தனிமைப்படுத்தப்பட்ட பல மொழிபெயர்ப்பிகள், ஒவ்வொன்றுக்கும் சொந்த GIL. `InterpreterPoolExecutor` மூலம் அவற்றைப் பயன்படுத்துவது எளிது; அது இழைக் குளத்தைப் போலவே இருக்கும். பாடநெறியின் அளவீட்டில் நான்கு கனமான பணிகள் 0.39 வி எடுத்தன, ஒரே இழையில் 1.03 வி-க்கு எதிராக.

## சுருக்கம்

Asyncio python காத்திருப்புப் பணிகளுக்கான கருவி: `async def`-இல் coroutine-கள், `await`-இல் இடைநிறுத்தங்கள், TaskGroup அல்லது `gather` மூலம் ஒரே நேரத்தில் தொடக்கம். குறியீட்டை async-ஆக மாற்றாமல் இழைகள் அதே வேலையைச் செய்யும்; கனமான கணக்கீடுகளுக்கு செயல்முறைகள் தேவை, ஏனெனில் சாதாரண CPython-இல் GIL இழைகளை ஒரே நேரத்தில் கணக்கிட விடுவதில்லை. Free-threading மற்றும் துணை மொழிபெயர்ப்பிகள் இந்தப் படத்தை மெல்லமாக மாற்றுகின்றன, ஆனால் அடிப்படை விதி — "காத்திருந்தால் asyncio அல்லது இழைகள்; கணக்கிட்டால் செயல்முறைகள்" — இன்னும் பொருந்துகிறது. பாடநெறியில் இவை அனைத்தும் நடைமுறையில் விளக்கப்பட்டுள்ளன: செலவு கண்காணிப்பான் நாணய விகிதங்களை ஒரே நேரத்தில் ஒரு வினாடியில் பெறுகிறது, மூன்று வினாடிகளுக்குப் பதிலாக.

## FAQ

**ஒரு சாதாரண குறியீட்டிலிருந்து async செயல்பாட்டை அழைக்க முடியுமா?**

ஆம், asyncio.run(coro()) மூலம்: அது நிகழ்வுச் சுழற்சியை உருவாக்கி, coroutine-ஐ இயக்கி, சுழற்சியை மூடும். ஏற்கனவே இயங்கும் நிகழ்வுச் சுழற்சிக்குள் asyncio.run-ஐ அழைக்க முடியாது; அங்கே await பயன்படுத்துங்கள்.

**Asyncio பல இழைகளில் வேலை செய்கிறதா?**

இல்லை, நிகழ்வுச் சுழற்சி எல்லா coroutine-களையும் ஒரே இழையில் இயக்குகிறது; await புள்ளிகளில் மட்டுமே மாறுகிறது. அதனால் இழைகளை விட தரவு இனங்கள் குறைவு, ஆனால் ஒரு தடுக்கும் செயல்பாடு எல்லாவற்றையும் நிறுத்தும்.

**asyncio-ஐ pip மூலம் நிறுவ வேண்டுமா?**

இல்லை, asyncio Python 3-இன் நிலையான நூலகத்தில் உள்ளது; import asyncio எழுதினால் போதும். PyPI-இல் உள்ள asyncio தொகுப்பு காலாவதியானது, அதை நிறுவ வேண்டியதில்லை.

**தடுக்காமல் கனமான செயல்பாட்டை asyncio-இலிருந்து எப்படி இயக்குவது?**

கணக்கீடுகளுக்கு ProcessPoolExecutor உடன் loop.run_in_executor, தடுக்கும் உள்ளீடு-வெளியீட்டுக்கு asyncio.to_thread; நிகழ்வுச் சுழற்சி மற்ற பணிகளைத் தொடர்ந்து கவனிக்கும்.

**coroutine-க்கும் Task-க்கும் என்ன வேறுபாடு?**

Coroutine என்பது await மூலம் காத்திருக்கப்படும் வரை எதுவும் செய்யாத பொருள். Task coroutine-ஐச் சுற்றி, உடனே நிகழ்வுச் சுழற்சியின் அட்டவணையில் வைக்கிறது; அதனால் அது மற்ற குறியீட்டுடன் இணையாக இயங்கும்.


---
Source: Skillaria.Top (https://skillaria.top)
