- পাইথনে অ্যাসিঙ্ক্রোনাস প্রোগ্রামিং একাধিক I/O-নির্ভর টাস্ককে একে অপরকে ব্লক না করে অগ্রসর হতে দেয়।
async,awaitএবং ইভেন্ট লুপ। - এর মতো টুল ব্যবহার করা
asyncio,aiohttpঅ্যাসিঙ্ক কনটেক্সট ম্যানেজার এবং অ্যাসিঙ্ক ইটারেশন স্কেলেবল নেটওয়ার্কিং এবং এপিআই-নির্ভর ওয়ার্কলোড সক্ষম করে। - নেটওয়ার্ক এবং ফাইল আই/ও-এর জন্য অ্যাসিঙ্ক চমৎকার, কিন্তু সিপিইউ-নির্ভর কাজগুলোর জন্য এর সাথে মাল্টিপ্রসেসিং বা বিশেষায়িত পরিষেবা ব্যবহার করা উচিত।
- নির্ভরযোগ্য অ্যাসিঙ্ক অ্যাপ্লিকেশন লেখার জন্য ভালো অভ্যাসগুলো—যেমন ব্লকিং কল এড়িয়ে চলা, কনকারেন্সি সীমিত রাখা এবং প্রতিটি টাস্ক অনুযায়ী ত্রুটি সামলানো—খুবই গুরুত্বপূর্ণ।
পাইথনে অ্যাসিঙ্ক্রোনাস প্রোগ্রামিং একসময় একটি বিশেষায়িত বিষয় থাকলেও, বর্তমানে এটি আধুনিক ও রেসপন্সিভ অ্যাপ্লিকেশন নির্মাতাদের জন্য অন্যতম প্রধান দক্ষতায় পরিণত হয়েছে। আপনি যদি ওয়েব এপিআই, মাইক্রোসার্ভিস, রিয়েল-টাইম ড্যাশবোর্ড বা যেকোনো ধরনের ভারী ইনপুট/আউটপুট (I/O) নিয়ে কাজ করে থাকেন, তাহলে সম্ভবত আপনি এমন একটি সমস্যার সম্মুখীন হয়েছেন যেখানে আপনার কোড আসল কাজ করার চেয়ে অপেক্ষা করতেই বেশি সময় ব্যয় করে। ঠিক এখানেই অ্যাসিঙ্ক টেকনিকগুলো তাদের কার্যকারিতা দেখায়।
নেটওয়ার্ক, ডিস্ক বা কোনো বাহ্যিক পরিষেবার জন্য অপেক্ষা করতে গিয়ে আপনার প্রোগ্রামকে নিষ্ক্রিয়ভাবে বসিয়ে রাখার পরিবর্তে, অ্যাসিঙ্ক্রোনাস কোড আপনাকে সেই অপেক্ষার সময়গুলোকে ওভারল্যাপ করতে এবং অ্যাপ্লিকেশনটিকে সচল রাখতে সাহায্য করে। এই নির্দেশিকায় আমরা পাইথনে async কীভাবে কাজ করে, এটি কী কী সমস্যার সমাধান করে, কখন এটি সত্যিই সহায়ক এবং কখন এটি একটি ভুল পদ্ধতি, সে সম্পর্কে গভীরভাবে আলোচনা করব এবং এর বাস্তব উদাহরণগুলো দেখব। async, await, asyncio এবং জনপ্রিয় অ্যাসিঙ্ক লাইব্রেরি যেমন aiohttp.
পাইথনে অ্যাসিঙ্ক্রোনাস প্রোগ্রামিং কী?
মূলতঃ, অ্যাসিঙ্ক্রোনাস প্রোগ্রামিং হলো আপনার কোডকে এমনভাবে গঠন করার একটি পদ্ধতি, যার মাধ্যমে একাধিক টাস্ক একে অপরকে বাধা না দিয়ে কাজ এগিয়ে নিয়ে যেতে পারে, এমনকি যখন তারা একটিমাত্র অপারেটিং সিস্টেম থ্রেড ব্যবহার করে। ক্লাসিক সিনক্রোনাস পদ্ধতিতে, একটি অপারেশন শেষ হওয়ার পরেই পরবর্তীটি শুরু হয়: একটি এপিআই (API) কল করা হয়, অপেক্ষা করা হয়, রেসপন্সটি পার্স করা হয়, এবং তারপরেই পরবর্তী ধাপে যাওয়া হয়। অ্যাসিঙ্ক কোডের সাহায্যে, আপনি বেশ কয়েকটি দীর্ঘ-চলমান অপারেশন চালু করতে পারেন এবং যখন সেগুলোর মধ্যে কোনো একটি শুধু অপেক্ষা করতে থাকে, তখন পাইথনকে সেগুলোর মধ্যে সুইচ করতে দিতে পারেন।
পাইথন বিশেষ সিনট্যাক্স এবং ইভেন্ট লুপকে কেন্দ্র করে নির্মিত একটি সহযোগী শিডিউলারের সমন্বয়ে এই মডেলটি বাস্তবায়ন করে। এই সবকিছুর দরজা খুলে দেয় এমন দুটি মূলশব্দ হলো async এবং awaitআপনি ফাংশনগুলিকে অ্যাসিঙ্ক্রোনাস হিসেবে চিহ্নিত করতে ব্যবহার করেন async defএবং তুমি তাদের ভিতরে থেমে যাও await যখনই আপনি এমন কোনো অপারেশনে পৌঁছান যা ইভেন্ট লুপে নিয়ন্ত্রণ ফিরিয়ে দিতে পারে।
An async def ফাংশনটি সরাসরি কোনো মান রিটার্ন করে না; এটি একটি কো-রুটিন অবজেক্ট রিটার্ন করে যা এমন একটি গণনাকে প্রতিনিধিত্ব করে, যাকে শিডিউল করা এবং যার জন্য অ্যাওয়েট রাখা যায়। যখন আপনি ব্যবহার await ঐ ফাংশনের ভিতরে, পাইথন বর্তমান কো-রুটিনটিকে স্থগিত করে এবং অপেক্ষারত অপারেশনটি (যেমন একটি নেটওয়ার্ক অনুরোধ) সম্পূর্ণ না হওয়া পর্যন্ত অন্যান্য অপেক্ষমান কাজগুলিকে চলতে দেয়, এবং অপারেশনটি সম্পূর্ণ হওয়ার সাথে সাথেই এক্সিকিউশন আবার শুরু হয়। await.
এটি অত্যন্ত গুরুত্বপূর্ণ: অ্যাসিঙ্ক্রোনাস পাইথন কোড সাধারণত সিঙ্গেল-থ্রেডেড হলেও, এটি কনকারেন্ট হয় এই অর্থে যে একাধিক অপারেশন ওভারল্যাপিং টাইম উইন্ডোতে অগ্রসর হয়। যখন একটি টাস্ক I/O-এর জন্য অপেক্ষা করে, তখন অন্য একটি টাস্ক সিপিইউ টাইম পায়। এই কারণেই I/O-নির্ভর কাজের জন্য অ্যাসিঙ্ক আদর্শ, কিন্তু এটি জাদুকরীভাবে সিপিইউ-নির্ভর কাজকে ত্বরান্বিত করে না।
একটি বাস্তব উপমা: দাবা প্রদর্শনী এবং অপেক্ষার সময়
কনকারেন্সি বনাম সিকোয়েনশিয়াল এক্সিকিউশন ব্যাখ্যা করার জন্য পাইথন কমিউনিটিতে ব্যবহৃত একটি ক্লাসিক উপমা এসেছে একযোগে অনুষ্ঠিত দাবা প্রদর্শনী থেকে। কল্পনা করুন একজন গ্র্যান্ডমাস্টার ২৪ জন অপেশাদারের বিরুদ্ধে খেলছেন। তিনি যুগপৎ এবং অ-যুগপৎ কৌশলের প্রতিচ্ছবি হিসেবে দুটি ভিন্ন উপায়ে এই প্রতিযোগিতাটি পরিচালনা করতে পারেন।
সিঙ্ক্রোনাস সংস্করণে, তিনি একজন প্রতিপক্ষের সাথে বসে শুরু থেকে শেষ পর্যন্ত সেই একটি খেলাই খেলেন এবং তারপর পরবর্তী টেবিলে যান। তার প্রতিটি চাল দিতে ৫ সেকেন্ড সময় লাগে, যেখানে প্রত্যেক অপেশাদার খেলোয়াড় ভাবতে প্রায় ৫৫ সেকেন্ড সময় ব্যয় করে। একটি সাধারণ খেলায় ৩০ বার চাল বিনিময় হয় (অর্থাৎ মোট ৬০টি চাল)। এর মানে হলো, প্রতিটি খেলা চলে (৫৫ + ৫) × ৩০ = ১৮০০ সেকেন্ড, যা প্রায় ৩০ মিনিট। ২৪টি খেলা নিয়ে পুরো আয়োজনটি ১২ ঘণ্টা ধরে চলে।
অ্যাসিঙ্ক্রোনাস সংস্করণে, সে ঘরের চারপাশে হেঁটে প্রতিটি বোর্ডে একটি করে চাল দেয় এবং তারপর সাথে সাথেই পরেরটিতে চলে যায়, আর এই সময়ে বর্তমান প্রতিপক্ষ তার জবাব নিয়ে ভাবতে থাকে। ২৪টি বোর্ডে এক রাউন্ড চাল দিতে ২৪ × ৫ = ১২০ সেকেন্ড বা ২ মিনিট সময় লাগে। এই ধরনের ৩০টি রাউন্ডের পর, খেলার সম্পূর্ণ সেটটি শেষ হতে প্রায় ৩৬০০ সেকেন্ড অর্থাৎ ১ ঘণ্টা সময় লাগে।
গুরুত্বপূর্ণ বিষয়টি হলো, তার খেলার মূল গতি কখনোই বদলায়নি; যা বদলেছিল তা হলো প্রতিপক্ষের অপেক্ষার সময়কে তিনি কীভাবে কাজে লাগাতেন। অ্যাসিঙ্ক্রোনাস পাইথন কোডও একই নীতি অনুসরণ করে: এটি ইনপুট/আউটপুট (I/O) দ্রুততর করে না, কিন্তু এটি নিশ্চিত করে যে আপনি এমন একটি দরকারি কাজ করছেন, যখন অন্যথায় আপনাকে নেটওয়ার্ক, ডিস্ক বা অন্য কোনো বাহ্যিক রিসোর্সের জন্য অপেক্ষা করতে গিয়ে আটকে থাকতে হতো।
সিঙ্ক বনাম অ্যাসিঙ্ক রিকোয়েস্ট: এপিআই সহ একটি বাস্তব উদাহরণ
পাইথনে async-এর অন্যতম সাধারণ ব্যবহার হলো এক্সটার্নাল এপিআই (API) নিয়ে কাজ করা, যেখানে প্রতিটি অনুরোধ সম্পন্ন হতে সহজেই কয়েকশো মিলিসেকেন্ড বা তারও বেশি সময় লাগতে পারে। উদাহরণস্বরূপ, কল্পনা করুন যে আপনি তাদের পাবলিক এপিআই ব্যবহার করে কয়েকটি গিটহাব অ্যাকাউন্টের ফলোয়ার সংখ্যা জানতে চান।
সরল সিঙ্ক্রোনাস পদ্ধতিতে একটি জনপ্রিয় ব্লকিং HTTP ক্লায়েন্ট ব্যবহার করা হবে, যেমন requests. আপনি একটি লুপের মধ্যে প্রতিটি ইউজার এন্ডপয়েন্টের জন্য একটি GET রিকোয়েস্ট চালাবেন, JSON পেলোডটি পড়বেন, এবং ডেটা এক্সট্র্যাক্ট করবেন। followers ফিল্ডটি প্রিন্ট বা সংরক্ষণ করে। এটি সহজ এবং পাঠযোগ্য, কিন্তু এর একটি অসুবিধা আছে: আপনি প্রতিটি অ্যাকাউন্ট প্রসেস করার সময়, প্রোগ্রামটি অনুরোধটি সম্পাদন করে এবং পরবর্তীটি শুরু করার আগেই কেবল প্রতিক্রিয়ার জন্য অপেক্ষা করে।
সুতরাং আপনি যদি তিনজন ব্যবহারকারীকে চেক করেন যেমন api.github.com/users/python, api.github.com/users/google এবং api.github.com/users/firebaseকোডটি প্রথম অনুরোধটি পাঠায়, GitHub-এর প্রতিক্রিয়া না পাওয়া পর্যন্ত আটকে থাকে, তারপর দ্বিতীয় অনুরোধে চলে যায়, এবং এভাবেই চলতে থাকে। অল্প কয়েকজন ব্যবহারকারীর ক্ষেত্রে এটা হয়তো গ্রহণযোগ্য হতে পারে, কিন্তু আপনার তালিকা যখন শত শত বা হাজার হাজারে পৌঁছায়, তখন মোট প্রসেসিং সময় বহুগুণে বেড়ে যায়, কারণ আপনার অ্যাপটি তার কার্যকালের বেশিরভাগ সময়ই নিষ্ক্রিয়ভাবে রিমোট সার্ভারের জন্য অপেক্ষা করে।
কাজ দ্রুত করার জন্য, আপনি এর উপরে নির্মিত একটি অ্যাসিঙ্ক্রোনাস ইমপ্লিমেন্টেশনে যেতে পারেন। asyncio এবং একটি অ্যাসিঙ্ক-সক্ষম HTTP ক্লায়েন্টের মতো aiohttp. এই মডেলে, আপনি বেশ কয়েকটি কো-রুটিন টাস্ক চালু করেন, যেগুলো প্রায় একই সাথে তাদের HTTP রিকোয়েস্টগুলো পাঠায়। এরপর ইভেন্ট লুপ সেগুলোর যেকোনো একটির রেসপন্সের জন্য অপেক্ষা করে এবং ডেটা আসা মাত্রই প্রতিটি টাস্ক পুনরায় চালু করে; এর ফলে পরবর্তীটি শুরু করার জন্য একটি রিকোয়েস্ট পুরোপুরি শেষ হওয়ার অপেক্ষা করতে হয় না।
যখন এই দুটি পদ্ধতিকে পাশাপাশি রেখে কার্যকারিতা পরীক্ষা করা হয়, তখন অ্যাসিঙ্ক সংস্করণটি সাধারণত অনেক বড় ব্যবধানে এগিয়ে থাকে, বিশেষ করে ব্যবহারকারীর সংখ্যা বাড়ার সাথে সাথে। প্রতিটি অনুরোধের সময় অপরিবর্তিত থাকে, কিন্তু সমস্ত ফলাফল পাওয়ার মোট সময় উল্লেখযোগ্যভাবে কমে যায়, কারণ আপনি ধারাবাহিকভাবে না করে একযোগে অনেকগুলো সংযোগ পরিচালনা করছেন।
মূল ধারণা: কোরাউটিন, ইভেন্ট লুপ, টাস্ক এবং ফিউচার
অভ্যন্তরীণভাবে, আধুনিক অ্যাসিঙ্ক্রোনাস পাইথন কয়েকটি মূল বিল্ডিং ব্লকের উপর ভিত্তি করে গড়ে উঠেছে, যা প্রধানত সরবরাহ করে থাকে asyncio মডিউল। এই ধারণাগুলো বুঝতে পারলে ইকোসিস্টেমের বাকি অংশ অনেক বেশি স্পষ্ট হয়ে উঠবে এবং এটি আপনাকে শক্তিশালী অ্যাসিঙ্ক আর্কিটেকচার ডিজাইন করতে সাহায্য করবে।
কো-রুটিন হলো এক বিশেষ ধরনের ফাংশন যা নিজের কার্য সম্পাদনকে থামাতে এবং পুনরায় শুরু করতে পারে। আজকের সিনট্যাক্সে, আপনি একটিকে সংজ্ঞায়িত করেন async defযখন আপনি এটিকে কল করেন, তখন আপনি একটি কো-রুটিন অবজেক্ট পান যেটিকে অ্যাওয়েট বা শিডিউল করতে হয়; এটি একটি সাধারণ ফাংশনের মতো সঙ্গে সঙ্গে সম্পূর্ণভাবে রান করে না। এর ভিতরে, যখনই আপনি ব্যবহার করেন await একটি awaitable (যেমন অন্য কোনো coroutine, task, future ইত্যাদি) এর ক্ষেত্রে, Python সেই coroutine-টিকে ততক্ষণ পর্যন্ত স্থগিত রাখে যতক্ষণ না await করা অপারেশনটি সম্পন্ন হয়।
ইভেন্ট লুপ হলো সেই নিয়ন্ত্রক, যা সমস্ত অপেক্ষমান কো-রুটিন, ইনপুট/আউটপুট অপারেশন এবং টাইমারের হিসাব রাখে এবং যেকোনো নির্দিষ্ট সময়ে কোন কোডটি চলবে তা নির্ধারণ করে। ঐতিহাসিকভাবে আপনাকে লুপটি স্পষ্টভাবে পেতে এবং পরিচালনা করতে হতো asyncio.get_event_loop()কিন্তু আধুনিক পাইথন কোডে পছন্দের প্যাটার্ন হলো লেট asyncio.run() আপনার জন্য একটি শীর্ষ-স্তরের অ্যাসিঙ্ক ফাংশনের চারপাশে লুপটি তৈরি, চালানো এবং বন্ধ করে দেয়, যেমন main().
টাস্ক হলো কো-রুটিনের র্যাপার, যা ইভেন্ট লুপকে সেগুলোকে নির্বাহের জন্য সময়সূচী নির্ধারণ করতে নির্দেশ দেয়। আপনি এগুলিকে হালকা কাজ হিসেবে ভাবতে পারেন: লুপটি একাধিক থ্রেড চালু না করেই অনেক কাজের মধ্যে অগ্রগতি পর্যায়ক্রমে করতে পারে। আপনি সাধারণত টাস্ক তৈরি করেন asyncio.create_task() অথবা সাহায্যকারীদের ডেকে যেমন asyncio.gather(), যেগুলো অভ্যন্তরীণভাবে একাধিক কাজ পরিচালনা করে।
ফিউচার হলো এমন ফলাফল যা পরবর্তীতে পাওয়া যাবে, যা জাভাস্ক্রিপ্টের প্রমিজের মতোই। টাস্ক এবং ফিউচার উভয়ই অ্যাওয়েটেবল অবজেক্ট: আপনি পারেন await অন্তর্নিহিত অপারেশনটি শেষ না হওয়া পর্যন্ত সেগুলোকে স্থগিত রাখা হয়। এই সমন্বিত প্রোটোকল অর্কেস্ট্রেশন কোডকে অনেক সরল করে তোলে, কারণ অ্যাসিঙ্ক ফ্লো গঠন করা বলতে মূলত সঠিক ক্রমে সঠিক অবজেক্টগুলোকে অ্যাওয়েট করাকেই বোঝায়।
বাস্তবে অ্যাসিঙ্ক সিনট্যাক্স: async, await, async with এবং async for
সার্জারির async কীওয়ার্ডটি শুধু ফাংশন ডেফিনিশনের মধ্যেই সীমাবদ্ধ নয়; এটি কন্টেক্সট ম্যানেজার এবং লুপ পর্যন্তও বিস্তৃত, যাতে আরও উন্নত প্যাটার্নগুলো অ্যাসিঙ্ক জগতে অংশগ্রহণ করতে পারে। এই বর্ধিত সিনট্যাক্স জানা থাকলে নেটওয়ার্ক কানেকশন, সেশন, স্ট্রিম এবং কাস্টম প্রোটোকল নিয়ে মার্জিত কোড লিখতে সুবিধা হয়।
সবচেয়ে সাধারণ ফর্ম হয় async defযা একটি অ্যাসিঙ্ক্রোনাস ফাংশন (একটি কো-রুটিন ফ্যাক্টরি) সংজ্ঞায়িত করে। এই ধরনের ফাংশনের ভিতরে, আপনি অবাধে ব্যবহার করবেন await যখনই আপনি অন্য কোনো কো-রুটিন বা অ্যাওয়েটেবল অপারেশন কল করেন, যেমন asyncio.sleep()একটি অ্যাসিঙ্ক্রোনাস HTTP অনুরোধ, অথবা একটি অ্যাসিঙ্ক্রোনাস ডাটাবেস কোয়েরি। মনে রাখবেন যে আপনি ব্যবহার করতে পারবেন না await সরাসরি একটি স্ক্রিপ্টের শীর্ষ স্তরে; এটি অবশ্যই একটির ভিতরে থাকতে হবে async def.
যদিও আপনি ফোন করতে প্রলুব্ধ হতে পারেন time.sleep() আপনার কো-রুটিনের ভিতরে ডিলে ব্যবহার করলে, তা অ্যাসিঙ্ক ব্যবহারের উদ্দেশ্যকেই পুরোপুরি ব্যর্থ করে দেবে। time.sleep() এটি ইভেন্ট লুপ সহ পুরো থ্রেডকে ব্লক করে, ফলে সেই সময়ে অন্য কোনো অ্যাসিঙ্ক টাস্ক এগোতে পারে না। এর পরিবর্তে, আপনাকে অবশ্যই এর নন-ব্লকিং বিকল্পটি ব্যবহার করতে হবে। asyncio.sleep()যা টাইমার গণনা শেষ না হওয়া পর্যন্ত লুপের কাছে নিয়ন্ত্রণ ফিরিয়ে দেয়।
পাইথন অ্যাসিঙ্ক্রোনাস কনটেক্সট ম্যানেজারকেও সমর্থন করে। async withবিশেষ পদ্ধতিগুলি সংজ্ঞায়িত করার মাধ্যমে বাস্তবায়িত হয় __aenter__ এবং __aexit__. এটি বিশেষত সেইসব অবজেক্ট নিয়ে কাজ করার সময় সুবিধাজনক, যেগুলোর জন্য অ্যাসিঙ্ক্রোনাস অপারেশন জড়িত একটি পরিষ্কার সেটআপ এবং টিয়ারডাউন সিকোয়েন্স প্রয়োজন, যেমন একটি নেটওয়ার্ক সেশন খোলা বা একটি অ্যাসিঙ্ক্রোনাস রিসোর্স অধিগ্রহণ করা। একটি সাধারণ উদাহরণ হলো পরিচালনা করা। aiohttp.ClientSession অথবা একটি স্বতন্ত্র HTTP অনুরোধ ব্যবহার করে async with ম্যানুয়ালি কল করার পরিবর্তে ব্লক close().
অবশেষে, অ্যাসিঙ্ক্রোনাস ইটারেশন উন্মোচিত হয় এর মাধ্যমে async forযা জাদুকরী পদ্ধতির উপর নির্ভর করে __aiter__ এবং __anext__ PEP 492-এ বর্ণিত। অ্যাসিঙ্ক ইটারেটর এবং অ্যাসিঙ্ক জেনারেটর ব্যবহার করে আপনি সময়ের সাথে সাথে আইটেম ইয়েল্ড করতে পারেন। await ইটারেশন প্রক্রিয়ার ভিতরে, যা নেটওয়ার্কের মাধ্যমে বা অন্য কোনো অ্যাসিঙ্ক উৎস থেকে ধীরে ধীরে আসা ডেটা স্ট্রিমিং করার জন্য উপযুক্ত।
একই সাথে একাধিক কাজ চালানো asyncio
অ্যাসিঙ্ক্রোনাস প্রোগ্রামিংয়ের আসল শক্তি তখনই প্রকাশ পায়, যখন আপনি একাধিক I/O-নির্ভর কাজ একের পর এক না চালিয়ে একযোগে চালান। পাইথনের অ্যাসিঙ্ক ইকোসিস্টেমে এর প্রধান টুলগুলো হলো asyncio.create_task() এবং asyncio.gather(), উভয়ই ইভেন্ট লুপে কো-রুটিন শিডিউল করে।
সঙ্গে asyncio.gather()আপনি একবারে একাধিক কো-রুটিন চালু করতে পারেন এবং সেগুলোর সবগুলোর কাজ শেষ হওয়া পর্যন্ত অপেক্ষা করতে পারেন, এবং ফলাফল একটি লিস্ট বা টাপল হিসেবে গ্রহণ করতে পারেন। একসাথে অনেকগুলো HTTP কল, ডাটাবেস কোয়েরি বা যেকোনো পুনরাবৃত্তিমূলক অ্যাসিঙ্ক্রোনাস অপারেশনের ক্ষেত্রে এটি অত্যন্ত সাধারণ। নেপথ্যে, gather() প্রতিটি কো-রুটিনকে একটি টাস্কের আওতায় আনে এবং সেগুলোর সম্পূর্ণতা নিশ্চিত করে।
আপনি যদি গিটহাব প্রোফাইল আনার উদাহরণে ফিরে যান কিন্তু এটিকে রিফ্যাক্টর করেন aiohttp এবং asyncio.gather()এর ফলে একটি ফাংশনে তিনবার কল করা হবে, যেমন fetch_user() একই সাথে চালু করা হচ্ছে। প্রতিটি টাস্ক তার HTTP অনুরোধ শুরু করে, ডেটার জন্য অপেক্ষা করার সময় নিয়ন্ত্রণ ছেড়ে দেয় এবং প্রতিক্রিয়াটি এসে পৌঁছালে তা পার্স করা পুনরায় শুরু করে। ব্যবহারকারীর দৃষ্টিকোণ থেকে, তিনটি ফলাফলই প্রায় একই সময়ে প্রদর্শিত হয়।
তবে, এমন পরিস্থিতিও রয়েছে যেখানে আপনি একসাথে হাজার হাজার বা লক্ষ লক্ষ টাস্ক চালু করতে চাইবেন না, কারণ তা আপনার নিজের মেশিনকে অতিরিক্ত চাপে ফেলতে পারে অথবা বাহ্যিক রেট লিমিটে (external rate limit) পৌঁছে যেতে পারে। একটি সাধারণ রীতি হলো শুধুমাত্র প্রক্রিয়াকরণের মাধ্যমে সমান্তরাল প্রক্রিয়াকরণকে সীমিত করা। MAX_TASKS আপনার অ্যাসিঙ্ক ওয়ার্কফ্লোর ভিতরে সেমাফোর, বাউন্ডেড পুল বা ম্যানুয়াল ব্যাচিং লজিক ব্যবহার করে একবারে একাধিক অপারেশন সম্পন্ন করুন।
একই সাথে অনেকগুলো কাজ চালানোর ক্ষেত্রে আরেকটি গুরুত্বপূর্ণ দিক হলো ত্রুটি কীভাবে সামলানো হয়; বাস্তব অ্যাপ্লিকেশনগুলিতে একটিমাত্র ব্যর্থ অনুরোধের কারণে পুরো ব্যাচটি ক্র্যাশ করা খুব কমই গ্রহণযোগ্য। আদর্শগতভাবে, আপনার অ্যাসিঙ্ক অর্কেস্ট্রেশনের উচিত প্রতিটি টাস্কের জন্য আলাদাভাবে এক্সেপশন ধরা এবং পরিচালনা করা; যেমন—সেগুলোকে লগ করা, বেছে বেছে পুনরায় চেষ্টা করা অথবা ব্যাচের বাকি অংশ অক্ষত রেখে আংশিক ফলাফল ফেরত দেওয়া।
যুগপৎ ব্যবহার পরিচালনা: সুবিধা ও অসুবিধা
আপনার মনে কনকারেন্সি এবং প্যারালেলিজমের ধারণা দুটিকে আলাদা করে নেওয়া জরুরি, কারণ অ্যাসিঙ্ক পাইথন প্রথমটি প্রদান করলেও দ্বিতীয়টি আবশ্যিকভাবে প্রদান করে না। কনকারেন্সি মানে হলো একাধিক টাস্ক একই সময়ে অগ্রসর হয়, অন্যদিকে প্যারালেলিজম মানে হলো সেগুলো একাধিক সিপিইউ কোরে একই মুহূর্তে চলে।
সাধারণ অ্যাসিঙ্ক্রোনাস কোড ব্যবহার করে asyncio এটি একাধিক OS থ্রেড তৈরি করে না; পরিবর্তে, এটি একটি একক থ্রেডে কাজগুলিকে মাল্টিপ্লেক্স করে, যখন প্রতিটি কাজ I/O-তে ব্লক হয়, যা অনেকটা এর অনুরূপ Node.js এ প্রোগ্রামিং এসিনক্রোনা. এই কারণেই এটি হাজার হাজার সংযোগের সাথে এত ভালোভাবে কাজ করে: কনটেক্সট সুইচিং সাশ্রয়ী, কারণ এটি সহযোগিতামূলক এবং অপারেটিং সিস্টেমের পরিবর্তে ইভেন্ট লুপ দ্বারা নিয়ন্ত্রিত হয়।
এই নকশাটির কিছু চ্যালেঞ্জ রয়েছে, বিশেষ করে সমন্বয় এবং ব্যতিক্রমী পরিস্থিতি সামলানোর ক্ষেত্রে। যেহেতু আপনার লজিক এখন একাধিক কো-রুটিনে বিভক্ত যা সময়ের সাথে সাথে একে অপরের সাথে মিশে যায়, তাই স্টেট শেয়ার করা, ত্রুটি ছড়ানো এবং রিসোর্স পরিষ্কার করার সময় আপনাকে আরও সতর্ক থাকতে হবে। ভুলে যাওয়া বাগের মতো সমস্যা দেখা দিতে পারে। awaitযেসব টাস্কের জন্য কখনো অপেক্ষা করা হয় না, অথবা ব্যাকগ্রাউন্ড টাস্কের মধ্যে নীরবে চাপা পড়ে যাওয়া এক্সেপশনগুলো সূক্ষ্ম হতে পারে এবং ডিবাগ করা কঠিন হতে পারে।
আপনার অ্যাসিঙ্ক কোডবেসকে রক্ষণাবেক্ষণযোগ্য রাখতে, আপনার কিছু সুপ্রতিষ্ঠিত ইঞ্জিনিয়ারিং নীতি অনুসরণ করা উচিত: কো-রুটিনগুলোকে একটিমাত্র দায়িত্বে সীমাবদ্ধ রাখা, যেখানে সম্ভব ত্রুটি পরিচালনা (error handling) কেন্দ্রীভূত করা, এবং রানটাইমে কী ঘটছে তা বোঝার জন্য পর্যাপ্ত লগিং যুক্ত করা। ভালো টুলস এবং সুস্পষ্ট নিয়মকানুন রেস-কন্ডিশনের মতো সমস্যা বা রিসোর্স লিক প্রতিরোধে অনেকখানি সাহায্য করে, এমনকি একটি সিঙ্গেল-থ্রেডেড অ্যাসিঙ্ক পরিবেশেও।
কখন অ্যাসিঙ্ক্রোনাস কোড সত্যিই সাহায্য করে (এবং কখন করে না)
অ্যাসিঙ্ক্রোনাস প্রোগ্রামিং ইনপুট/আউটপুট-নির্ভর ওয়ার্কলোডের জন্য অত্যন্ত কার্যকর, কিন্তু এটি পারফরম্যান্সের প্রতিটি সমস্যার অব্যর্থ সমাধান নয়। যেকোনো অপ্টিমাইজেশন প্রচেষ্টার প্রথম ধাপ হওয়া উচিত আপনার প্রতিবন্ধকতাগুলো I/O থেকে আসছে নাকি CPU-নির্ভর গণনা থেকে আসছে, তা শনাক্ত করা।
যদি আপনার অ্যাপ্লিকেশনটি তার বেশিরভাগ সময় নেটওয়ার্ক প্রতিক্রিয়ার জন্য অপেক্ষা করতে, ফাইল পড়তে ও লিখতে, ডাটাবেস কোয়েরি করতে বা সকেটের মাধ্যমে যোগাযোগ করতে ব্যয় করে, তাহলে অ্যাসিঙ্ক প্রায় নিশ্চিতভাবেই একটি ভালো সমাধান। সাধারণ উদাহরণগুলির মধ্যে রয়েছে ওয়েব এপিআই যা একাধিক বাহ্যিক পরিষেবার সাথে যোগাযোগ করে, ইটিএল পাইপলাইন যা একই সাথে একাধিক ডেটা উৎস থেকে ডেটা পড়ে ও লেখে, এবং মাইক্রোসার্ভিস যা একই সময়ে অনেকগুলো ক্লায়েন্ট সংযোগ বজায় রাখে।
অন্যদিকে, যদি আপনার কাজের চাপ সংখ্যা গণনা, চিত্র প্রক্রিয়াকরণ বা জটিল সিমুলেশনের মতো ভারী সিপিইউ অপারেশন দ্বারা প্রভাবিত হয়, তবে শুধুমাত্র অ্যাসিঙ্ক ব্যবহার করে গতি বাড়ানো যাবে না। এই ধরনের পরিস্থিতিতে, GIL (গ্লোবাল ইন্টারপ্রেটার লক) একটি একক পাইথন প্রসেসের মধ্যে সমান্তরালভাবে কী চালানো যাবে তা সীমাবদ্ধ করে রাখে। সাধারণত মাল্টি-প্রসেসিং, নেটিভ এক্সটেনশন, বা বিশেষায়িত ব্যাকএন্ড ব্যবহার করে আপনি আরও ভালো ফলাফল পাবেন।
কর্পোরেট পরিবেশে একটি বাস্তবসম্মত কৌশল হলো এই পদ্ধতিগুলোর সমন্বয় করা: ল্যাটেন্সি কমানো এবং থ্রুপুট বাড়ানোর জন্য ক্লাউড পরিষেবাগুলোর (AWS, Azure এবং অন্যান্য) জন্য asyncio এবং async-aware SDK ব্যবহার করা, এবং একই সাথে সিপিইউ-নির্ভর কাজগুলোকে আলাদা প্রসেস, ওয়ার্কার বা পরিচালিত কম্পিউট পরিষেবাগুলোর কাছে অর্পণ করা। এভাবে আপনি ল্যাঙ্গুয়েজ রানটাইমের বিরুদ্ধে লড়াই না করে প্রতিটি টুলের শক্তিকে কাজে লাগাতে পারেন।
অ্যাসিঙ্ক পাইথন লেখার সেরা অনুশীলন
একবার আপনি আরও ব্যাপকভাবে অ্যাসিঙ্ক ব্যবহার শুরু করলে, কিছু নির্দিষ্ট ধরণ ও অভ্যাস আপনাকে সবচেয়ে সাধারণ ভুলগুলো এড়াতে সাহায্য করবে। এগুলো আপনার কোডকে সেইসব সহকর্মীদের কাছেও আরও স্পষ্ট করে তোলে, যারা হয়তো এখনও অ্যাসিঙ্ক ইকোসিস্টেমের সাথে গভীরভাবে পরিচিত নন।
একটি মৌলিক নিয়ম হলো আপনার অ্যাসিঙ্ক্রোনাস কোড পাথে ব্লকিং কল পরিহার করা। এর মানে হল এই ধরনের জিনিস প্রতিস্থাপন করা time.sleep() সঙ্গে await asyncio.sleep()এবং যেসব লাইব্রেরি অ্যাসিঙ্ক-কম্প্যাটিবল এপিআই (API) প্রদান করে না, সেগুলোর ব্যাপারে সতর্ক থাকা। যদি কোনো থার্ড-পার্টি প্যাকেজ সম্পূর্ণরূপে সিনক্রোনাস হয়, তবে একটি কো-রুটিন থেকে এটিকে ব্যাপকভাবে কল করলে তা ইভেন্ট লুপকে ব্লক করে দিতে পারে এবং আপনার কনকারেন্সির সুবিধা নষ্ট করে দিতে পারে।
যখন আপনার একাধিক স্বতন্ত্র I/O অপারেশনের একটি ব্যাচ থাকে, তখন ইউটিলিটি ব্যবহার করে সেগুলিকে একযোগে চালানো শ্রেয়, যেমন asyncio.gather() অথবা সর্বোচ্চ সমান্তরালতা স্তর দ্বারা সীমাবদ্ধ কাজের সমষ্টি। এই প্যাটার্নটি খোলা সংযোগ বা চলমান অনুরোধের সংখ্যা নিয়ন্ত্রণে রেখেও থ্রুপুট বৃদ্ধি করে।
ডিজাইনের নির্দেশিকা হিসেবে, কো-রুটিনগুলোকে তুলনামূলকভাবে ছোট এবং একটি সুস্পষ্ট দায়িত্বের উপর কেন্দ্রীভূত রাখার চেষ্টা করুন, ঠিক যেভাবে আপনি পরিচ্ছন্ন সিনক্রোনাস কোডে ফাংশন ডিজাইন করেন। নেটওয়ার্কিং, বিজনেস লজিক এবং এরর হ্যান্ডলিং-কে একত্রিত করে এমন বিশাল একক কো-রুটিনগুলো দ্রুত পরীক্ষা করা এবং সেগুলোর মর্মোদ্ধার করা কঠিন হয়ে পড়ে, বিশেষ করে যখন মাঝপথে কিছু ব্যর্থ হয়।
অবশেষে, আপনি যে ইকোসিস্টেম উপাদানগুলোর ওপর নির্ভর করেন, সেগুলো সত্যিই অ্যাসিঙ্ক্রোনাস ব্যবহার সমর্থন করে কি না, তা সর্বদা যাচাই করে নিন। অনেক জনপ্রিয় লাইব্রেরি আলাদা অ্যাসিঙ্ক ক্লায়েন্ট বা ডেডিকেটেড সাবমডিউল সরবরাহ করে; আবার অন্যগুলো "অ্যাসিঙ্ক" ফিচারের বিজ্ঞাপন দিলেও আড়ালে ব্লকিং হতে পারে। ডকুমেন্টেশন মনোযোগ দিয়ে পড়া এবং ছোট ছোট বেঞ্চমার্ক পরীক্ষা করা আপনাকে সূক্ষ্ম পারফরম্যান্স রিগ্রেশন থেকে বাঁচাতে পারে।
ব্যবহারিক প্রয়োগের দৃশ্যকল্প এবং স্থাপত্য ধারণা
বাস্তব সফটওয়্যার প্রজেক্টে, প্রচলিত ওয়েব ব্যাকএন্ড থেকে শুরু করে অত্যাধুনিক এআই-চালিত সিস্টেম পর্যন্ত বিভিন্ন আর্কিটেকচারে অ্যাসিঙ্ক তার কার্যকারিতা প্রদর্শন করে। একীভূতকারী উপাদানটি হলো, নিষ্ক্রিয় অপেক্ষায় সময় নষ্ট না করে সর্বদা বহুবিধ ইনপুট/আউটপুট (I/O) ভিত্তিক অপারেশন পরিচালনা করার প্রয়োজন।
এর একটি চিরায়ত উদাহরণ হলো এমন একটি ওয়েব সার্ভিস, যাকে ক্লায়েন্টের জন্য একটিমাত্র প্রতিক্রিয়া তৈরি করতে একাধিক বাহ্যিক এপিআই-কে কল করতে হয়। অ্যাসিঙ্ক ব্যবহার করে, সার্ভিসটি একবারে সমস্ত আউটবাউন্ড রিকোয়েস্ট ট্রিগার করতে পারে এবং প্রতিটি অংশ এসে পৌঁছানোর সাথে সাথেই চূড়ান্ত পেলোডটি একত্রিত করতে পারে, যা মোট রেসপন্স টাইম উল্লেখযোগ্যভাবে কমিয়ে দেয়। মাইক্রোসার্ভিস আর্কিটেকচার এবং পেমেন্ট গেটওয়ে, সোশ্যাল নেটওয়ার্ক বা অ্যানালিটিক্স প্ল্যাটফর্মের সাথে ইন্টিগ্রেশনের ক্ষেত্রে এটি প্রচলিত।
এর আরেকটি গুরুত্বপূর্ণ ব্যবহার হলো ডেটা ইঞ্জিনিয়ারিং: পাইপলাইন এবং ETL জবগুলো প্রায়শই সমান্তরালভাবে একাধিক ডেটাবেস, ফাইল সিস্টেম বা ক্লাউড স্টোরেজ বাকেটের সাথে কাজ করে। একই সাথে একাধিক উৎস থেকে ডেটা পড়ে এবং ফলাফল প্রস্তুত হওয়া মাত্রই তা লিখে ফেলার মাধ্যমে, আপনি সামগ্রিক লেটেন্সি কমাতে পারেন এবং উপলব্ধ ব্যান্ডউইথের আরও ভালো ব্যবহার করতে পারেন, বিশেষ করে যখন ক্লাউড স্টোরেজ বা REST-ভিত্তিক ডেটা API নিয়ে কাজ করা হয়।
Async বিজনেস ইন্টেলিজেন্স ড্যাশবোর্ড এবং Power BI-এর মতো টুলগুলোর সাথেও ভালোভাবে কাজ করে, যেখানে ব্যাকএন্ডগুলোকে দীর্ঘস্থায়ী HTTP সংযোগগুলো ব্লক না করে বিভিন্ন সার্ভিস থেকে ডেটা একত্রিত করতে হয়। আপনার কাস্টম এপিআই লেয়ার বা ইন্টিগ্রেশন মাইক্রোসার্ভিস তৈরি করা asyncio লোডের অধীনে অনুভূত প্রতিক্রিয়াশীলতা এবং থ্রুপুট উন্নত করতে পারে।
কাস্টম সফটওয়্যার, কৃত্রিম বুদ্ধিমত্তা, সাইবার নিরাপত্তা এবং ক্লাউড কনসাল্টিং-এ বিশেষজ্ঞ কোম্পানিগুলো প্রায়শই এমন ওয়ার্কফ্লো পরিচালনা করতে অ্যাসিঙ্ক কৌশলের উপর ব্যাপকভাবে নির্ভর করে, যা এআই মডেলকে কল করে, ইভেন্ট লগ করে, হুমকি পর্যবেক্ষণ করে এবং ক্লাউড কন্ট্রোল প্লেনের সাথে যোগাযোগ স্থাপন করে। অর্কেস্ট্রেশনের জন্য অ্যাসিঙ্ক আই/ও এবং ভারী কাজের জন্য আলাদা সিপিইউ-অপ্টিমাইজড ওয়ার্কারের সমন্বয় একটি সাধারণ অভ্যন্তরীণ প্যাটার্ন, যা স্কেলেবল ও রক্ষণাবেক্ষণযোগ্য সিস্টেম তৈরি করে।
অনেক ডেভেলপার এবং টিমের জন্য, প্রথম পদক্ষেপটি হলো অ্যাপ্লিকেশনের সেই অংশগুলিতে অ্যাসিঙ্ক (async) চালু করা যেগুলো স্পষ্টভাবে ইনপুট/আউটপুট (I/O) নির্ভর বলে মনে হয়; এরপর এর সুবিধাগুলো স্পষ্ট হয়ে উঠলে এবং টিম এই প্যারাডাইম ও টুলিংয়ের ওপর আস্থা অর্জন করলে, সেখান থেকে পর্যায়ক্রমে অগ্রসর হওয়া।
পরিশেষে, পাইথনে অ্যাসিঙ্ক্রোনাস প্রোগ্রামিং হলো অপেক্ষার সময়কে বুদ্ধিমানের সাথে ব্যবহার করা: আপনার কোডকে এমনভাবে গঠন করার মাধ্যমে, যা... async, awaitকো-রুটিন এবং ইভেন্ট লুপ ব্যবহার করে আপনি এমন অ্যাপ্লিকেশন তৈরি করতে পারেন যা দ্রুততর মনে হয়, লোডের অধীনে আরও ভালোভাবে স্কেল করে এবং উপলব্ধ সংস্থানগুলির সর্বোত্তম ব্যবহার করে, বিশেষ করে নেটওয়ার্ক, ফাইল এবং বাহ্যিক পরিষেবাগুলির সাথে কাজ করার সময়।
