GO — কনকারেন্সিঃ Goroutines এবং Channels

হাই পার্ফরমেন্স প্রোগ্রামিং এডভান্সড টপিক। বিশেষ করে প্যারালাল ও কনকারেন্ট প্রোগ্রামিং।

আপনি যদি এর আগে অন্য কোনো প্রোগ্রামিং ল্যাঙ্গুয়েজে কনকারেন্সি করে থাকেন তাহলে গো এর কনকারেন্সি মডেল আপনাকে নতুন করে ভাবতে শেখাবে। আমার অফিসে আমি পাইথন ও গো দুইটা নিয়েই কাজ করি। আপনি যদি পাইথনে ইন্টারমিডিয়েট ইউজার হন তাহলে পাইথনের কনকারেন্সি নিয়ে আমার এই লেখাটা আগে পড়ার অনুরোধ রইলো।

আমি আজকের এই লেখাতে জেনারেল কনসেপ্ট নিয়ে আলোচনা করবো না খুব একটা দরকার ছাড়া, কারণ এর জন্য আলাদা একটা আর্টিকেলই লিখতে হবে। তাই আমার সাজেশন হলো জেনারেল কনসেপ্ট জানার জন্য An Introduction to Parallel Programming এই বইটি পড়তে পারেন। আর ল্যাংগুয়েজ স্পেফিসিক কনসেপ্ট এর জন্য বিভিন্ন ল্যাঙ্গুয়েজের বই পড়তে পারেন। যেমন পাইথের জন্য Parallel Programming with Python এই বইটা পড়তে পারেন। গো এর জন্য Concurrency in Go এই বইটা পড়তে পারেন।

পূর্ব শর্তঃ গো নিয়ে ইন্টারমিডিয়েট থেকে এডভান্সড নলেজ এবং সাথে প্যারালাল ও কনকারেন্ট প্রোগ্রামিং নিয়ে ধারণা।

গো বর্তমানে জনপ্রিয় একটা ল্যাঙ্গুয়েজ এর বেশ কিছু ফিচারের জন্য। এর মধ্যে অন্যতম হলো এর কনকারেন্সি ফীচার এর ল্যাঙ্গুয়েজ এর মধ্যেই বিল্ট-ইন, যেখানে অন্যান্য ল্যাঙ্গুয়েজে কনকারেন্সিকে এক্সট্রা ফিচার হিসেবে দেয়া হয়। এর জন্য আমি আগে সব সময়ই বলতাম, ল্যাঙ্গুয়েজ কখনো কনকারেন্ট হতে পারে না, তবে গো নিয়ে কাজ করার সময় সেই ধারণা পাল্টে গেছে। গো এর প্রায় সবকিছুই রান হয় কনকারেন্টলি, এমনকি গো এর main() ফাংশনও কনকারেন্টলি রান হয় যাকে বলে মেইন গোরুটিন বা মেইন থ্রেড।

চিত্রে দেখুন মেইন গো রুটিন এর পাশাপাশি আরও কতগুলা গো রুটিন রান হয়েছে।

কনকারেন্সি ও প্যারালালিজম এক নয়

কনকারেন্সি মানে হচ্ছে একই সাথে অনেক কাজ ডিল করা। আর প্যারালালিজম হচ্ছে একই সাথে অনেক কাজ করা। পার্থক্য আছে এখানে।

গো কনকারেন্সির মূল উপাদানগুলি হল

০১ গো রুটিন

০২ চ্যানেলস

এছাড়াও আরও কিছু উপাদান আছে। আসুন গোরুটিন দিয়েই শুরু করি।

গোরুটিন

আপনি যদি সিএসই এর স্টুডেন্ট হয়ে থাকেন তাহলে অপারেটিং সিস্টেম নিয়ে অবশ্যই কোর্স করেছেন। সে ক্ষেত্রে আমার বোঝাতে একটু সুবিধা হবে। অন্য সাবজেক্টের থাকলেও অপারেটিং সিস্টেম নিয়ে সলিড ধারণা থাকলে খুবই ভালো হয়।

