Python-এ `concurrent` মডিউল
এই প্রবন্ধে, আমরা পাইথনের concurrent মডিউল ব্যাখ্যা করবো।
Concurrency এবং parallelism এর ধারণা স্পষ্ট করার পাশাপাশি, আমরা ব্যবহারিক উদাহরণসহ concurrent মডিউল ব্যবহার করে কীভাবে asynchronous প্রসেসিং বাস্তবায়ন করা যায় তা ব্যাখ্যা করবো।
YouTube Video
Python-এ concurrent মডিউল
পাইথনে প্রসেসিং দ্রুত করতে গেলে, concurrency এবং parallelism-এর পার্থক্যগুলো মনে রাখা গুরুত্বপূর্ণ। concurrent মডিউল হলো এই পার্থক্যগুলো মাথায় রেখে নিরাপদ ও সহজভাবে asynchronous প্রসেসিং হ্যান্ডেল করার গুরুত্বপূর্ণ উপায়।
Concurrency এবং Parallelism-এর পার্থক্য
-
Concurrency হলো এমনভাবে একটি প্রসেস ডিজাইন করা, যাতে বিভিন্ন টাস্ক ছোট কাজের একক করে তাদের মধ্যে স্যুইচ করে এগিয়ে যায়। টাস্কগুলো প্রকৃতপক্ষে একসাথে চলতে না থাকলেও, "অপেক্ষার সময়" ব্যাবহার করে পুরো প্রসেস আরও দক্ষ করা যায়।
-
Parallelism এমন এক ব্যবস্থা, যাতে একই সময়ে শারীরিকভাবে একাধিক টাস্ক চালানো হয়। একাধিক CPU কোর ব্যবহার করে, প্রসেসিং একসাথে এগিয়ে যায়।
উভয়টাই প্রসেসিং দ্রুত করার কৌশল, তবে concurrency হল 'কিভাবে এগোনো যায়' এর নকশার বিষয়, আর parallelism হল 'কিভাবে চলে' তার কার্যকারিতার বিষয়, তাই এরা মৌলিকভাবে ভিন্ন।
concurrent মডিউল কী?
পাইথনের concurrent একটি স্ট্যান্ডার্ড লাইব্রেরি, যা নিরাপদ ও সহজভাবে concurrency এবং parallelism পরিচালনার উচ্চ-স্তরের API প্রদান করে। এটি এমনভাবে ডিজাইন করা হয়েছে যাতে আপনি শুধু 'টাস্ক সম্পাদনের' উপর ফোকাস করতে পারেন, এবং থ্রেড বা প্রসেস তৈরি ও পরিচালনার মতো নিম্ন-স্তরের জটিলতা নিয়ে চিন্তা করতে হয় না।
ThreadPoolExecutor ও ProcessPoolExecutor-এর ভূমিকা
concurrent মডিউল টাস্কের প্রকৃতির উপর ভিত্তি করে দুটি প্রধান বিকল্প দেয়।
-
ThreadPoolExecutorএটি concurrent বাস্তবায়নের জন্য উপযুক্ত, বিশেষভাবে নেটওয়ার্ক বা ফাইল অপারেশনের মতো অনেক I/O অপেক্ষার টাস্কের জন্য। টাস্কগুলির মধ্যে স্যুইচ করে, এটি অপেক্ষার সময়কে কার্যকরভাবে ব্যবহার করে। -
ProcessPoolExecutorএই বাস্তবায়নটি parallel প্রসেসিংয়ের জন্য এবং CPU-নির্ভর টাস্কের জন্য উপযুক্ত। এটি একাধিক প্রসেস ব্যবহার করে বিদ্যমান CPU কোরসমূহের পূর্ণ সদ্ব্যবহার করে।
তাই, concurrent মডিউলের একটি প্রধান বৈশিষ্ট্য হলো, এটি এমন কাঠামো প্রদান করে যাতে আপনি প্রয়োজনে concurrency এবং parallelism-এর মধ্যে সঠিকভাবে নির্বাচন করতে পারেন।
ThreadPoolExecutor-এর মূল বিষয় (I/O টাস্কের জন্য)
ThreadPoolExecutor হলো I/O-নির্ভর টাস্ক যেমন নেটওয়ার্ক যোগাযোগ বা ফাইল অপারেশনের জন্য উপযুক্ত। এটি একাধিক থ্রেডের মধ্যে টাস্কগুলো ভাগ করে, অপেক্ষার সময়কে কার্যকরভাবে ব্যবহার করে।
1from concurrent.futures import ThreadPoolExecutor
2import time
3
4def fetch_data(n):
5 # Simulate an I/O-bound task
6 time.sleep(1)
7 return f"data-{n}"
8
9with ThreadPoolExecutor(max_workers=3) as executor:
10 futures = [executor.submit(fetch_data, i) for i in range(5)]
11
12 for future in futures:
13 print(future.result())- এই উদাহরণে, এক সেকেন্ড অপেক্ষা করা একাধিক I/O টাস্ক একযোগে সম্পাদিত হচ্ছে।
submitব্যবহার করে, ফাংশন কলগুলো asynchronous টাস্ক হিসেবে নিবন্ধন করা হয়, আরresult()কল করে ফলাফলের জন্য অপেক্ষা ও সংগ্রহ করা যায়, যার মাধ্যমে অপেক্ষার সময়কে কার্যকরভাবে ব্যবহার করে সংক্ষিপ্তভাবে concurrent প্রসেসিং করা যায়।
map ব্যবহার করে সহজ concurrency
যদি জটিল কন্ট্রোলের প্রয়োজন না হয়, তাহলে map ব্যবহার করলে কোড আরও সংক্ষিপ্ত হয়।
1from concurrent.futures import ThreadPoolExecutor
2import time
3
4def fetch_data(n):
5 # Simulate an I/O-bound task
6 time.sleep(1)
7 return f"data-{n}"
8
9with ThreadPoolExecutor(max_workers=3) as executor:
10 results = executor.map(fetch_data, range(5))
11
12 for result in results:
13 print(result)- এই উদাহরণে,
ThreadPoolExecutor.mapব্যবহার করে একাধিক I/O টাস্ক concurrent ভাবে সম্পাদিত হয়।mapইনপুটের ক্রমানুসারে আউটপুট দেয়, তাই আপনি প্রায় ধারাবাহিক (sequential) প্রসেসিং এর মত কোড লিখতে পারবেন এবং asynchronous প্রসেসিং নিয়ে সচেতন না হয়েও concurrent কার্যক্রম চালানো যায়—এটা একটি বড় সুবিধা।
ProcessPoolExecutor-এর মূল বিষয় (CPU-নির্ভর টাস্কের জন্য)
ভারি হিসাব-নিকাশ যেখানে CPU পুরোপুরি ব্যবহৃত হয়, সেক্ষেত্রে থ্রেডের বদলে process ব্যবহার করা উচিত। এতে করে Global Interpreter Lock (GIL)-এর সীমাবদ্ধতা এড়ানো যায়।
1from concurrent.futures import ProcessPoolExecutor
2
3def heavy_calculation(n):
4 # Simulate a CPU-bound task
5 total = 0
6 for i in range(10_000_000):
7 total += i * n
8 return total
9
10if __name__ == "__main__":
11 with ProcessPoolExecutor(max_workers=4) as executor:
12 results = executor.map(heavy_calculation, range(4))
13
14 for result in results:
15 print(result)এই উদাহরণে, CPU-নির্ভর হিসাব-নিকাশ ProcessPoolExecutor ব্যবহার করে সমান্তরালভাবে (parallel) করা হয়। কারণ এখানে process তৈরি হয়, তাই __main__ গার্ড প্রয়োজন, যা একাধিক CPU কোর ব্যবহার করে নিরাপদ parallel প্রসেসিং নিশ্চিত করে।
as_completed ব্যবহার করে সম্পন্ন হওয়ার ক্রম অনুযায়ী প্রসেসিং
as_completed তখন সহায়ক, যখন আপনি টাস্ক সম্পন্ন হওয়ার ক্রমানুসারে ফলাফল নিতে চান।
1from concurrent.futures import ThreadPoolExecutor, as_completed
2import time
3
4def fetch_data(n):
5 # Simulate an I/O-bound task
6 time.sleep(1)
7 return f"data-{n}"
8
9with ThreadPoolExecutor(max_workers=3) as executor:
10 futures = [executor.submit(fetch_data, i) for i in range(5)]
11
12 for future in as_completed(futures):
13 print(future.result())- এই উদাহরণে, একাধিক asynchronous টাস্ক একই সাথে চলেছে, এবং ফলাফল সেই ক্রমে নেওয়া হয়েছে যেভাবে তারা সম্পন্ন হয়েছে।
as_completedব্যবহার করলে টাস্কের ক্রম নির্বিশেষে দ্রুত ফলাফল নেওয়া যায়, এজন্য এটি অগ্রগতি প্রদর্শন বা ধারাবাহিকভাবে হ্যান্ডেল করার জন্য উপযুক্ত।
এক্সেপশন হ্যান্ডলিং
concurrent-এ, আপনি যখন result() কল করেন, তখন এক্সেপশন ওঠে।
1from concurrent.futures import ThreadPoolExecutor
2
3def risky_task(n):
4 # Simulate a task that may fail for a specific input
5 if n == 3:
6 raise ValueError("Something went wrong")
7 return n * 2
8
9with ThreadPoolExecutor() as executor:
10 futures = [executor.submit(risky_task, i) for i in range(5)]
11
12 for future in futures:
13 try:
14 print(future.result())
15 except Exception as e:
16 print("Error:", e)- এই উদাহরণ দেখায়, কিছু টাস্ক এক্সেপশন তুললেও বাকিগুলো চলতে থাকে এবং ফলাফল নেওয়ার সময় প্রত্যেকটির এক্সেপশন আলাদাভাবে হ্যান্ডল করা যায়।
concurrent-এরFutureব্যবহার করে, অ্যাসিনক্রোনাস প্রসেসিংয়ের সফলতা ও ব্যর্থতা নিরাপদে হ্যান্ডল করা যায়—এটা গুরুত্বপূর্ণ।
থ্রেড ও প্রসেস নির্বাচনের জন্য নির্দেশনা
concurrency ও parallelism কার্যকরভাবে ব্যবহার করতে, টাস্কের প্রকৃতি অনুযায়ী সঠিক পদ্ধতি নির্বাচন করা গুরুত্বপূর্ণ।
বাস্তবে, নিচের মানদণ্ডগুলো আপনাকে সিদ্ধান্তে সাহায্য করবে।
- যোগাযোগ বা ফাইল অপারেশনের মতো যেসব প্রক্রিয়ায় অনেক I/O অপেক্ষা থাকে, সেগুলোর জন্য
ThreadPoolExecutorব্যবহার করুন। - CPU-নির্ভর, ভারি হিসাব-নিকাশের টাস্কের জন্য
ProcessPoolExecutorব্যবহার করুন। - যদি অনেক সহজ টাস্ক থাকে,
mapব্যবহার করলে কোড আরও সংক্ষিপ্ত হয়। - যদি এক্সিকিউশনের ক্রম বা এক্সেপশন হ্যান্ডলিং-এ নির্ভুল নিয়ন্ত্রণ দরকার হয়, তাহলে
submitএবংas_completedএকসাথে ব্যবহার করুন।
concurrent ব্যবহারের উপকারিতা
concurrent মডিউল ব্যবহার করে, আপনি অ্যাসিনক্রোনাস প্রসেসিং নিরাপদ এবং স্বভাবিকভাবে পরিচালনা করতে পারেন।
মূল উপকারিতাগুলো হলো:।
- আপনাকে কম-স্তরের থ্রেড বা প্রসেস ম্যানেজমেন্ট নিয়ে চিন্তা করতে হবে না।
- এটি পাইথনের স্ট্যান্ডার্ড লাইব্রেরির অংশ হিসেবে দেয়া হয়েছে, তাই নিশ্চিন্তে ব্যবহার করতে পারবেন।
- কোড আরও পড়ার উপযোগী ও রক্ষণাবেক্ষণযোগ্য হয়।
- concurrency ও parallelism শেখার প্রথম পদক্ষেপ হিসেবে এটি আদর্শ।
শুধু এই নির্দেশিকাগুলো মনে রাখলেই concurrent-ব্যবহারে ব্যর্থতার হার অনেক কমে যাবে।
সারসংক্ষেপ
concurrent মডিউল হলো পাইথনে ব্যবহারিক concurrency ও parallelism-এর জন্য স্ট্যান্ডার্ড অপশন। এটি প্রসেসিং-এর কাঠামো বড় পরিবর্তন ছাড়াই পারফরম্যান্স বাড়ায়—বাস্তবে এটা একটি গুরুত্বপূর্ণ সুবিধা। concurrent ব্যবহার করে, আপনি সংক্ষিপ্তভাবে অ্যাসিনক্রোনাস প্রসেসিং ইমপ্লিমেন্টের পাশাপাশি নিরাপদে এক্সেপশন হ্যান্ডলিং ও এক্সিকিউশন কন্ট্রোল পরিচালনা করতে পারেন।
আপনি আমাদের ইউটিউব চ্যানেলে ভিজ্যুয়াল স্টুডিও কোড ব্যবহার করে উপরের নিবন্ধটি অনুসরণ করতে পারেন। দয়া করে ইউটিউব চ্যানেলটিও দেখুন।