Condition Variables এবং Atomic Operations হল C++-এ মাল্টি-থ্রেডিং প্রোগ্রামিংয়ের জন্য অত্যন্ত গুরুত্বপূর্ণ টুলস, যা থ্রেড সমন্বয় এবং মেমরি অ্যাক্সেস সমন্বয়কে সহজতর করে। এগুলি মূলত থ্রেড সিঙ্ক্রোনাইজেশন এবং থ্রেড নিরাপদ অপারেশন নিশ্চিত করতে ব্যবহৃত হয়।
১. Condition Variables
Condition Variable হল একটি সিঙ্ক্রোনাইজেশন টুল যা থ্রেডগুলোকে সমন্বয় করতে সাহায্য করে। এটি থ্রেডগুলোকে এটা জানতে দেয় কখন একটি নির্দিষ্ট শর্ত পূর্ণ হয়েছে, যাতে থ্রেডগুলো পরবর্তী পদক্ষেপে যেতে পারে। এটি বিশেষত তখন ব্যবহৃত হয় যখন এক থ্রেড অন্য থ্রেডের কোন অবস্থার অপেক্ষা করছে, যেমন এক থ্রেড একটি রিসোর্স ফ্রী হওয়ার জন্য অপেক্ষা করছে বা একটি নির্দিষ্ট শর্ত পূর্ণ হওয়ার জন্য অপেক্ষা করছে।
Condition Variable সাধারণত std::mutex এর সাথে ব্যবহৃত হয়, কারণ থ্রেডগুলোর মধ্যে সিঙ্ক্রোনাইজেশন নিশ্চিত করতে মিউটেক্স ব্যবহার করা হয়। এর মূল কাজ হচ্ছে থ্রেডগুলোর মধ্যে সিগন্যাল পাঠানো, যেমন একটি থ্রেড অপেক্ষা করছে এবং অন্য থ্রেড সেই শর্ত পূর্ণ হলে অপেক্ষমান থ্রেডকে অবহিত করবে।
std::condition_variable এর ফাংশন:
wait(): একটি থ্রেড তার লকটি মুক্ত করে এবং অন্য থ্রেডের সিগন্যালের জন্য অপেক্ষা করে।notify_one(): একটি থ্রেডকে সিগন্যাল পাঠায়, যার মাধ্যমে একটি থ্রেড অপেক্ষা করতে থাকা অবস্থায় চলতে শুরু করবে।notify_all(): সব থ্রেডকে সিগন্যাল পাঠায়, যারা শর্ত পূর্ণ হওয়ার জন্য অপেক্ষা করছে।
উদাহরণ:
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void print_id(int id) {
std::unique_lock<std::mutex> lck(mtx);
while (!ready) cv.wait(lck); // যখন ready false, থ্রেড অপেক্ষা করবে
std::cout << "Thread " << id << '\n';
}
void go() {
std::unique_lock<std::mutex> lck(mtx);
ready = true; // ready কে true করতে হবে
cv.notify_all(); // সব থ্রেডকে সিগন্যাল পাঠানো
}
int main() {
std::thread threads[10];
for (int i = 0; i < 10; ++i)
threads[i] = std::thread(print_id, i); // থ্রেড তৈরি করা
std::cout << "10 threads ready to race...\n";
go(); // থ্রেডগুলোকে চলতে বলা
for (auto& th : threads) th.join(); // থ্রেডগুলো শেষ হওয়া পর্যন্ত অপেক্ষা করা
return 0;
}আউটপুট:
10 threads ready to race...
Thread 0
Thread 1
Thread 2
Thread 3
Thread 4
Thread 5
Thread 6
Thread 7
Thread 8
Thread 9এখানে, cv.wait(lck) থ্রেডকে অপেক্ষা করতে বলে, যখন ready মানটি true হয়ে যায়, তখন cv.notify_all() তা থ্রেডগুলোকে অবহিত করে চলতে দেয়।
২. Atomic Operations
Atomic Operations হল এমন অপারেশন যেগুলি থ্রেড সেফ এবং একত্রে সমাপ্ত হয়। এর মানে হল যে, একাধিক থ্রেড যখন একটি ভেরিয়েবলের মান পরিবর্তন করতে চেষ্টা করে, তখন এটি শুধুমাত্র এক থ্রেডের দ্বারা সম্পন্ন হবে, অন্যথায় এটি প্রতিযোগিতামূলক অবস্থার সৃষ্টি করবে। C++11 থেকে std::atomic ক্লাসটি যুক্ত করা হয়েছে, যা এই ধরনের নিরাপদ অপারেশন করতে সাহায্য করে।
std::atomic ব্যবহার করে বিভিন্ন ডেটা টাইপ (যেমন, int, bool, pointer ইত্যাদি) অ্যাটমিকভাবে আপডেট করা যায়, এবং এটি সিঙ্ক্রোনাইজেশন সমস্যা এড়াতে সাহায্য করে।
std::atomic এর সাধারণ বৈশিষ্ট্য:
- অ্যাটমিক অপারেশন: একাধিক থ্রেডের মধ্যে ডেটা রেস কন্ডিশন (race condition) এড়ানোর জন্য এটোমিক অপারেশন ব্যবহার করা হয়।
- এটোমিক মান অ্যাক্সেস: এক থ্রেড দ্বারা অ্যাটমিক মান পরিবর্তন করা হলে, অন্য থ্রেড তা দেখতে পাবে (এটি একত্রে ঘটবে)।
উদাহরণ:
#include <iostream>
#include <thread>
#include <atomic>
std::atomic<int> count(0); // এটোমিক ভেরিয়েবল
void increment() {
for (int i = 0; i < 1000; ++i) {
++count; // এটোমিক ইনক্রিমেন্ট
}
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
std::cout << "Count: " << count.load() << std::endl; // এটোমিক মান লোড করা
return 0;
}আউটপুট:
Count: 2000এখানে, দুটি থ্রেড একসাথে count ভেরিয়েবলটি ইনক্রিমেন্ট করছে, তবে যেহেতু এটি std::atomic<int> টাইপ, এই অপারেশনটি সঠিকভাবে সিঙ্ক্রোনাইজড থাকে এবং কোনও রেস কন্ডিশন সৃষ্টি হয় না।
Atomic Operations এর সাধারণ ফাংশনসমূহ:
load(): এটোমিক ভেরিয়েবলের মান পড়ে।store(): এটোমিক ভেরিয়েবলে একটি নতুন মান সন্নিবেশ করে।fetch_add(): এটোমিক ভেরিয়েবলে একটি মান যোগ করে এবং পুরনো মানটি রিটার্ন করে।fetch_sub(): এটোমিক ভেরিয়েবলে একটি মান বিয়োগ করে এবং পুরনো মানটি রিটার্ন করে।compare_exchange_weak(): এটোমিক ভেরিয়েবলের মান তুলনা করে এবং যদি এটি প্রত্যাশিত মানের সমান হয় তবে নতুন মান সেট করে।
উপসংহার:
- Condition Variables: থ্রেড সমন্বয়ের জন্য ব্যবহৃত হয়, যখন একটি থ্রেড অন্য থ্রেডের শর্ত পূর্ণ হওয়ার জন্য অপেক্ষা করে।
- Atomic Operations: একাধিক থ্রেডের মধ্যে ডেটা রেস কন্ডিশন এড়াতে ব্যবহৃত হয় এবং একসাথে একাধিক থ্রেডের মধ্যে ডেটা অ্যাক্সেস সিঙ্ক্রোনাইজড রাখে।
এই দুটি ধারণা C++-এ মাল্টি-থ্রেডিং কার্যকরভাবে পরিচালনা করতে সহায়ক, এবং সিঙ্ক্রোনাইজেশন এবং সঠিক মেমরি অ্যাক্সেস নিশ্চিত করতে ব্যবহৃত হয়।
Read more