Trong bài viết này, chúng ta sẽ thảo luận về cách sử dụng khóa mutex để bảo vệ dữ liệu được chia sẻ trong môi trường đa luồng và tránh các điều kiện cuộc đua.
Để khắc phục điều kiện cuộc đua trong môi trường đa luồng, chúng ta cần mutex. Tức là mỗi luồng cần khóa một mutex trước khi sửa đổi hoặc đọc dữ liệu chia sẻ. au khi sửa đổi dữ liệu, mỗi luồng sẽ mở khóa mutex.
std::mutex
Trong thư viện về thread của C++11, mutex nằm trong file header <mutex>. Lớp đại diện cho một mutex là lớp std::mutex. Có hai phương thưc quan trọng của mutex:
1. lock()
2. unlock()
Ta sẽ sử dụng mutex để fix bài toán Multithreaded Wallet đã đề cập trong phần trước.
Vì, Wallet cung cấp dịch vụ để thêm tiền trong Wallet và cùng một đối tượng Wallet được sử dụng giữa các thread khác nhau, vì vậy chúng ta cần thêm khóa trong phương thức addMoney() của Wallet. Trước tiên lấy khóa sau đó tăng tiền mMoney của đối tượng Wallet theo số lượng được chỉ định và sau đó trả lại khóa. Hãy xem đoạn code bên dưới,
#include<iostream>
#include<thread>
#include<vector>
#include<mutex>
class Wallet
{
int mMoney;
std::mutex mutex;
public:
Wallet() :mMoney(0){}
int getMoney() { return mMoney; }
void addMoney(int money)
{
mutex.lock();
for(int i = 0; i < money; ++i)
{
mMoney++;
}
mutex.unlock();
}
};
Hãy chạy lại chương trình với lớp Wallet được thêm vào mutex. Nếu số tiền ban đầu trong ví là 0 thì sau khi hoàn thành tất cả các thread, số tiền sẽ là 5000. Khóa mutex này đảm bảo rằng số tiền trong Ví là 5000. Khóa mutex trong addMoney đảm bảo rằng chỉ khi một thread hoàn thành việc sửa đổi tiền thì một thread khác mới được sửa đổi.
Nhưng, điều gì sẽ xảy ra nếu chúng ta quên mở khóa mutex ở cuối. Trong trường hợp như vậy, một thread sẽ thoát mà không giải phóng khóa và các thread khác sẽ vẫn chờ. Loại kịch bản này có thể xảy ra trong trường hợp một số ngoại lệ xuất hiện sau khi khóa mutex. Để tránh những tình huống như vậy, chúng ta nên sử dụng std::lock_guard.
std::lock_guard
std::lock_guard là một lớp template thực hiện RAII cho mutex. Chi tiết về RAII có thể xem lại trong phần 2 của series này. Nó bao bọc mutex bên trong đối tượng cũng như khóa mutex đính kèm trong hàm tạo của nó. Khi hàm hủy được gọi, nó sẽ giải phóng mutex.
class Wallet
{
int mMoney;
std::mutex mutex;
public:
Wallet() :mMoney(0){}
int getMoney() { return mMoney; }
void addMoney(int money)
{
std::lock_guard<std::mutex> lockGuard(mutex);
// Mutex được khóa trong hàm constructor
for(int i = 0; i < money; ++i)
{
// Nếu có ngoại lệ xảy ra lúc này
// thì hàm hủy của lock_guard
// sẽ được gọi do stack unwinding.
//
mMoney++;
}
// Một khi thoát khỏi hàm, thì
// hàm hủy của lock_guard cũng sẽ được gọi.
// Mutex được mở khóa trong hàm destructor.
}
};
Để lại một bình luận