Is there anything wrong in following source code if I don't use mutex?
bool bStop = false;
void thread1_fun()
{
while (!bStop)
{
doSomething();
}
}
void thread2_fun()
{
bStop = true;
}
Is there anything wrong in following source code if I don't use mutex?
bool bStop = false;
void thread1_fun()
{
while (!bStop)
{
doSomething();
}
}
void thread2_fun()
{
bStop = true;
}
It is undefined behaviour to write to an object in one thread while another thread accesses the object at all.
Unless you specifically inform the compiler that there should be a fence, such as the use of std::atomic
, std::mutex
et al, all bets are off.
The compiler is within its rights to re-write the code as this:
bool bStop = false;
void thread1_fun()
{
const bool stopped = bStop;
// compiler reasons: "because there is no fence, the variable clearly cannot
// have changed to true, since no-other thread will modify it, since
// to modify it without a fence would be UB."
while (!stopped)
{
doSomething();
}
}
void thread2_fun()
{
// this happens in my thread's view of the world,
// but no other thread need see it.
bStop = true;
}
Is there anything wrong in following source code if I don't use mutex?
Yes, you have a race condition, which leads to undefined behavior.
You need to either use std::atomic<bool>
or protect the writes/reads to bStop
with a synchronisation primitive such as std::mutex
.
Contrary to (sadly) common belief, marking bStop
as volatile
does not solve anything here. The volatile
qualifier has nothing to do with thread-safety.
Writing to a variable in one thread and reading the value of that variable in another thread is a data race. A program with a data race has undefined behavior; that means that the C++ standard doesn't tell you what that program will do. You can avoid the data race by making the variable atomic or by using various kinds of synchronization, the most common being std::mutex
and std::condition_variable
.
You might be able to get a program to "work" reliably without synchronization by analyzing the details of the code that you're compiling, the details of how your compiler generates object code from that source code, and the details of how your hardware treats the resulting object code. And then, if you change any of those three, you have to do the analysis all over again, because with those changes it might not "work" any more. Of course, if you do that, you're down and dirty with all the kinds of details that high-level languages save you from. Except in very rare circumstances, this simply isn't worth the effort. Use the language correctly.