让我们考虑两个或多个线程和一个资源。如果相关,我在Ubuntu上使用C ++ 11。 以下代码说明了这种情况:
#include <thread>
#include <mutex>
class Res
{
//Data
};
void use_resource(const Res& rsc) {/*Do stuff*/}
void modify_resource(Res& rsc) {/*Modify the resource*/}
class A
{
Res resource;
std::mutex resource_mtx;
std::thread thd;
public:
A()
{
thd = std::thread(&A::loop,this);
}
void loop()
{
while(true)
{
use_resource(resource); //(Case 1)
//Some work
{
std::lock_guard<std::mutex> mlock(resource_mtx);
modify_resource(resource); //(Case 2)
}
}
}
Res get_resource()
{
std::lock_guard<std::mutex> mlock(resource_mtx);
return resource; //(Case 3)
}
};
int main()
{
A a;
while(true)
{
Res res1 = a.get_resource();
//Do stuff with the resource
}
}
我们有一个包含一些数据的资源。 A中的函数 loop()仅在第一个线程上运行。其他线程可以调用 get_resource()来访问资源。只能通过功能 modified_resource 修改资源。
我的理解是,情况2和情况3需要锁定,因为情况2涉及写操作,情况3涉及从另一个线程读取。
我想知道的是,在情况1中是否需要锁定。从cppreference.com看来,数据竞争的定义是:
当表达式的求值写入存储位置时, 另一个评估读取或修改了相同的存储位置, 据说表达冲突。有两个冲突的程序 评估具有数据竞争,除非:
i)两个评估都在同一线程或同一信号处理程序上执行,或者
ii)两个冲突的评估都是原子操作(请参阅std :: atomic),或者
iii)发生冲突的评估之一发生在另一评估之前(请参阅std :: memory_order)
我认为这些定义中的案例1没有数据竞争:
1)和2)(它们在同一线程中,i)之间没有冲突。
1)和3)(没有一个是写操作)之间没有冲突。
这使我想到了两个问题:
Q1)在提供的特定代码中,是否需要通过锁定 resource_mtx 来保护对 use_resource (案例1)的调用,以避免数据争用?
Q2)是否可以在 loop()函数中以任意顺序或数字重复这种情况1和2?一个任意的例子是:
while(true)
{
{
std::lock_guard<std::mutex> mlock(resource_mtx);
modify_resource(resource); //(Case 2)
}
use_resource(resource); //(Case 1)
{
std::lock_guard<std::mutex> mlock(resource_mtx);
modify_resource(resource); //(Case 2)
}
use_resource(resource); //(Case 1)
use_resource(resource); //(Case 1)
{
std::lock_guard<std::mutex> mlock(resource_mtx);
modify_resource(resource); //(Case 2)
}
}
如上所述,我的猜测是在两种情况下都不需要锁定,但是我很可能会丢失某些东西,例如编译器重新排序甚至是数据争用的实际定义。
到目前为止,我仅看到有关来自单独线程的只读访问的问题,这是我的情况2(there和there)或两个不同线程之间的交互({{3} }),但我还没有看到在同一线程中进行只读访问的特定问题。
编辑:编辑代码以实际反映多线程方案。
答案 0 :(得分:2)
在您的示例中,您是“安全的”(假设正确的Res
)。
当2)
(写操作)与1)
/ 2)
或3)
(不同步)同时执行时,会发生问题。
1)
和3)
可能同时发生(只读)。
1)
和2)
在同一线程中,因此不能与2)
同时发生。
3)
和2)
受mutex
保护。
请注意,如果您不在3)
中创建副本但返回引用,则3)
mutex
不会被扩展为正确的范围。