অপারেটিং সিস্টেমের অন্যতম কাজ হচ্ছে এপ্লিকেশন রান করা। একটা এপ্লিকেশন এর ইন্সটেন্স রান হলে সেটাকে প্রসেস বলে। একটা এপ্লিকেশনের অনেকগুলি প্রসেস থাকতে পারে। সেক্ষেত্রে সেটাকে মাল্টি-প্রসেসিং এপ্লিকেশন বলে। যাই হোক,প্রসেসের ভিতরে থাকে এক বা একাধিক থ্রেড যেগুলিকে ওএস থ্রেড বলে। একটা এপ্লিকেশনের কাজের লোড অনেকগুলি থ্রেডের মাঝে ভাগ করে দিলে কাজটা দ্রুত প্রসেস হবে।

কিন্ত প্রথম সমস্যা হচ্ছে, মাল্টিপল প্রসেস এক সাথে অনেক মেমোরি স্পেস দখল করে। কিন্ত থ্রেড অনেক কম জায়গা দখল করে। কিন্ত তবুও থ্রেড অনেক হ্যাভিওয়েট। এর জন্য জাভার থ্রেডিং নিয়ে কমপ্লেইন আছে।

আর তাছাড়া থ্রেড শিডিউলিং এর কাজ করে মূলত অপারেটিং সিস্টেম।

এসব এর চেয়ে গোরুটিন আলাদা। গোরুটিন হলো গো এর থ্রেড, যা অনেক অনেক কম জায়গা দখল করে, তাই অনেক লাইটওয়েট। আপনি এক সাথে মিলিয়ন মিলিয়ন গোরুটিন রান করতে পারবেন যা একদম কম মেমোরি দখল করবে এবং আপনার কম্পিউটারের সকল এভেইলেবল সিপিইউ কোরকে ব্যবহার করবে দরকার পড়লে। আর তাছাড়া গো নিজেই তার গোরুটিন থ্রেডগুলি শিডিউলিং করে। তাই নর্মাল থ্রেডের চেয়ে গোরুটিন অনেক ইফিসিয়েন্ট।

Image result for goroutines

তাহলে আমরা জানলাম গোরুটিন হলো গো এর নিজস্ব লাইটওয়েট, অত্যন্ত ইফিসিয়েন্ট থ্রেড মডেল। এবার আসি গো এর চ্যানেল নিয়ে।

চ্যানেল

প্রসেসরের কাজ প্রসেস করা। কি প্রসেস করা? ইন্সট্রাকশন অনুযায়ী ডাটা প্রসেস করা। তাই থ্রেডের কাজও সেই ডাটা নিয়েই কাজ করা। কিন্ত মাল্টিপল থ্রেড নিয়ে ঝামেলা আছে। একটা উদাহরণ দেই।

ধরুন মন্টুকে আপনি আপনার দোকানের হিসাব নিকাশ করতে দিলেন। মন্টু খুব খুশি মনে করতেছিল। এর মাঝে আপনি ভাবলেন বল্টুকেও একই কাজে লাগিয়ে দিবেন তাতে করে কাজ দ্রুত হবে। কিন্ত বল্টু আর মন্টু একই হিসাব এর কাজ দুইজনে একসাথে করতে গিয়ে হিসাব নিয়ে টানাটানি লেগে গেলো। ফলাফলঃ হিসাবে গড়মিল।

এটাকে বলে রেস কন্ডিশন।

