std::lock_guard 似乎提供了线程安全性,尽管作用域块

std::lock_guard seems to give thread safety despite scoped block

本文关键字:安全性 线程 作用域 lock guard std      更新时间:2023-10-16
mutex m;
void thread_function()
{
static int i = 0;
for(int j =0; j<1000; j++)
{
{ //this scope should make this ineffective according to my understanding
lock_guard<mutex> lock(m);
}
i++;
cout<<i<<endl;
}
}

我从 2 个线程调用此函数。因此,如果函数以线程安全的方式运行,则 int i 的期望值为 1000*2 = 2000。

如果没有互斥锁,结果从 1990 年到 2000 年变化,正如打印 i 时所预期的那样(由于非原子 int i(。在没有作用域块的情况下插入锁保护装置可防止这种情况。

但是,根据我的理解,在其周围使用作用域块应该使其立即获取并释放锁,因此在写入 int i 时不再有线程安全。但是,我注意到我总是2000。我是不是误会了什么?

你的理解是正确的,结果总是2000可能取决于机器。原因可能是计算机上i++语句之前的同步恰好总是导致线程在足够距离内执行它以避免争用条件。但是,如前所述,这并不能保证。

正如其他人已经告诉您的那样,问题是您的i++非常接近线程同步,以至于它(几乎总是(设法在线程被抢占之前进行更新。

如果我们像这样更改您的程序:

#include <iostream>
#include <thread>
#include <mutex>
std::mutex m;
void thread_function()
{
static int i = 0;
for (int j = 0; j < 1000; j++)
{
{ //this scope should make this ineffective according to my understanding
std::lock_guard<std::mutex> lock(m);
}
std::cout << "Printing some stuff. Weee!" << std::endl; // <- New line
i++;
std::cout << i << std::endl;
}
}
int main()
{
std::thread t1(thread_function);
std::thread t2(thread_function);
t1.join();
t2.join();
return 0;
}

然后有时两个线程的总和将不再是 2000。与锁不存在时相比,您仍然会更少地遇到竞争条件,但这恰恰表明了竞争条件和未定义行为的主要危险:您的程序实际上可以在大部分时间工作,即使根据标准它是严格错误的。