我们知道,正确使用std :: lock_guard就像这种RAII样式:
void increase_decrease() {
std::lock_guard<std::mutex> guard(global_mutex);
static const int times = 50;
for (int i = 0; i < times; i++) {
global_data ++;
}
for (int i = 0; i < times; i++) {
global_data --;
}
}
在这里,我的重点是不关于如何使用std::lock_guard
或互斥锁。
在下面的代码中,我们故意以错误的方式使用std::lock_guard
。 (也就是说,将其放在关键部分之前的一个块中。)
创建16个线程,以将全局int变量加1并从中减去1,该变量被初始化为0,持续50次。
std::lock_guard
在一个块中被调用,并且该块在关键部分之前(错误!切勿执行此类操作!)。遵循RAII-style机制,互斥锁将在阻止之后再次释放(用法错误)。因此,当它进入关键部分时,就没有锁了。
#include <iostream>
#include <chrono>
#include <thread>
#include <mutex>
#include <vector>
int global_data = 0;
std::mutex global_mutex;
void increase_decrease() {
// XXX: INCORRECT USAGE! INCORRECT USAGE! INCORRECT USAGE!
{
std::lock_guard<std::mutex> guard(global_mutex);
}
// // XXX: uncomment to sleep for a litter while
// std::this_thread::sleep_for(std::chrono::milliseconds(10));
static const int times = 50;
for (int i = 0; i < times; i++) {
global_data ++;
}
for (int i = 0; i < times; i++) {
global_data --;
}
}
void try_mutex() {
const int num_workers = 16;
std::vector<std::thread> workers;
auto start = std::chrono::system_clock::now();
for (int i = 0; i < num_workers; i++) {
std::thread t(increase_decrease);
workers.push_back(std::move(t));
}
for (auto &t: workers) {
t.join();
}
auto end = std::chrono::system_clock::now();
std::chrono::duration<double> elapsed_seconds = end-start;
std::cout << "global_data: " << global_data
<< ", elapsed second: " << elapsed_seconds.count();
}
int main() {
try_mutex();
}
我发现睡眠10毫秒是否会导致不同的结果。
不睡觉,主呼叫的20个标准输出为:
global_data: 0, elapsed second: 0.000363
global_data: 0, elapsed second: 0.000359
global_data: 0, elapsed second: 0.000349
global_data: 0, elapsed second: 0.000345
global_data: 0, elapsed second: 0.000352
global_data: 0, elapsed second: 0.000323
global_data: 0, elapsed second: 0.000619
global_data: 0, elapsed second: 0.000431
global_data: 34, elapsed second: 0.000405
global_data: -14, elapsed second: 0.000415
global_data: 0, elapsed second: 0.000497
global_data: 0, elapsed second: 0.000366
global_data: 0, elapsed second: 0.000413
global_data: 0, elapsed second: 0.000406
global_data: 0, elapsed second: 0.000353
global_data: 0, elapsed second: 0.000363
global_data: 0, elapsed second: 0.000361
global_data: 0, elapsed second: 0.000358
global_data: 0, elapsed second: 0.000348
global_data: 0, elapsed second: 0.000367
但是,如果我们取消睡眠注释,则main的20个调用的标准输出为:
global_data: 44, elapsed second: 0.011108
global_data: 15, elapsed second: 0.010645
global_data: 25, elapsed second: 0.012905
global_data: 27, elapsed second: 0.012914
global_data: 9, elapsed second: 0.012871
global_data: 46, elapsed second: 0.012836
global_data: 44, elapsed second: 0.011307
global_data: -2, elapsed second: 0.01286
global_data: 77, elapsed second: 0.012853
global_data: 43, elapsed second: 0.011984
global_data: 0, elapsed second: 0.011134
global_data: -3, elapsed second: 0.011571
global_data: 49, elapsed second: 0.012438
global_data: 43, elapsed second: 0.011552
global_data: -20, elapsed second: 0.010807
global_data: 0, elapsed second: 0.010514
global_data: 0, elapsed second: 0.010916
global_data: -44, elapsed second: 0.012829
global_data: 50, elapsed second: 0.011759
global_data: 9, elapsed second: 0.012873
第一种情况下global_data
等于0
的可能性比第二种情况更大。我尝试了很多次。 不只是一个巧合。
因此,似乎互斥体有机会在通过std::lock_guard
获得的区块之后生效一会儿。为什么?
谢谢。
答案 0 :(得分:4)
std::lock_guard
在关键部分之前的块中调用。遵循RAII风格的机制,互斥锁应在阻止之后释放。
否,互斥锁会在您放入的块的末尾释放。而且那是在访问global_data
之前的。因此,它不受保护,这是未定义的行为。您可以通过未定义的行为看到所看到的结果,并且您不应该花费太多精力来理解这些结果。
如果您删除lock_guard
周围的括号,它将很好用。
答案 1 :(得分:3)
您在变量global_data
上进行了数据竞争(因为在开始操纵它之前先释放了它)。因此,运行代码的结果是不可预测的。
如果将global_data
从int
更改为std::atomic <int>
,则无论有无sleep
(全部为零),都将得到相同的输出。