পাইথন নাকি স্লো?
আজকে আমি দেখাবো হাই পার্ফরমেন্স পাইথন নিয়ে। এখানে আমি দেখাবো পাইথনের থ্রেডিং, কনকারেন্সি, এসিনক্রোনাস প্রোগ্রামিং ব্যবহার করে কিভাবে পাইথন প্রোগ্রামকে ফাস্ট করবেন।
চলুন শুরু করা যাক
শুরুর পূর্বেঃ থ্রেডিং/মাল্টি থ্রেডিং, এসিনক্রোনাস বা ডিস্ট্রিবিউটেড প্রোগ্রামিং পাইথনের খুবই এডভান্সড লেভেলের টপিক। তাই পাইথনের ফান্ডামেন্টাল ও ইন্টারমিডিয়েট লেভেলের ধারণা না থাকলে বুঝতে কষ্ট হবে। সাথে ওএস নিয়ে ধারণা থাকলে আরো ভালো।
আর অবশ্যই পাইথন ৩.৫+ ভার্সন লাগবে এই ফীচারগুলি ভালোভাবে পাবার জন্য।
Parallel, Concurrent, Synchronous, Asynchronous and Distributed Computing কোনটা কি?
একজন কাস্টমার ফুড এর দোকানে গেলো। বসলো এবং নিচের কাজগুলি করলোঃ
০১. কাস্টমার ওয়েটারকে “চিকেন বিরিয়ানী” আনতে অর্ডার দিলো
০২. এরপর কাস্টমার অপেক্ষা করতে লাগলো বিরিয়ানী আসার
এইদিকে ওয়েটার গিয়ে তাদের ১০ জন শেফ এর মাঝে একজন খালি থাকা শেফ কে বললেন কাস্টমার এর অর্ডার করা বিরিয়ানীর কথা। আর যদি ১০ জন শেফের সবাই ব্যস্ত থাকে তাহলে হোটেল এর দ্বিতীয় শাখায় কাজ পাঠিয়ে দেয়া হয়। শেফ বিরিয়ানী রান্না করতে শুরু করলো। ওয়েটার এসে কাস্টমারকে জানালো “অপেক্ষা করুন, আপনার অর্ডার গরম গরম প্রস্তুত করা হচ্ছে”। এরপর কাস্টমার অপেক্ষা করতে লাগলো আর ঐদিকে ওয়েটার নতুন আসা আরেক কাস্টমারের কাছে গেলো এবং একইভাবে ঐ নতুন কাস্টমারের কাছ থেকে অর্ডার নিয়ে আরেকজন শেফকে দায়িত্ব দিলো রান্না করার।
খেয়াল করে দেখুন, কাস্টমার কিন্ত অর্ডার দিয়ে অপেক্ষায় বসেই আছে। সে কিন্ত এই অর্ডার না খেয়ে বাহিরে গিয়ে অন্য কাজ করতে পারতেছে না। তার মানে খাবার এর অর্ডারের জন্য কাস্টমারের অন্য কাজগুলি ব্লক হয়ে আছে।
আর অপরদিকে ওয়েটার কিন্ত প্রত্যেকটা কাস্টমারের কাছ থেকেই অর্ডার নিচ্ছেই, কখনোই কোনো কাস্টমারকে বলছে না “আমাদের আগের কাস্টমার এর অর্ডার চলছে, তারটা শেষ হলে আপনারটা দিবো”। এটা বললে তার ব্যবসা থাকবে না। তাই সে মোট ১০ জন অভিজ্ঞ শেফ রেখেছেন যারা ৫ মিনিটে রান্না করা খাবার গরম করে সার্ভ করতে পারে। এরপরও ১০ জন ব্যস্ত হলে হোটেল এর দ্বিতীয় শাখায় কাজ পাঠিয়ে দেয়া হয়।
আসুন কম্পিউটিং এর দিক থেকে চিন্তা করি।
এখানে কাস্টমার এর কাজগুলি একটার পর আরেকটা হচ্ছে, আগের কাজ সম্পন্ন না হলে পরেরটা করতে পারছে না এবং আগের কাজটা পরের কাজকে ব্লক করে রাখছে, এটা হলো “সিনক্রোনাস বা সিক্যুয়েন্সিয়াল প্রসেসিং”
কিন্ত ওয়েটার কিন্ত একটা অর্ডার নিয়েই বসে নেই, সে এক সাথে অনেকগুলি অর্ডার নিচ্ছে এবং শেফদের মাঝে কাজ ভাগ করে দিচ্ছে। এবং যখন শেফ তার কাজ শেষ করছে তখন সে ওয়েটারকে জানিয়ে দিচ্ছে যে অর্ডার রেডি। এরপর ওয়েটার গিয়ে খাবার নিয়ে এসে কাস্টমারকে দেয়। ওয়েটার অর্ডারগুলিকে শেফদের মাঝে ভাগ করে দিয়ে নিজেকে ফ্রি রাখছে নতুন কাস্টমারের জন্য। যার ফলে কোন অর্ডারই তার কাজকে ব্লক করতে পারছে না। এখানে ওয়েটার এর কাজ হলো “এসিনক্রোনাস প্রসেসিং”
অপরদিকে কাজ কিন্তু একই ধরনের “খাবার প্রস্তুত করা” কিন্ত এই একই কাজ অনেক কাস্টমারের হয় বিধায় একই সাথে ১০ জন অভিজ্ঞ শেফ এর মাঝে কাজগুলি ভাগ করে দেয়া হয় যাতে করে প্রত্যেক শেফই একই সাথে “খাবার প্রস্তুত করা” কাজটি সম্পন্ন করতে পারে। এখানে ১০ জন শেফ এর মাঝে একটি কাজ ভাগ করে বন্টন করে একই সাথে চালিয়ে যাওয়াটা হলো “প্যারালাল প্রসেসিং”।
সর্বশেষ খেয়াল করুন, ১০ জন শেফই যদি ব্যস্ত থাকে তাহলেও কিন্ত কাস্টমারকে ফিরিয়ে দেয়া হচ্ছে না। বরং অর্ডারকে হোটেল এর আরেক শাখায় পাঠিয়ে দেয়া হয় তৈরি করার জন্য। এটা হচ্ছে “ডিস্ট্রিবিউটেড প্রসেসিং”।
০১. কাস্টমার ওয়েটারকে “চিকেন বিরিয়ানী” আনতে অর্ডার দিলো
০২. এরপর কাস্টমার অপেক্ষা করতে লাগলো বিরিয়ানী আসার
এইদিকে ওয়েটার গিয়ে তাদের ১০ জন শেফ এর মাঝে একজন খালি থাকা শেফ কে বললেন কাস্টমার এর অর্ডার করা বিরিয়ানীর কথা। আর যদি ১০ জন শেফের সবাই ব্যস্ত থাকে তাহলে হোটেল এর দ্বিতীয় শাখায় কাজ পাঠিয়ে দেয়া হয়। শেফ বিরিয়ানী রান্না করতে শুরু করলো। ওয়েটার এসে কাস্টমারকে জানালো “অপেক্ষা করুন, আপনার অর্ডার গরম গরম প্রস্তুত করা হচ্ছে”। এরপর কাস্টমার অপেক্ষা করতে লাগলো আর ঐদিকে ওয়েটার নতুন আসা আরেক কাস্টমারের কাছে গেলো এবং একইভাবে ঐ নতুন কাস্টমারের কাছ থেকে অর্ডার নিয়ে আরেকজন শেফকে দায়িত্ব দিলো রান্না করার।
খেয়াল করে দেখুন, কাস্টমার কিন্ত অর্ডার দিয়ে অপেক্ষায় বসেই আছে। সে কিন্ত এই অর্ডার না খেয়ে বাহিরে গিয়ে অন্য কাজ করতে পারতেছে না। তার মানে খাবার এর অর্ডারের জন্য কাস্টমারের অন্য কাজগুলি ব্লক হয়ে আছে।
আর অপরদিকে ওয়েটার কিন্ত প্রত্যেকটা কাস্টমারের কাছ থেকেই অর্ডার নিচ্ছেই, কখনোই কোনো কাস্টমারকে বলছে না “আমাদের আগের কাস্টমার এর অর্ডার চলছে, তারটা শেষ হলে আপনারটা দিবো”। এটা বললে তার ব্যবসা থাকবে না। তাই সে মোট ১০ জন অভিজ্ঞ শেফ রেখেছেন যারা ৫ মিনিটে রান্না করা খাবার গরম করে সার্ভ করতে পারে। এরপরও ১০ জন ব্যস্ত হলে হোটেল এর দ্বিতীয় শাখায় কাজ পাঠিয়ে দেয়া হয়।
আসুন কম্পিউটিং এর দিক থেকে চিন্তা করি।
এখানে কাস্টমার এর কাজগুলি একটার পর আরেকটা হচ্ছে, আগের কাজ সম্পন্ন না হলে পরেরটা করতে পারছে না এবং আগের কাজটা পরের কাজকে ব্লক করে রাখছে, এটা হলো “সিনক্রোনাস বা সিক্যুয়েন্সিয়াল প্রসেসিং”
কিন্ত ওয়েটার কিন্ত একটা অর্ডার নিয়েই বসে নেই, সে এক সাথে অনেকগুলি অর্ডার নিচ্ছে এবং শেফদের মাঝে কাজ ভাগ করে দিচ্ছে। এবং যখন শেফ তার কাজ শেষ করছে তখন সে ওয়েটারকে জানিয়ে দিচ্ছে যে অর্ডার রেডি। এরপর ওয়েটার গিয়ে খাবার নিয়ে এসে কাস্টমারকে দেয়। ওয়েটার অর্ডারগুলিকে শেফদের মাঝে ভাগ করে দিয়ে নিজেকে ফ্রি রাখছে নতুন কাস্টমারের জন্য। যার ফলে কোন অর্ডারই তার কাজকে ব্লক করতে পারছে না। এখানে ওয়েটার এর কাজ হলো “এসিনক্রোনাস প্রসেসিং”
অপরদিকে কাজ কিন্তু একই ধরনের “খাবার প্রস্তুত করা” কিন্ত এই একই কাজ অনেক কাস্টমারের হয় বিধায় একই সাথে ১০ জন অভিজ্ঞ শেফ এর মাঝে কাজগুলি ভাগ করে দেয়া হয় যাতে করে প্রত্যেক শেফই একই সাথে “খাবার প্রস্তুত করা” কাজটি সম্পন্ন করতে পারে। এখানে ১০ জন শেফ এর মাঝে একটি কাজ ভাগ করে বন্টন করে একই সাথে চালিয়ে যাওয়াটা হলো “প্যারালাল প্রসেসিং”।
সর্বশেষ খেয়াল করুন, ১০ জন শেফই যদি ব্যস্ত থাকে তাহলেও কিন্ত কাস্টমারকে ফিরিয়ে দেয়া হচ্ছে না। বরং অর্ডারকে হোটেল এর আরেক শাখায় পাঠিয়ে দেয়া হয় তৈরি করার জন্য। এটা হচ্ছে “ডিস্ট্রিবিউটেড প্রসেসিং”।
উপরের চিত্রে গ্রাফিক্যালি আমি দেখিয়েছি কোনটা কিভাবে কাজ করে।
এবার আসুন আমরা কাজে যাই।
প্রবলেমঃ
ধরুন আপনি চাচ্ছেন আপনার রেস্টূরেন্টকে সঠিকভাবে ম্যানেজ করতে যাতে করে অনেক কাস্টমারের অর্ডার নেয়া যায় খুব কম লোকবল দিয়েই। আসুন শুরু করি আমাদের কাজ।
সল্যুশনঃ
১. রেস্টূরেন্ট ম্যানেজমেন্ট — Synchronous ওয়েতে
ধরি আমাদের কাস্টমার ৫ জন এবং ওয়েটার ১ জন যিনি অর্ডার নেয়, রান্না করে ও খাবার রেডি করার কাজ একাই করে থাকেন
ওয়েটার কে ধরি সিপিইউ এর একটা থ্রেড, একটা থ্রেড একই সময়ে মাত্র একটা কাজ সম্পন্ন করতে পারে।
পাইথন কোড
import time def get_food(c_id): print("Customer %s's food is cooking...." % c_id) time.sleep(5) print("Customer %s's food is ready" % c_id) def make_order(c_id): print("Customer %s's food is Ordering...." % c_id) get_food(c_id) cust = 5 print("=====Starting time: " + str(time.ctime()))for c in range(cust): make_order(c) print("=====Ending time: " + str(time.ctime()))
আউটপুট
Running thread: : MainThread
=====Starting time: Sun Apr 8 15:33:14 2018
Customer 0’s food is Ordering….
Customer 0’s food is cooking….
Running thread: : MainThread
Customer 0’s food is ready
Customer 1’s food is Ordering….
Customer 1’s food is cooking….
Running thread: : MainThread
Customer 1’s food is ready
Customer 2’s food is Ordering….
Customer 2’s food is cooking….
Running thread: : MainThread
Customer 2’s food is ready
Customer 3’s food is Ordering….
Customer 3’s food is cooking….
Running thread: : MainThread
Customer 3’s food is ready
Customer 4’s food is Ordering….
Customer 4’s food is cooking….
Customer 4’s food is ready
=====Ending time: Sun Apr 8 15:33:39 2018
=====Starting time: Sun Apr 8 15:33:14 2018
Customer 0’s food is Ordering….
Customer 0’s food is cooking….
Running thread: : MainThread
Customer 0’s food is ready
Customer 1’s food is Ordering….
Customer 1’s food is cooking….
Running thread: : MainThread
Customer 1’s food is ready
Customer 2’s food is Ordering….
Customer 2’s food is cooking….
Running thread: : MainThread
Customer 2’s food is ready
Customer 3’s food is Ordering….
Customer 3’s food is cooking….
Running thread: : MainThread
Customer 3’s food is ready
Customer 4’s food is Ordering….
Customer 4’s food is cooking….
Customer 4’s food is ready
=====Ending time: Sun Apr 8 15:33:39 2018
এখানে দেখুন আমাদের মোট ৫ টা কাস্টমারের জন্য মাত্র ১ টা থ্রেড (একজন ওয়েটার) চালু হয়েছেঃ MainThread, এর মানে হলো আপনি যতগুলি কাজই করেন না কেনো এক সাথে, পাইথন একটা থ্রেডেই সব একটা শেষ হলে আরেকটা এভাবে রান করবে, কারণ পাইথন সিঙ্গেল থ্রেডেড। এর ফলে আপনাকে নানা সমস্যায় পড়তে হবে।
প্রথমত উপরের আউটপুট খেয়াল করে দেখুন প্রতিটা কাস্টমারের অর্ডার নেয়া, রান্না করা এবং খাবার রেডি করা একটার পর আরেকটা সিক্যুয়েন্স অনুসারে হচ্ছে। আর অর্ডার নিচ্ছে মাত্র একজন, আমাদের MainThread, যার ফলে একটা কাস্টমারের খাবার রেডি না হওয়া পর্যন্ত পরের কাস্টমারকে বসে থাকতে হবে। এমনকি অর্ডারও দিতে পারবে না।
একজন কাস্টমারের জন্য অর্ডার নেয়া, খাবার রান্না করা ও রেডি করাতে সময় লাগে মোট ৫ সেকেন্ড। তাহলে উপরের ওয়েতে ৫ জন কাস্টমারের জন্য লাগবেঃ
৫ x ৫ = ২৫ সেকেন্ড
তাই হয়েছেঃ Starting Time(15:33:14) - Ending Time(15:33:39) = 25 seconds
এত সময় লাগাটা মোটেই আশাজনক নয়। একজন কাস্টমারের জন্য বাকি কাস্টমারগুলি ব্লক হয়ে থাকে। কি করা যায়?
আসুন আমরা ৫ জন কাস্টমারের জন্য ৫ জন্য ওয়েটার নেই যারা একই সাথে কাজ করতে পারবে
২. রেস্টূরেন্ট ম্যানেজমেন্ট — Multi-threading কনকারেন্ট ওয়েতে
ধরি আমাদের কাস্টমার ৫ জন এবং ওয়েটারও ৫ জন যারা অর্ডার নেয়, রান্না করে ও খাবার রেডি করার কাজ একাই করে থাকে
ওয়েটার কে ধরি একটা থ্রেড, একটা থ্রেড একই সময়ে মাত্র একটা কাজ সম্পন্ন করতে পারে।
পাইথন কোড
import time from threading import Thread import threadingdef get_food(c_id): print("Customer %s's food is cooking...." % c_id) time.sleep(5) print("Customer %s's food is ready" % c_id) print("=======Ending time: " + str(time.ctime()))def make_order(c_id): print("Running Thread: " + str(threading.current_thread().name)) print("Customer %s's food is Ordering...." % c_id) get_food(c_id)cust = 5 threads = []print("=======Starting time: " + str(time.ctime()))for c in range(cust): thread = Thread(target=make_order, args=(c,)) thread.start()for t in threads: t.join()
আউটপুট
=======Starting time: Sun Apr 8 15:30:04 2018
Running Thread: Thread-1
Customer 0’s food is Ordering….
Customer 0’s food is cooking….
Running Thread: Thread-2
Customer 1’s food is Ordering….
Customer 1’s food is cooking….
Running Thread: Thread-3
Customer 2’s food is Ordering….
Customer 2’s food is cooking….
Running Thread: Thread-4
Customer 3’s food is Ordering….
Customer 3’s food is cooking….
Running Thread: Thread-5
Customer 4’s food is Ordering….
Customer 4’s food is cooking….
Customer 1’s food is ready
Customer 0’s food is ready
=======Ending time: Sun Apr 8 15:30:09 2018
=======Ending time: Sun Apr 8 15:30:09 2018
Customer 3’s food is ready
Customer 2’s food is ready
Customer 4’s food is ready
=======Ending time: Sun Apr 8 15:30:09 2018
=======Ending time: Sun Apr 8 15:30:09 2018
=======Ending time: Sun Apr 8 15:30:09 2018
Running Thread: Thread-1
Customer 0’s food is Ordering….
Customer 0’s food is cooking….
Running Thread: Thread-2
Customer 1’s food is Ordering….
Customer 1’s food is cooking….
Running Thread: Thread-3
Customer 2’s food is Ordering….
Customer 2’s food is cooking….
Running Thread: Thread-4
Customer 3’s food is Ordering….
Customer 3’s food is cooking….
Running Thread: Thread-5
Customer 4’s food is Ordering….
Customer 4’s food is cooking….
Customer 1’s food is ready
Customer 0’s food is ready
=======Ending time: Sun Apr 8 15:30:09 2018
=======Ending time: Sun Apr 8 15:30:09 2018
Customer 3’s food is ready
Customer 2’s food is ready
Customer 4’s food is ready
=======Ending time: Sun Apr 8 15:30:09 2018
=======Ending time: Sun Apr 8 15:30:09 2018
=======Ending time: Sun Apr 8 15:30:09 2018
এখানে দেখুন আমাদের মোট ৫ টা কাস্টমারের জন্য ৫ টা থ্রেড (ওয়েটার) চালু হয়েছেঃ Thread-1, Thread-2, Thread-3, Thread-4, Thread-5
যাই হোক। কাজের কথায় আসি
বাহ, ৫ জন ওয়েটারকে যুক্ত করলাম ৫ জন কাস্টমারের জন্য।
আউটপুট খেয়াল করে দেখুন প্রতিটা কাস্টমারের অর্ডার নেয়া, রান্না করা এবং খাবার রেডি করা এই ৩ টা কাজ করছে একজন করে ওয়েটার বা থ্রেড। অর্থাৎ ৫ জন কাস্টমারের জন্য ৫ জন ওয়েটার বা থ্রেড। এখন আর একজন কাস্টমারের জন্য আরেকজন কাস্টমারকে বসে থাকতে হবে না।
একজন কাস্টমারের জন্য অর্ডার নেয়া, খাবার রান্না করা ও রেডি করাতে সময় লাগে মোট ৫ সেকেন্ড। তাহলে উপরের ওয়েতে ৫ জন কাস্টমারের জন্য লাগার কথাঃ
৫ x ৫ = ২৫ সেকেন্ড। কিন্ত না, সময় লেগেছে মাত্র ৫ সেকেন্ড!
Starting Time(15:30:04) - Ending Time(15:30:09) = 5 seconds
ওয়াও! কিভাবে সম্ভব? ৫ গুণ তাড়াতাড়ি!
ওয়েটার বা থ্রেড ছিল ৫ টা মোট ৫ জন কাস্টমারের জন্য। ৫ জন একই সময়ে অর্ডার নিয়ে রান্না করা ও খাবার রেডি করার কাজ সমানভাবে করে গিয়েছে। একজনের পর আরেকজন… এভাবে কাজ করেনি তাই মোট সময় লেগেছে ৫ সেকেন্ডই।
অনেক সময় বাচলো তাই না? কিন্ত সমস্যা কি খেয়াল করেছেন?
ওয়েটার নির্দিষ্ট, মাত্র ৫ জন। কিন্ত একই সাথে ১০ জন কাস্টমার আসলে কি করবেন? প্রথম ৫ জনেরটা একই সাথে শুরু হলেও পরের ৫ জনকে অপেক্ষা করতে হবে আগের ৫ জন এর টা শেষ হবার জন্য, কারণ ওয়েটার মাত্র ৫ জন।
এ তো আরেক সমস্যা। তাহলে কি আরো ওয়েটার বাড়াবো?
বাড়াতে পারেন যদি আপনার অনেক টাকা থাকে। তবে আমি আপনাকে আরেকটা সমাধান দেই।
ধরুন, আপনার রেস্টূরেন্টে ওয়েটার থাকবে ১ জনই যে অর্ডার নিবে, কিন্ত রান্না করার শেফ থাকবে ৫ জন। ওয়েটার কেবল অর্ডার নিবে এবং সেই অর্ডার শেফদের দিয়ে পরের অর্ডার নিবে। কেমন হবে? এর ফলে কাস্টমারকে অর্ডার দেয়ার জন্য আর অপেক্ষা করতে হবে না। সে শুধু অপেক্ষা করবে কখন খাবার আসবে তার জন্য। এতে করে ওয়েটার সব সময়ই নতুন অর্ডার নেয়ার জন্য রেডি থাকবে।
৩. রেস্টূরেন্ট ম্যানেজমেন্ট — Asynchronous ওয়েতে
ধরি আমাদের কাস্টমার ৫ জন এবং ওয়েটার ১ জন যিনি অর্ডার নেয়, রান্না করে ও খাবার রেডি করার কাজ একাই করে থাকেন
ওয়েটার কে ধরি সিপিইউ এর একটা থ্রেড, একটা থ্রেড একই সময়ে মাত্র একটা কাজ সম্পন্ন করতে পারে।
পাইথন কোড
import time
import asyncio
import logging
logging.basicConfig(
level=logging.INFO,
format=' %(message)s: %(threadName)10s',
)
async def get_food(c_id):
logging.info("Running thread: ")
print("Customer %s's food is cooking...." % c_id)
await asyncio.sleep(5)
print("Customer %s's food is ready" % c_id)
async def make_order(c_id):
print("Customer %s's food is Ordering...." % c_id)
await get_food(c_id)
cust = 5
tasks = []
for c in range(cust):
tasks.append(make_order(c))
print("=======Starting time: " + str(time.ctime()))
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
print("=======Ending time: " + str(time.ctime()))
আউটপুট
Running thread: : MainThread=======Starting time: Sun Apr 8 15:35:08 2018
Running thread: : MainThread
Running thread: : MainThread
Running thread: : MainThread
Running thread: : MainThread
Customer 2’s food is Ordering….
Customer 2’s food is cooking….
Customer 3’s food is Ordering….
Customer 3’s food is cooking….
Customer 4’s food is Ordering….
Customer 4’s food is cooking….
Customer 0’s food is Ordering….
Customer 0’s food is cooking….
Customer 1’s food is Ordering….
Customer 1’s food is cooking….
Customer 2’s food is ready
Customer 4’s food is ready
Customer 1’s food is ready
Customer 3’s food is ready
Customer 0’s food is ready
=======Ending time: Sun Apr 8 15:35:13 2018
Running thread: : MainThread
Running thread: : MainThread
Running thread: : MainThread
Running thread: : MainThread
Customer 2’s food is Ordering….
Customer 2’s food is cooking….
Customer 3’s food is Ordering….
Customer 3’s food is cooking….
Customer 4’s food is Ordering….
Customer 4’s food is cooking….
Customer 0’s food is Ordering….
Customer 0’s food is cooking….
Customer 1’s food is Ordering….
Customer 1’s food is cooking….
Customer 2’s food is ready
Customer 4’s food is ready
Customer 1’s food is ready
Customer 3’s food is ready
Customer 0’s food is ready
=======Ending time: Sun Apr 8 15:35:13 2018
এখানে দেখুন আমাদের মোট ৫ টা কাস্টমারের জন্য মাত্র ১ টা থ্রেড (একজন ওয়েটার) চালু হয়েছেঃ MainThread
কিন্ত ৫ জন কাস্টমারের জন্য ২৫ সেকেন্ড লাগার বদলে মাত্র ৫ সেকেন্ডেই অর্ডার নেয়া, রান্না করা আর খাবার রেডি করা যাচ্ছেঃ
Starting Time(15:35:08) - Ending Time(15:35:13) = 5 seconds
অসম্ভব! এ কি করে সম্ভব?
রান্না করা ও খাবার রেডি করা বোঝাতে আমরা asyncio.sleep(5) ফাংশন ব্যবহার করেছি। এই স্লিপ এর ফলে সিপিইউ কোন কাজ করে না ৫ সেকেন্ড এর জন্য। তাই এটাকে আমরা I/O Bound কাজ ধরতে পারি। এই ধরনের কাজগুলির জন্য তো আর সিপিইউ কে আটকে রাখা যায় না তাই আমরা স্লিপিং এর সময় সিপিইউকে ছেড়ে দিচ্ছি পরের কাজ করার জন্য এবং যখন এই স্লিপিং পিরিডয়ড শেষ হচ্ছে তখন আমরা আউটপুট দিচ্ছি।
তার মানে দাড়ালো, I/O Bound কাজগুলির সময় আমরা সিপিইউ এর থ্রেড কে ফ্রি করে দিচ্ছি যাতে করে সে অন্য কাজ করতে পারে।
তাই হয়েছে, ওয়েটার অর্ডার নিয়ে শেফ কে দিয়েই ফ্রি হয়ে যাচ্ছে পরের অর্ডার নেয়ার জন্য। আর শেফরা রান্না করে খাবার রেডি করে কাস্টমারকে দিচ্ছে।
ওয়েব এপ্লিকেশনকে এভাবে চালানো হলে খুবই ইফিসিয়েন্ট হয়। কারণ ওয়েব সার্ভার জাস্ট রিক্যুয়েস্ট নিয়েই ফ্রি হয়ে গেলো। ডাটাবেজ থেকে রিড রাইট করা যেহেতু I/O বাউন্ড কাজ তাই এই কাজটি এসিনক্রোনাস ভাবে করলে একজন ইউজারের জন্য বাকি ইউজারদের আর অপেক্ষা করতে হবে না রিক্যুয়েস্ট পাঠাতে।
প্যারালাল ওয়েতেও ৫ সেকেন্ড আবার এসিনক্রোনাস ওয়েতেও ৫ সেকেন্ড। তাহলে কোনটা ব্যবহার করবো?
আসল কথাতে আসি। থ্রেডিং এ ৪/৫ টা থ্রেড একসাথে রান হলেও আসলে পাইথন একসাথে মাত্র একটা থ্রেড রান করে। এটা পাইথন এর মূল ইন্টারপ্রেটার CPython এর লিমিটেশন।
তাহলে কিভাবে ৫ গুণ তাড়াতাড়ি কাজ হলো?
আসলে time.sleep(5) কোন সিপিইউ বাউন্ড কাজ না, এটা আইও বাউন্ড কাজ। পাইথনের থ্রেডিং চলাকালে প্রথমে একটা থ্রেড চালু হয়, এরপর সেটা যদি সিপিইউ এর কাজ করতে থাকে তাহলে পরের থ্রেড গুলি ব্লক হয়ে থাকবে। ঠিক সিনক্রোনাস এর মতো। কিন্ত যদি আইও বাউন্ড হয় তাহলে পাইথনের ইন্টারপ্রেটার আইও বাউন্ড কাজ পাওয়া মাত্রই ঐ থ্রেডকে ছেড়ে দিয়ে পরের থ্রেডে চলে যায়। আর আগের থ্রেডটি তার আইও বাউন্ড কাজ চালিয়ে যেতে থাকে, কারণ সেখানে সিপিইউ এর দরকার পড়ে না। এই কারণে আমাদের থ্রেডিং প্রোগ্রাম ৫ গুণ ফাস্ট হয়েছে কারণ আমাদের কাজটা ছিল আইও বাউন্ড। সিপিইউ বাউন্ড হলে সিঙ্গেল থ্রেড এর চাইতে আরো অনেক স্লো হয়ে যেতো মাল্টি থ্রেডিং করলে। এভাবে করেই পাইথন কনকারেন্সির সুবিধা দেয় আইও বাউন্ড কাজের জন্য থ্রেডিং করে।
কেনো মাল্টি থ্রেডিং এ সিপিইউ বাউন্ড কাজ স্লো, আর কিভাবেই বা মুক্তি পাওয়া যাবে এটা নিয়ে পরের পোস্ট লিখতেছি অনেক বিস্তারিত।
কিছু পয়েন্টে আসি
০১. মাল্টি থ্রেড নিয়ে কাজ করা একটু কমপ্লেক্স সিঙ্গেল থ্রেড এর চাইতে। তাই আপনার কাজগুলি যদি ম্যাক্সিমামই I/O বাউন্ড হয়ে তাহলে আপনি সিঙ্গেল থ্রেডেড এসিনক্রোনাস ব্যবহার করতে পারেন। আর উপরের ব্যাখ্যা অনুযায়ী থ্রেডিং ও করতে পারেন। আইও বাউন্ড কাজের জন্য এসিনক্রোনাস আর থ্রেডিং দুইটাই কাজের।
০২. আপনার কাজগুলি যদি সবই CPU বাউন্ড হয় তাহলে সিঙ্গেল থ্রেডেড এসিনক্রোনাস বা থ্রেডিং করে কোন লাভ হবে না, কারণ সিপিইউ এর থ্রেড কাজ করতে গিয়ে আটকে থাকবে এবং পরের কাজগুলি আটকে যাবে। এই অবস্থায় প্যারালাল ওয়েতে আগালে বেস্ট হবে। কিভাবে পাইথনে প্যারালাল করবেন তা দেখাবো আগামী পোস্টে।
০৩. প্যারালাল ওয়েতে কাজ করার কিছু অসুবিধা আছে। ১০ টা থ্রেড যদি একটা কাজ করতে থাকে ভাগ ভাগ করে তাহলে দেখা যাবে র্যান্ডমভাবে ২/৩ টা থ্রেড হয়তো একই কাজের অংশ নিয়ে টানাটানি শুরু করবে, একে বলে “রেস কন্ডিশন”। এর ফলে আপনি কাংখিত আউটপুট পাবার বদলে সিপিইউ থ্রেড এর ঝগড়ার কবলে পড়ে ইনফিনিট লুপ এ পড়ে যেতে পারেন। এর জন্য প্রয়োজন “থ্রেড সিনক্রোনাইজেশন” ও “থ্রেড লক”, এগুলি নিয়ে আরেকদিন লিখবো।
০৪. জাভা স্ক্রিপ্টে কলব্যাক ফাংশনের মাধ্যমে জানিয়ে দেয়া হয় যে কাজ শেষ হয়েছে। কিন্ত কলব্যাক এর অসুবিধা হলো কোন এরোর বা এক্সেপশন হলে আর আপনি সেটা চেক না করলে আর খুজে পাবেন না, সিপিইউ এর থ্রেড সেটাকে খেয়ে ফেলে। জাভাস্ক্রিপ্ট এ প্রমিজ দিয়ে এর থেকে মুক্তি পাওয়া যায়। (লেটেস্ট আপডেট আমার জানা নেই)
একটা কলব্যাক এর ভেতরে আরো ৫ টা কলব্যাক নেস্টেড করে রাখলে আপনি যে সমস্যায় পড়বেন তাকে বলে “কলব্যাক হেল”।
খেয়াল করে দেখুন, মাল্টি থ্রেডিং আর এসিনক্রোনাস এই দুইটাই একটা ক্ষেত্রে ইফিসিয়েন্টঃ সিঙ্গেল কোর সিপিইউ এর জন্য আইও বাউন্ড কাজ এর। কিন্তু এই ২ টার মাঝে পার্থক্য হলো একাধিক থ্রেড রানিং থাকলে পাইথন একটা থ্রেডই কেবল একই সময় রান করতে পারে। আর যখনই আইও বাউন্ড কাজ বা থ্রেড টার্মিনেট হয়ে যাবে তখন সিপিইউ অটোম্যাটিক্যালি থ্রেড কন্টেক্সট সুইচ করবে, যাকে বলে নন-ডিটার্মিনিস্টিক কনটেক্সট সুইচিং। অপরদিকে এসিনক্রোনাস ওয়েতে আপনি নিজে কোড করে কন্ট্রোল করবেন কখন কন্টেক্সট সুইচ করবেন। যেমনঃ আমাদের উদাহরণে যখনই আমরা get_food() নামক আইও বাউন্ড কাজ এ যাচ্ছি তখন কনটেক্সট সুইচ করে দিচ্ছি থ্রেড ফ্রি রাখার জন্য। এর মানে হলো, আপনি নিজে ইচ্ছামত আইও বাউন্ড কাজ দেখলে কন্টেক্সট সুইচ করতে পারবেন, অন্যদিকে থ্রেডিং এ এই কাজটা সিপিইউ নিজে করে।
0 comments:
Post a Comment