Python Async বনাম Threads: কখন asyncio ব্যবহার করবেন
Python-এ Concurrency কেন গুরুত্বপূর্ণ
আপনার একটি Python স্ক্রিপ্ট আছে যা একাধিক API থেকে ডেটা আনে। এটি কাজ করে, কিন্তু ধীর—প্রতিটি রিকোয়েস্ট আগেরটি শেষ হওয়ার জন্য অপেক্ষা করে। আপনি asyncio এবং threads সম্পর্কে শুনেছেন, কিন্তু কোনটি ব্যবহার করবেন? ভুল মডেল বেছে নিলে কোড হয় অপ্রয়োজনীয়ভাবে জটিল বা বেদনাদায়কভাবে ধীর হয়ে যেতে পারে।
এই গাইডটি বিভ্রান্তি দূর করবে। আমরা Python-এর async এবং threading মডেল তুলনা করব, দেখাবো ঠিক কখন কোনটি ব্যবহার করবেন, এবং ব্যবহারিক উদাহরণ দেব যা আপনি আপনার নিজের প্রকল্পে প্রয়োগ করতে পারেন।
Python-এর Concurrency ল্যান্ডস্কেপ বোঝা
Python তিনটি প্রধান concurrency মডেল অফার করে:
- Threads – একটি প্রসেসের মধ্যে একাধিক থ্রেড, OS দ্বারা পরিচালিত। CPU-বাউন্ড কাজের জন্য Global Interpreter Lock (GIL) দ্বারা সীমাবদ্ধ।
- Async (asyncio) – একক-থ্রেডেড ইভেন্ট লুপ যা
awaitপয়েন্টে কাজের মধ্যে সুইচ করে। I/O-বাউন্ড অপারেশনের জন্য চমৎকার। - Multiprocessing – আলাদা প্রসেস, প্রতিটির নিজস্ব Python ইন্টারপ্রেটার এবং মেমরি স্পেস। সত্যিকারের parallelism-এর জন্য GIL বাইপাস করে।
GIL হল একটি mutex যা একাধিক থ্রেডকে একসাথে Python বাইটকোড চালানো থেকে বিরত রাখে। এর মানে হল থ্রেড CPU-বাউন্ড কাজ দ্রুত করে না, কিন্তু I/O-বাউন্ড কাজে সাহায্য করে কারণ I/O অপারেশনের সময় GIL ছেড়ে দেওয়া হয়।
কখন asyncio ব্যবহার করবেন
asyncio তখন দুর্দান্ত যখন আপনার প্রোগ্রাম বেশিরভাগ সময় বাইরের ইভেন্টের জন্য অপেক্ষা করে: নেটওয়ার্ক রিকোয়েস্ট, ফাইল I/O, ডেটাবেস কোয়েরি, বা টাইমার। যেহেতু এটি একক থ্রেড ব্যবহার করে, আপনি থ্রেড তৈরি এবং কনটেক্সট সুইচিংয়ের ওভারহেড এড়াতে পারেন। ইভেন্ট লুপ হাজার হাজার concurrent সংযোগ দক্ষতার সাথে পরিচালনা করে।
asyncio ব্যবহার করুন যখন:
- আপনি একটি ওয়েব সার্ভার বা ক্লায়েন্ট তৈরি করছেন যা অনেক একসাথে সংযোগ পরিচালনা করে (যেমন FastAPI, aiohttp)।
- আপনার কাজগুলি I/O-বাউন্ড এবং আপনার উচ্চ concurrency প্রয়োজন (শত বা হাজার হাজার কাজ)।
- আপনি টাস্ক শিডিউলিং এবং বাতিলকরণের উপর সূক্ষ্ম নিয়ন্ত্রণ চান।
- আপনি ইতিমধ্যে একটি async ফ্রেমওয়ার্ক বা লাইব্রেরি ব্যবহার করছেন।
এখানে একটি সাধারণ উদাহরণ যা একসাথে একাধিক URL আনে:
import asyncio
import aiohttp
async def fetch(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.text()
async def main():
urls = [
'https://example.com',
'https://example.org',
'https://example.net',
]
tasks = [fetch(url) for url in urls]
results = await asyncio.gather(*tasks)
print(f'Fetched {len(results)} pages')
asyncio.run(main())
কখন Threads ব্যবহার করবেন
Threads তখন ভালো যখন আপনার blocking I/O অপারেশন থাকে যার async সমতুল্য নেই, বা যখন আপনি legacy কোড নিয়ে কাজ করছেন যা async-বান্ধব নয়। এছাড়াও অল্প সংখ্যক কাজের জন্য এগুলি বোঝা সহজ।
Threads ব্যবহার করুন যখন:
- আপনাকে blocking I/O সমান্তরালে চালাতে হবে, যেমন একাধিক ফাইল পড়া বা synchronous API কল করা।
- আপনি এমন একটি লাইব্রেরির সাথে সংহত করছেন যা async সমর্থন করে না।
- আপনার মাঝারি সংখ্যক concurrent কাজ আছে (দশ, হাজার নয়)।
- আপনি আপনার কোড synchronous রাখতে চান কিন্তু I/O ওভারল্যাপ করতে চান।
concurrent.futures.ThreadPoolExecutor ব্যবহার করে উদাহরণ:
from concurrent.futures import ThreadPoolExecutor
import requests
def fetch(url):
response = requests.get(url)
return response.text
urls = [
'https://example.com',
'https://example.org',
'https://example.net',
]
with ThreadPoolExecutor(max_workers=10) as executor:
results = list(executor.map(fetch, urls))
print(f'Fetched {len(results)} pages')
asyncio এবং Threads তুলনা
সিদ্ধান্ত নিতে সাহায্য করার জন্য এখানে একটি পাশাপাশি তুলনা:
| দিক | asyncio | Threads |
|---|---|---|
| Concurrency মডেল | একক-থ্রেডেড ইভেন্ট লুপ | একাধিক OS থ্রেড |
| সেরা জন্য | I/O-বাউন্ড, উচ্চ concurrency | I/O-বাউন্ড, blocking কল |
| CPU-বাউন্ড পারফরম্যান্স | দুর্বল (GIL) | দুর্বল (GIL) |
| স্কেলেবিলিটি | হাজার হাজার কাজ | শত শত থ্রেড |
| জটিলতা | async/await সিনট্যাক্স প্রয়োজন | পরিচিত synchronous স্টাইল |
| ডিবাগিং | ট্রেসব্যাক সহ কঠিন হতে পারে | স্ট্যান্ডার্ড ডিবাগিং টুল |
| ইকোসিস্টেম | ক্রমবর্ধমান async সমর্থন | সর্বজনীন |
CPU-বাউন্ড কাজ সম্পর্কে কী?
GIL-এর কারণে asyncio বা threads কোনটিই Python-এ CPU-নিবিড় কাজ সমান্তরাল করতে সাহায্য করবে না। CPU-বাউন্ড কাজের জন্য—যেমন সংখ্যা গণনা, ইমেজ প্রসেসিং, বা ডেটা কম্প্রেশন—multiprocessing বা concurrent.futures.ProcessPoolExecutor ব্যবহার করুন।
উদাহরণ:
from concurrent.futures import ProcessPoolExecutor
def cpu_heavy(n):
return sum(i * i for i in range(n))
if __name__ == '__main__':
with ProcessPoolExecutor() as executor:
results = list(executor.map(cpu_heavy, [10**6, 10**6, 10**6]))
print(results)
সেরা অনুশীলন এবং সমস্যা
- asyncio-এর সাথে blocking কল মিশ্রিত করবেন না। যদি আপনি একটি async কাজের ভিতরে একটি blocking ফাংশন কল করেন, এটি সম্পূর্ণ ইভেন্ট লুপ ব্লক করবে। থ্রেড পুলে blocking কোড চালানোর জন্য
asyncio.to_thread()ব্যবহার করুন। - থ্রেড পুলের আকার সীমিত করুন। অতিরিক্ত থ্রেড তৈরি করলে কনটেক্সট-সুইচিং ওভারহেড হয়।
ThreadPoolExecutor(max_workers=N)ব্যবহার করুন। - ব্যতিক্রম সঠিকভাবে পরিচালনা করুন। asyncio-তে, কাজের মধ্যে unhandled ব্যতিক্রম অলক্ষিত থাকতে পারে।
asyncio.gather(..., return_exceptions=True)ব্যবহার করুন বা কলব্যাক যোগ করুন। - নতুন I/O-ভারী প্রকল্পের জন্য asyncio পছন্দ করুন। ইকোসিস্টেম পরিপক্ক, এবং FastAPI এবং aiohttp-এর মতো ফ্রেমওয়ার্ক এটি সহজ করে তোলে।
- legacy বা সাধারণ স্ক্রিপ্টের জন্য threads ব্যবহার করুন। যদি আপনার কয়েকটি blocking কল সমান্তরাল করতে হয়, threads কম আক্রমণাত্মক।
সচরাচর জিজ্ঞাসা
আমি কি asyncio এবং threads একসাথে ব্যবহার করতে পারি?
হ্যাঁ। আপনি asyncio.to_thread() বা loop.run_in_executor() ব্যবহার করে থ্রেড পুলে blocking কোড চালাতে পারেন। এটি উপকারী যখন আপনি async কোড থেকে একটি synchronous লাইব্রেরি কল করতে চান ইভেন্ট লুপ ব্লক না করে।
asyncio কি GIL-এর সাথে কাজ করে?
হ্যাঁ, asyncio একটি একক থ্রেডে চলে এবং GIL-এর অধীন। তবে, যেহেতু এটি I/O-বাউন্ড কাজের জন্য ডিজাইন করা হয়েছে, I/O অপারেশনের সময় GIL ছেড়ে দেওয়া হয়, ফলে অন্য কাজগুলি চলতে পারে। CPU-বাউন্ড কাজের জন্য, asyncio কোন গতি বৃদ্ধি দেয় না।
কোনটি দ্রুত: asyncio নাকি threads?
অনেক concurrent অপারেশন সহ I/O-বাউন্ড কাজের জন্য, asyncio সাধারণত দ্রুত এবং আরও স্কেলেবল কারণ এটি থ্রেড ওভারহেড এড়ায়। অল্প সংখ্যক blocking কাজের জন্য, threads সহজ হতে পারে এবং একইভাবে পারফর্ম করে। কোনটিই CPU-বাউন্ড কাজে সাহায্য করে না।
সঠিক পছন্দ করা
আপনার বাধা I/O না CPU তা চিহ্নিত করে শুরু করুন। যদি এটি I/O হয় এবং আপনার উচ্চ concurrency প্রয়োজন, asyncio ব্যবহার করুন। যদি এটি I/O হয় কিন্তু আপনি blocking লাইব্রেরি বা সাধারণ স্ক্রিপ্ট নিয়ে কাজ করছেন, threads একটি শক্ত পছন্দ। CPU-বাউন্ড কাজের জন্য, multiprocessing ব্যবহার করুন।
মনে রাখবেন যে আপনি প্রায়শই এই মডেলগুলি একত্রিত করতে পারেন—উদাহরণস্বরূপ, নেটওয়ার্ক অপারেশনের জন্য asyncio এবং ফাইল I/O-এর জন্য একটি থ্রেড পুল ব্যবহার করা। মূল বিষয় হল ট্রেড-অফগুলি বোঝা এবং আপনার নির্দিষ্ট সমস্যার সাথে মানানসই টুল বেছে নেওয়া।
যখন আপনার concurrent API কল থেকে ফেরত JSON ডেটা দ্রুত ফরম্যাট বা যাচাই করতে হবে, আমাদের JSON Formatter চেষ্টা করুন সহজে pretty-print এবং ডিবাগ করার জন্য।