উপরের চিত্রে থ্রেড ১ হলো মন্টু, থ্রেড ২ হলো বল্টু। আর শেয়ার্ড ভেরিয়েবল হলো হিসাব নিকাশ। প্রথমে থ্রেড ১ জিরো ডাটার সাথে ১ যোগ করে ১ বানিয়ে দিলো। এবার থ্রেড ২ এর উদয় হলো। সে এসে দেখলো ডাটার মান ১ হয়ে আছে। তাই থ্রেড ১ আর থ্রেড ২ রেসিং করে একই সাথে ডাটার সাথে ১ যোগ করলো। কিন্ত সমস্যা হলো দুইজনই যেহেতু একই সাথে ডাটা যোগ করেছে, তাহলে ডাটার মান আসলে কতো হবে? ২ নাকি ৩? বোঝা মুশকিল। এটা মাল্টি থ্রেডেড প্রোগ্রামিং এর বড় সমস্যা যা সফটওয়্যার ডেভলপারদেরকে হ্যান্ডেল করতে হয়।

মাল্টি-থ্রেডেড না হলে কিন্তু এক সাথে ডাটা রাইট করার রেস হতো না।

এই রেস কন্ডিশন এর সল্যুশন হলো থ্রেড লক, মিউটেক্স আর সেমাফোর। এগুলি খুব কমপ্লেক্স জিনিস যা প্রোগ্রামকে কমপ্লেক্স করে দেয়। যেহেতু আমাদের আজকের টপিক এগুলি না তাই আমি এগুলি আর ব্যাখ্যা করলাম না।

তবে এগুলির মূল কনসেপ্ট হচ্ছে একটা থ্রেড যখন শেয়ার্ড মেমোরিতে ডাটা রাইট করবে তখন সে লক করে দিবে, এতে করে তার কাজ শেষ না হওয়া পর্যন্ত অন্য থ্রেড গুলি অপেক্ষা করবে। এতে করে মাল্টি-থ্রেডিং এর সুবিধা পূর্নভাবে নেয়া যাচ্ছে না কারণ অপেক্ষা করতে গিয়ে সময় নষ্ট হয়। আর থ্রেড লকের আরেক অসুবিধা হলো একটা থ্রেড যদি লক করে অনেক সময় নিয়ে ফেলে তাহলে অন্য থ্রেডগুলি আর সুযোগ পাবে না, যাকে বলে থ্রেড স্টারভেশন

এগুলির মূল কারণ হলো থ্রেড সমূহের মাঝে মেমরি শেয়ার করার কারণে। কিন্ত মেমোরি শেয়ার করা ছাড়া থ্রেড সমূহের মাঝে ডাটা শেয়ার হবে কিভাবে?

এগুলির সমাধান হলো থ্রেড সমূহের মাঝে মেসেজের মাধ্যমে ডাটা বিনিময় করা। যাকে বলে মেসেজ পাসিং

কিন্ত গো এর চেয়ে এগিয়ে। গো কনকারেন্সির মূল বিষয় হলঃ

Do not communicate by sharing memory

Share memory by communicating

অর্থাৎ একটা ডাটাকে অনেক থ্রেড এর মাঝে শেয়ার করার বদলে থ্রেড সমূহের মাঝে কমিউনকেশনের মাধ্যমে ডাটা শেয়ার করা।

থ্রেড সমূহের মাঝে এই কমিউনিকেশনের মাধ্যমকে বলা হয় চ্যানেল।

চ্যানেলের মাধ্যমে থ্রেড সমূহের মাঝে ডাটা শেয়ার করা হয়। এর ফলে রেস কন্ডিশন আর হয় না। আবার অনেক সময় ধরে থ্রেড লকও করতে হয় না, ফলে প্রোগ্রাম অনেক ফাস্ট হয়। তবে গো তে আছে চ্যানেল লক করা। আসুন ব্যাখ্যা করিঃ

এখানে দেখুন (১) এ একটা গোরুটিন, চ্যানেলে ডাটা পাঠানোর জন্য প্রস্তুতি নিচ্ছে আর অন্য থ্রেড নিজের মত কাজ করছে। (২) এ দেখতে পাচ্ছি গোরুটিন ১ চ্যানলে ডাটা রাখলো, (৩) এ দেখতে পাচ্ছি গোরুটিন ২ ও চ্যানেলে ঢুকেছে ডাটা নেওয়ার জন্য। (৪) এ দেখতে পাচ্ছি গোরুটিন দুইটার মাঝে ডাটা শেয়ারিং হচ্ছে। (৫) এ দেখতে পাচ্ছি গোরুটিন ২ ডাটা নিয়ে নিয়েছে। (৬) এ দেখতে পাচ্ছি দুইটা গোরুটিনই চ্যানেল থেকে বের হয়ে গেছে।

