Trong môi trường đa luồng, chia sẻ dữ liệu giữa các luồng là rất dễ dàng. Nhưng việc chia sẻ dữ liệu dễ dàng này có thể gây ra vấn đề trong ứng dụng. Một vấn đề như vậy là điều kiện cuộc đua (race condition).
Race condition là gì
Race condition là tình huống xảy ra khi nhiều thread cùng truy cập và cùng lúc thay đổi dữ liệu. Đó có thể là một biến, một row trong database, một vùng shared data, memory , etc…. Vì thuật toán chuyển đổi việc thực thi giữa các thread có thể xảy ra bất cứ lúc nào, nên không thể biết được thứ tự của các thread truy cập và thay đổi dữ liệu. Điều này dẫn đến giá trị của data sẽ không như mong muốn. Kết quả sẽ phụ thuộc vào thuật toán thread scheduling của hệ điều hành. Quá trình các thread thực thi lệnh trông như một cuộc đua giữa các vận động viên điền kinh. Vì vậy, vấn đề này có thể liên tưởng đến thuật ngữ “Race condition”.
Một ví dụ của Race Condition
Hãy tạo một class Wallet có biến thành viên mMoney dùng để quản lý tiền và có phương thức addMoney(). Hàm này sẽ làm tăng giá trị của mMoney của đối tượng wallet một lượng cụ thể.
class Wallet
{
int mMoney;
public:
Wallet() :mMoney(0){}
int getMoney() { return mMoney; }
void addMoney(int money)
{
for(int i = 0; i < money; ++i)
{
mMoney++;
}
}
};
Bây giờ hãy tạo 5 thread và tất cả các thread này sẽ chia sẻ cùng một đối tượng của lớp Wallet và thêm 1000 vào mMoney bằng cách sử dụng song song hàm thành viên addMoney(). Vì vậy, nếu số tiền ban đầu trong ví là 0. Sau đó, sau khi hoàn thành thực hiện tất cả các thread, số tiền nên là 5000. Nhưng, vì tất cả các thread đang sửa đổi dữ liệu được chia sẻ cùng lúc, có thể trong một số trường hợp tiền trong ví sẽ ít hơn nhiều so với 5000.
int testMultithreadedWallet()
{
Wallet walletObject;
std::vector<std::thread> threads;
for(int i = 0; i < 5; ++i){
threads.push_back(std::thread(&Wallet::addMoney, &walletObject, 1000));
}
for(int i = 0; i < threads.size() ; i++)
{
threads.at(i).join();
}
return walletObject.getMoney();
}
int main()
{
int val = 0;
for(int k = 0; k < 1000; k++)
{
if((val = testMultithreadedWallet()) != 5000)
{
std::cout << "Error at count = "<<k<<" Money in Wallet = "<<val << std::endl;
}
}
return 0;
}
Vì hàm thành viên addMoney() của cùng một đối tượng allet được thực thi 5 lần, do đó, số tiền dự kiến là 5000. Nhưng vì hàm addMoney() được thực thi song song do đó trong một số trường hợp, mMoney sẽ ít hơn 5000 nhừ kết quả bên dưới,
Error at count = 184 Money in Wallet = 4888
Error at count = 230 Money in Wallet = 4811
Error at count = 515 Money in Wallet = 4000
Error at count = 675 Money in Wallet = 4532
Error at count = 719 Money in Wallet = 4844
Error at count = 746 Money in Wallet = 4000
Error at count = 756 Money in Wallet = 4692
Đây chính là một điều kiện cuộc đua, vì ở đây hai hoặc nhiều thread đã cố gắng sửa đổi cùng một vị trí bộ nhớ cùng lúc và dẫn đến kết quả không mong muốn.
Tại sao điều này xảy ra?
Mỗi thread sẽ cùng tăng giá trị cho biến thành viên mMoney cùng một lúc. Mặc dù có vẻ là một dòng lệnh duy nhất nhưng mMoney++; này thực sự được chuyển đổi thành ba lệnh máy,
- Nạp giá trị mMoney vào thanh ghi
- Tăng giá trị thanh ghi
- Đưa giá trị thanh ghi trở lại cho mMoney
Bây giờ giả sử trong một kịch bản đặc biệt, thứ tự thực hiện các lệnh trên như sau,
Trong trường hợp này, việc tăng một giá trị đã bị bỏ qua. Thay vì giá trị được tăng lên là 2, nhưng do các thanh ghi khác nhau cùng thực hiện tăng giá trị lên 1 của cùng mMoney nên giá trị của biến này bị ghi đè. Giả sử, giá trị mMoney trước lúc thực thi kịch bản này là 40 thì giá trị của nó nên được tăng lên 2, tức 42. Nhưng vì điều kiện cuộc đua, nên giá trị nhận được chỉ là 41.
Trong phần tiếp theo ta sẽ tìm hiểu các giải quyết vấn đề này bằng các sử dụng mutex.
Để lại một bình luận