C++ এর mutex এবং locking mechanisms মাল্টি-থ্রেডিং প্রোগ্রামে থ্রেড সেফটি নিশ্চিত করতে গুরুত্বপূর্ণ ভূমিকা পালন করে। যখন একাধিক থ্রেড একসাথে শেয়ারড ডেটার সাথে কাজ করে, তখন mutex (mutual exclusion) ব্যবহৃত হয় যাতে একই সময়ে একাধিক থ্রেড একই ডেটা অ্যাক্সেস করতে না পারে এবং race conditions (যেখানে একাধিক থ্রেড একই ডেটা পরিবর্তন করার চেষ্টা করে) এড়ানো যায়।
C++11-এ std::mutex, std::lock_guard, এবং std::unique_lock সহ থ্রেড সিঙ্ক্রোনাইজেশন এবং ডেটা সুরক্ষার জন্য কার্যকরী মেকানিজম প্রদান করা হয়েছে। এগুলো একে অপরের সাথে কাজ করে এবং থ্রেড সেফ অপারেশন নিশ্চিত করতে সাহায্য করে।
১. std::mutex
std::mutex হল একটি mutex (mutual exclusion) ক্লাস যা থ্রেড সিঙ্ক্রোনাইজেশনের জন্য ব্যবহৃত হয়। এটি একটি রিসোর্স বা ডেটা স্ট্রাকচারকে এক সময়ে একটি থ্রেডের কাছে লক (lock) করে রাখে এবং অন্যান্য থ্রেডগুলিকে সেই রিসোর্স অ্যাক্সেস করতে দেয় না। যখন এক থ্রেড একটি mutex লক করে, তখন অন্য থ্রেডের জন্য এটি আনলক হওয়া পর্যন্ত সেই রিসোর্স অ্যাক্সেস করা সম্ভব হয় না।
উদাহরণ (std::mutex):
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
void print_numbers(int id) {
mtx.lock(); // lock the mutex to ensure exclusive access
std::cout << "Thread " << id << " is printing numbers" << std::endl;
mtx.unlock(); // unlock the mutex after work is done
}
int main() {
std::thread t1(print_numbers, 1);
std::thread t2(print_numbers, 2);
t1.join();
t2.join();
return 0;
}আউটপুট:
Thread 1 is printing numbers
Thread 2 is printing numbersএখানে, std::mutex mtx ব্যবহার করে দুটি থ্রেডের মধ্যে সিঙ্ক্রোনাইজেশন নিশ্চিত করা হয়েছে যাতে তারা একে অপরকে ওভারল্যাপ না করে একই রিসোর্স অ্যাক্সেস করতে পারে।
২. std::lock_guard
std::lock_guard একটি "RAII" (Resource Acquisition Is Initialization) ক্লাস যা স্বয়ংক্রিয়ভাবে mutex লক এবং আনলক করে। যখন আপনি একটি std::lock_guard অবজেক্ট তৈরি করেন, এটি একটি mutex লক করে এবং অবজেক্টটি ডেস্ট্রয় হলে স্বয়ংক্রিয়ভাবে mutex আনলক করে। এটি exception safety নিশ্চিত করতে সহায়ক, কারণ যদি কোনো এক্সসেপশন ঘটে, তখনও mutex আনলক হবে।
উদাহরণ (std::lock_guard):
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
void print_numbers(int id) {
std::lock_guard<std::mutex> lock(mtx); // lock_guard will lock and unlock automatically
std::cout << "Thread " << id << " is printing numbers" << std::endl;
}
int main() {
std::thread t1(print_numbers, 1);
std::thread t2(print_numbers, 2);
t1.join();
t2.join();
return 0;
}আউটপুট:
Thread 1 is printing numbers
Thread 2 is printing numbersএখানে, std::lock_guard স্বয়ংক্রিয়ভাবে mutex লক এবং আনলক করছে। এতে কোডটি সহজ ও নিরাপদ হয়, কারণ আপনার হাতে লক আনলক করার জন্য কোনো বিশেষ ধাপ নিতে হয় না।
৩. std::unique_lock
std::unique_lock একটি আরও উন্নত ক্লাস যা mutex লক করার জন্য ব্যবহৃত হয়, তবে এটি std::lock_guard এর তুলনায় আরও নমনীয়। এটি std::mutex এর সাথে যুক্ত হলে আপনি কিছু অতিরিক্ত সুবিধা পাবেন:
- ডেলেইড লকিং: এটি কিছু সময়ের জন্য লক করতে পারে না, যা
std::lock_guardএর ক্ষেত্রে সম্ভব নয়। - ডাইনামিক লকিং এবং আনলকিং: এটি লক এবং আনলক অপারেশনকে ল্যাজিক্যালি পৃথক করতে দেয়।
- মাল্টিপল মিউটেক্স লকিং: একাধিক mutex লক করতে হলে
std::unique_lockআরও সুবিধাজনক হতে পারে।
উদাহরণ (std::unique_lock):
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
void print_numbers(int id) {
std::unique_lock<std::mutex> lock(mtx); // lock the mutex
std::cout << "Thread " << id << " is printing numbers" << std::endl;
lock.unlock(); // manually unlock the mutex before the function ends
}
int main() {
std::thread t1(print_numbers, 1);
std::thread t2(print_numbers, 2);
t1.join();
t2.join();
return 0;
}আউটপুট:
Thread 1 is printing numbers
Thread 2 is printing numbersএখানে, std::unique_lock একটি mutex লক করেছে, কিন্তু আপনি চাইলে unlock() ফাংশনটি ব্যবহার করে এটি আনলক করতে পারেন আগেই। এটি বিশেষভাবে দরকারী যখন আপনি লক করা রিসোর্সের কিছু অংশে কাজ করতে চান এবং পরে আবার লক করতে চান।
৪. std::lock()
std::lock() ফাংশনটি একাধিক mutex একযোগভাবে লক করতে ব্যবহৃত হয়। এটি একটি ডেডলক এড়াতে সাহায্য করে, কারণ এটি একযোগে সমস্ত mutex লক করে এবং কোনও mutex আনলক করার আগে অন্য কোনও mutex লক করার চেষ্টা করে না।
উদাহরণ (std::lock):
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx1, mtx2;
void func1() {
std::lock(mtx1, mtx2); // Lock both mutexes simultaneously
std::lock_guard<std::mutex> lg1(mtx1, std::adopt_lock);
std::lock_guard<std::mutex> lg2(mtx2, std::adopt_lock);
std::cout << "func1 has locked both mtx1 and mtx2" << std::endl;
}
void func2() {
std::lock(mtx1, mtx2); // Lock both mutexes simultaneously
std::lock_guard<std::mutex> lg1(mtx1, std::adopt_lock);
std::lock_guard<std::mutex> lg2(mtx2, std::adopt_lock);
std::cout << "func2 has locked both mtx1 and mtx2" << std::endl;
}
int main() {
std::thread t1(func1);
std::thread t2(func2);
t1.join();
t2.join();
return 0;
}আউটপুট:
func1 has locked both mtx1 and mtx2
func2 has locked both mtx1 and mtx2এখানে, std::lock ফাংশনটি দুটি mutex (mtx1 এবং mtx2) একযোগভাবে লক করেছে, যা ডেডলক থেকে মুক্ত থাকতে সাহায্য করেছে।
উপসংহার
std::mutex: সাধারণ mutex যা এক থ্রেডকে লক করে অন্য থ্রেডদের লক থেকে বিরত রাখে।std::lock_guard: RAII স্টাইল mutex লকিং ক্লাস যা স্বয়ংক্রিয়ভাবে mutex লক এবং আনলক করে।std::unique_lock: আরও নমনীয় mutex লকিং ক্লাস যা manual unlocking এবং delay locking সমর্থন করে।std::lock: একাধিক mutex একযোগভাবে লক করতে ব্যবহৃত হয় এবং ডেডলক প্রতিরোধে সাহায্য করে।
এই লকিং মেকানিজমগুলোর মাধ্যমে মাল্টি-থ্রেড প্রোগ্রামে সিঙ্ক্রোনাইজেশন সহজ এবং নিরাপদ করা যায়।
Read more