Python-এ একসাথে একটার বেশি কাজ করা বেশিরভাগ অন্য ভাষায় নেই এমন একটা নির্দিষ্ট detail দিয়ে আকৃতি পায়: GIL।
GIL একবারে শুধু একটা thread-কে Python bytecode execute করতে দেয়, একটা multi-core machine-এও — thread জুড়ে ভাগ করা CPU-ভারী কাজের জন্য একটা আসল সীমাবদ্ধতা, কিন্তু বেশিরভাগ সময় অপেক্ষা করা (একটা network request, একটা file read) কাজের জন্য অনেক কম, কারণ সেই অপেক্ষার সময় GIL ছেড়ে দেওয়া হয়।
import threading
import time
def download(name):
print(f'Starting {name}')
time.sleep(2) # একটা ধীর network request-এর জায়গায়
print(f'Finished {name}')
threads = [threading.Thread(target=download, args=(f'file{i}',)) for i in range(3)]
for t in threads:
t.start()
for t in threads:
t.join()
# তিনটা "download"-ই একসাথে চলে — মোট সময় ~2s, ~6s নাthreading যা ঠিক করে না
GIL-এর কারণে, বেশি thread যোগ করা CPU-ভারী computation (ভারী math, image processing) দ্রুত করে না — শুধু সত্যিকারভাবে I/O-bound অপেক্ষা উপকৃত হয়। multiprocessing (আলাদা process, প্রতিটার নিজের GIL) CPU-ভারী parallelism-এর জন্য সঠিক tool, এই lesson-এর বাইরের একটা topic।
asyncio একটা একক thread-এ অনেক অপেক্ষার operation handle করে, একটা অপেক্ষা করার সময় এগুলোর মধ্যে switch করে — একদম কোনো thread ছাড়াই, JavaScript কোর্স থেকে event loop model-এর মতো ভাবনায়।
import asyncio
async def download(name):
print(f'Starting {name}')
await asyncio.sleep(2)
print(f'Finished {name}')
async def main():
await asyncio.gather(
download('file1'),
download('file2'),
download('file3'),
)
asyncio.run(main())
# এটাও মোট ~2s, কোনো thread জড়িত নাasync def একটা function-কে একটা coroutine হিসেবে চিহ্নিত করে — এটা একটা await-এ থামানো যায় আর পরে resume করা যায়, অন্য কিছু block না করে। asyncio.gather() বেশ কয়েকটা coroutine একসাথে চালায় আর সবগুলোর জন্য অপেক্ষা করে।
| threading | asyncio |
|---|---|
| async মাথায় রেখে কখনো লেখা হয়নি এমন library-র সাথে কাজ করে | specifically এর জন্য বানানো library দরকার (একটা async database driver, requests-এর বদলে aiohttp) |
| আসল OS thread, প্রতি thread-এ কিছু overhead | হালকা — হাজার হাজার coroutine practical, thread না |
| হাতেগোনা কয়েকটা concurrent task-এর জন্য reason করা সহজ | অনেক concurrent connection handle করা একটা server-এর জন্য standard modern পছন্দ |