Let us consider two or more threads, and a resource. Should this be relevant, I am using C++11 on Ubuntu. The situation is illustrated by the following code:
#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
}
}
We have a resource that contains some data. The function loop() in A runs on the first thread only. The other threads can call get_resource() to get access to the resource. The resource can only be modified by the function modified_resource .
My understanding is that there is a need for a lock in cases 2 and 3, since case 2 involves a write, and case 3 a read from another thread.
What I would like to know is if there is a need for a lock in case 1. From cppreference.com , the definition of a data race seems to be:
When an evaluation of an expression writes to a memory location and another evaluation reads or modifies the same memory location, the expressions are said to conflict. A program that has two conflicting evaluations has a data race unless:
i) both evaluations execute on the same thread or in the same signal handler, or
ii) both conflicting evaluations are atomic operations (see std::atomic), or
iii) one of the conflicting evaluations happens-before another (see std::memory_order)
I would think that there is no data race for case 1 in my code from those definitions:
There is no conflict between 1) and 2) (they are on the same thread, i) applies).
There is no conflict between 1) and 3) (none of them is a write).
This leads me to my two questions:
Q1) In the specific code provided, is there a need to protect the call to use_resource (Case 1) by a lock on resource_mtx to avoid a data race ?
Q2) Same question if this time cases 1 and 2 can be repeated, in any order or number, in the loop() function ? An arbitrary example would be:
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)
}
}
As said above, my guess would be that there is no need for a lock in both cases, but I might very well be missing something, like compiler reordering or even the actual definition of a data race.
So far, I have only seen questions about read only access from a separate thread, which is my case 2, ( there and there ) or interactions between two different threads ( there ), but I have not seen this specific problem of read only access in the same thread.
EDIT: Edited the code to actually reflect a multithreaded scenario.
In your sample you are "safe" (assuming correct Res
).
Problematic cases happen when 2)
(the write operation) would execute concurrently with 1)
/ 2)
or 3)
(without synchronization). 1)
and 3)
might happens concurrently, as read only.
1)
and 2)
are in the same thread, so cannot happen concurrently with 2)
. 3)
and 2)
are protected by mutex
.
Note that if in 3)
, you don't create copy but return reference, then 3)
mutex
would be not extended correct scope.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.