খেয়াল করে দেখুন গোরুটিন ১ যখন চ্যানেলে ডাটা রাখতে যাচ্ছে তখন থেকে শুরু করে গোরুটিন ২ সেই ডাটা না নেওয়া পর্যন্ত চ্যানেলেই অপেক্ষা করেছে সব কাজ থামিয়ে। আর চ্যানেলে ডাটা আসার সাথে সাথে গোরুটিন ২ ও সব কাজ থামিয়ে চ্যানেলে ঢুকেছে। সুতরাং আমরা দেখতে পাচ্ছি কোনও গোরুটিন চ্যানেলে ডাটা সেন্ড করলে আর চ্যানেলে কোনো ডাটা থেকে গেলে সব গোরুটিন কাজ করা থামিয়ে দেয়, যেটাকে বলে চ্যানেল লক। ট্র্যাডিশনাল থ্রেড লকের ক্ষেত্রে একটা থ্রেডের কাজ শেষ না হওয়া পর্যন্ত অন্য থ্রেডগুলি অপেক্ষা করতো, এমনকি সুযোগ না পেয়ে স্টার্ভেশনে ভুগত। কিন্ত এখানে চ্যানেলে ডাটা সেন্ড আর রিসিভ করার মাঝের সময়টুকুই শুধু লক থাকে। আর চ্যানেলে কোনো গোরুটিন ডাটা পাঠালে অন্য গোরুটিন সেটাকে রিসিভ করতে একদমই সময় লাগে না।

তবে খেয়াল রাখার বিষয় হলো একটা চ্যানেল দিয়ে দুইটা গোরুটিনের মাঝে সেন্ড-রিসিভ বানানো যায়। কারণ একটা গোরুটিন যখন চ্যানালের এক প্রান্তে ডাটা সেন্ড করে তখন অন্য প্রান্তের গোরুটিন সেই চ্যানেলের ডাটা রিসিভ করতে গিয়ে ব্লক করে, তাই অন্য কোনও গোরুটিন সেই চ্যানেল থেকে ডাটা পাবে না। আর স্বাভাবিক ভাবেই একটা চ্যানেল বা পাইপের দুই প্রান্তে তো মাত্র দুজনই থাকতে পারে। মাল্টিপল গোরুটিন এর জন্য মাল্টিপল চ্যানেল দরকার হয়।

তাহলে আমরা বলতে পারি চ্যানেল হলো গোরুটিন সমূহের মাঝে ডাটা আদান প্রদানের একটা মাধ্যম।

এবার আসুন আমরা প্রোগ্রামিং এ যাই।

ইমপ্লিমেন্টেশন

প্রথমেই আমরা একটি নন-কনকারেন্ট প্রোগ্রাম দেখিঃ

এখানে দেখুন আমরা একটি ফাংশন বানিয়েছি যেটি ১ সেকেন্ড পরপর প্রিন্ট করবে ইনফিনিট লুপে।

আমরা এই ফাংশনকে দুইবার কল করেছি। রান করলে দেখবেন শুধু প্রথম কলই রান হচ্ছে, দ্বিতীয় কল আর সুযোগ পাচ্ছে না। কারণ প্রথম কল রান হবার পরপরই এটা একটা ইনফিনিট লুপে পড়ে গেছে, যার ফলে দ্বিতীয় কল আর সুযোগ পাবে না কখনো।

এটা হচ্ছে সিক্যুয়েন্সিয়াল প্রোগ্রামিং। আমরা এবার কনকারেন্ট প্রোগ্রামিং করবো ফাংশনের দুইবার কলকে দুইটা থ্রেডে বা গোরুটিনে রান করবো। গো তে কনকারেন্সি খুবই সহজ। অন্যান্য ল্যাঙ্গুয়েজে থ্রেড স্পন করতে চাইলে আপনাকে অনেক কমপ্লেক্সিটিতে যেতে হবে। কিন্ত গো তে গোরুটিন বানাতে চাইলে রুটিনবা ফাংশনের নামের শুরুতে শুধু go লিখে দিলেই হবে। তাহলে আসুন আমরা কনকারেন্ট দেখিঃ

রান করে ম্যাজিক দেখুন, দেখবেন কোনো আউটপুট পাবেন না 😐

এর কারণ কি?

প্রথমেই বলেছিলাম গো এর মাঝে কনকারেন্সি বিল্ট-ইন অবস্থায় আছে। তাই গো এর main() ফাংশন নিজেও একটা গোরুটিন। প্রতিটা গোরুটিন আলাদা আলাদাভাবে কাজ করে। তাই আমরা যখন দুইটা গোরুটিন রান করেছি সাথে ছিল আমাদের মেইন ফাংশনের গোরুটিন। তাই গোরুটিনগুলি স্পন করার পরেই মেইন ফাংশন শেষ হয়ে গেছে। আর মেইন ফাংশন শেষ হওয়া মানেই আমাদের প্রোগ্রাম শেষ তাই বাকি গোরুটিন আর শো করেনি।

এর জন্য আমরা কয়েকটা কাজ করতে পারি। সহজভাবেই শুরু করি। আমরা আমাদের মেইন গোরুটিনকে অপেক্ষা করাবো আমাদের যতক্ষণ সময় দরকার ঠিক ততক্ষণ ধরে। এতে করে আমরা আবকি দুইটা গোরুটিন এর আউটপুট দেখতে পাবোঃ

আমরা আমাদের মেইন ফাংশনকে ১০ সেকেন্ড অপেক্ষা করিয়েছি। এই ১০ সেকেন্ডে আমরা দেখবো যে আমাদের কল করা দুইটা গোরুটিন একসাথে আউটপুট দিচ্ছে

এছাড়াও আমরা আরেকটু এডভান্সড লেভেলের ওয়েট গ্রুপ দিয়েও একই কাজ করতে পারি।

তাহলে আমরা দেখলাম গো তে কনকারেন্সি অনেক পাওয়ারফুল, কম্পেক্ট আর খুবই সহজ ইমপ্লিমেন্ট করা।

এবার আমরা দেখবো চ্যানেল এর ইমপ্লিমেন্টেশনঃ

এখানে আমরা একটা চ্যানেল বানিয়েছি make() ইনিশিয়ালাইজার ফাংশন দিয়ে। এরপর আমরা মেইন গোরুটিন থেকে আরেকটা গোরুটিন স্পন করেছি এবং এই দুইটা গোরুটিন এর মাঝে চ্যানেল তৈরি করে দিয়েছি। এরপর আমরা মেইন গোরুটিন থেকে চ্যানেলের মাধ্যমে ডাটা পাঠিয়েছি, যেটা অপর গোরুটিন গ্রহণ করেছে। এরপর আমরা অপর গোরুটিন থেকে মেইন গোরুটিনে আবার ডাটা পাঠিয়েছি, যা মেইন গোরুটিন গ্রহণ করেছে।

তাহলে দেখতেই পাচ্ছেন চ্যানেলের মাধ্যমে গোরুটিন এর মাঝে কিভাবে আমরা ডাটা আদান-প্রদান করতে পারি।

এগুলি ছিল গো এর বেসিক কনকারেন্সি। এছাড়াও আরও অনেক এডভান্সড কিছু আছে গো এর কনকারেন্সি তে। সেগুলি আরেকদিন আলোচনা করবো।

আল্লাহ হাফেজ।

--

--

Cyan Tarek
প্রোগ্রামিং পাতা

Software Engineer, Backend Ninja, DevOps Player, Microservice learner, Gopher/Golang Lover