Using std::condition_variable with atomic<bool> - c++

There are several questions on SO dealing with atomic, and other that deal with std::condition_variable. But my question if my use below is correct?
Three threads, one ctrl thread that does preparation work before unpausing the two other threads. The ctrl thread also is able to pause the worker threads (sender/receiver) while they are in their tight send/receive loops.
The idea with using the atomic is to make the tight loops faster in case the boolean for pausing is not set.
class SomeClass
{
public:
//...
// Disregard that data is public...
std::condition_variable cv; // UDP threads will wait on this cv until allowed
// to run by ctrl thread.
std::mutex cv_m;
std::atomic<bool> pause_test_threads;
};
void do_pause_test_threads(SomeClass *someclass)
{
if (!someclass->pause_test_threads)
{
// Even though we use an atomic, mutex must be held during
// modification. See documentation of condition variable
// notify_all/wait. Mutex does not need to be held for the actual
// notify call.
std::lock_guard<std::mutex> lk(someclass->cv_m);
someclass->pause_test_threads = true;
}
}
void unpause_test_threads(SomeClass *someclass)
{
if (someclass->pause_test_threads)
{
{
// Even though we use an atomic, mutex must be held during
// modification. See documentation of condition variable
// notify_all/wait. Mutex does not need to be held for the actual
// notify call.
std::lock_guard<std::mutex> lk(someclass->cv_m);
someclass->pause_test_threads = false;
}
someclass->cv.notify_all(); // Allow send/receive threads to run.
}
}
void wait_to_start(SomeClass *someclass)
{
std::unique_lock<std::mutex> lk(someclass->cv_m); // RAII, no need for unlock.
auto not_paused = [someclass](){return someclass->pause_test_threads == false;};
someclass->cv.wait(lk, not_paused);
}
void ctrl_thread(SomeClass *someclass)
{
// Do startup work
// ...
unpause_test_threads(someclass);
for (;;)
{
// ... check for end-program etc, if so, break;
if (lost ctrl connection to other endpoint)
{
pause_test_threads();
}
else
{
unpause_test_threads();
}
sleep(SLEEP_INTERVAL);
}
unpause_test_threads(someclass);
}
void sender_thread(SomeClass *someclass)
{
wait_to_start(someclass);
...
for (;;)
{
// ... check for end-program etc, if so, break;
if (someclass->pause_test_threads) wait_to_start(someclass);
...
}
}
void receiver_thread(SomeClass *someclass)
{
wait_to_start(someclass);
...
for (;;)
{
// ... check for end-program etc, if so, break;
if (someclass->pause_test_threads) wait_to_start(someclass);
...
}

I looked through your code manipulating conditional variable and atomic, and it seems that it is correct and won't cause problems.
Why you should protect writes to shared variable even if it is atomic:
There could be problems if write to shared variable happens between checking it in predicate and waiting on condition. Consider following:
Waiting thread wakes spuriously, aquires mutex, checks predicate and evaluates it to false, so it must wait on cv again.
Controlling thread sets shared variable to true.
Controlling thread sends notification, which is not received by anybody, because there is no threads waiting on conditional variable.
Waiting thread waits on conditional variable. Since notification was already sent, it would wait until next spurious wakeup, or next time when controlling thread sends notification. Potentially waiting indefinetly.
Reads from shared atomic variables without locking is generally safe, unless it introduces TOCTOU problems.
In your case you are reading shared variable to avoid unnecessary locking and then checking it again after lock (in conditional wait call). It is a valid optimisation, called double-checked locking and I do not see any potential problems here.
You might want to check if atomic<bool> is lock-free. Otherwise you will have even more locks you would have without it.

In general, you want to treat the fact that variable is atomic independently of how it works with a condition variable.
If all code that interacts with the condition variable follows the usual pattern of locking the mutex before query/modification, and the code interacting with the condition variable does not rely on code that does not interact with the condition variable, it will continue to be correct even if it wraps an atomic mutex.
From a quick read of your pseudo-code, this appears to be correct. However, pseudo-code is often a poor substitute for real code for multi-threaded code.
The "optimization" of only waiting on the condition variable (and locking the mutex) when an atomic read says you might want to may or may not be an optimization. You need to profile throughput.

atomic data doesn't need another synchronization, it's basis of lock-free algorithms and data structures.
void do_pause_test_threads(SomeClass *someclass)
{
if (!someclass->pause_test_threads)
{
/// your pause_test_threads might be changed here by other thread
/// so you have to acquire mutex before checking and changing
/// or use atomic methods - compare_exchange_weak/strong,
/// but not all together
std::lock_guard<std::mutex> lk(someclass->cv_m);
someclass->pause_test_threads = true;
}
}

Related

Why is this printing 1,3,2? [duplicate]

For simplicity, let's assume that we have only one conditional variable to match a single condition that is reflected by a boolean.
1) Why does std::condition_variable::wait(...) locks the mutex again after a "notify" has been sent to un-sleep it?
2) Seeing the behaviour in "1)", does that mean that when you do std::condition_variable::notify_all it only makes it so that all of the waiting threads are unblocked/woken up... but in order instead of all at once? If so, what can be done to do it all at once?
3) If I only care about threads sleeping until a condition is met and not care a single bit for any mutex acquisition, what can I do? Is there an alternative or should current std::condition_variable::wait(...) approach(es) be hacked around this?
If "hackery" is to be used, will this function work for unblocking all waiting threads on a condition and can it be called from any(per thread) threads:
//declared somehwere and modified before sending "notify"(ies)
std::atomic<bool> global_shared_condition_atomic_bool;
//the single(for simplicity in our case) condition variable matched with the above boolean result
std::condition_variable global_shared_condition_variable;
static void MyClass:wait()
{
std::mutex mutex;
std::unique_lock<std::mutex> lock(mutex);
while (!global_shared_condition_atomic_bool) global_shared_condition_variable.wait(lock);
}
it would have been called from random "waiting" threads like so:
void random_thread_run()
{
while(someLoopControlValue)
{
//random code...
MyClass:wait(); //wait for whatever condition the class+method is for.
//more random code...
}
}
Edit:
Gate class
#ifndef Gate_Header
#define Gate_Header
#include <mutex>
#include <condition_variable>
class Gate
{
public:
Gate()
{
gate_open = false;
}
void open()
{
m.lock();
gate_open = true;
m.unlock();
cv.notify_all();
}
void wait()
{
std::unique_lock<std::mutex> lock(m);
while (!gate_open) cv.wait(lock);
}
void close()
{
m.lock();
gate_open = false;
m.unlock();
}
private:
std::mutex m;
std::condition_variable cv;
bool gate_open;
};
#endif
Condition variables wake things up spuriously.
You must have a mutex and it must guard a message of some kind for them to work, or you have zero guarantee that any such wakeup occurred.
This was done, presumably, because efficient implementations of a non-spurious version end up being implemeneted in terms of such a spurious version anyhow.
If you fail to guard the message editing with a mutex (ie, no synchronization on it, the state of the message is undefined behavior. This can cause compilers to optimize the read from memory to skip it after the first read.
Even excluding that undefined behavior (imagine you use atomics), there are race conditions where a message is set, a notification occurs, and nobody waiting on the notification sees the message being set if you fail to have the mutex acquired in the time between the variable being set and the condition variable being notified.
Barring extreme cases, you usually want to use the lambda version of wait.
Auditing condition variable code is not possible unless you audit both the notification code and the wait code.
struct gate {
bool gate_open = false;
mutable std::condition_variable cv;
mutable std::mutex m;
void open_gate() {
std::unique_lock<std::mutex> lock(m);
gate_open=true;
cv.notify_all();
}
void wait_at_gate() const {
std::unique_lock<std::mutex> lock(m);
cv.wait( lock, [this]{ return gate_open; } );
}
};
or
void open_gate() {
{
std::unique_lock<std::mutex> lock(m);
gate_open=true;
}
cv.notify_all();
}
No, your code will not work.
The mutex protects modifications to the shared variable. As such, all of the waiting threads and the signaling thread must lock that specific mutex instance. With what you've written, each thread has its own mutex instance.
The main reason for all of this mutex stuff is due to the concept of spurious wakeup, an unfortunate aspect of OS implementations of condition variables. Threads waiting on them sometimes just start running even though the condition hasn't been satisfied yet.
The mutex-bound check of the actual variable allows the thread to test whether it was spuriously awoken or not.
wait atomically releases the mutex and starts waiting on the condition. When wait exits, the mutex is atomically reacquired as part of the wakeup process. Now, consider a race between a spurious wakeup and the notifying thread. The notifying thread can be in one of 2 states: about to modify the variable, or after modifying it and about to notify everyone to wake up.
If the spurious wakeup happens when the notifying thread is about to modify the varaible, then one of them will get to the mutex first. So the spuriously awoken thread will either see the old value or the new value. If it sees the new, then it has been notified and will go do its business. If it sees the old, then it will wait on the condition again. But if it saw the old, then it blocked the notifying thread from modifying that variable, so it had to wait until the spurious thread went back to sleep.
Why does std::condition_variable::wait(...) locks the mutex again after a "notify" has been sent to un-sleep it?
Because the mutex locks access to the condition variable. And the first thing you have to do after waking up from a wait call is to check the condition variable. As such, that must be done under the protection of the mutex.
The signalling thread must be prevented from modifying the variable while other threads are reading it. That's what the mutex is for.
Seeing the behaviour in "1)", does that mean that when you do std::condition_variable::notify_all it only makes it so that all of the waiting threads are unblocked/woken up... but in order instead of all at once?
The order they wake up in is not specified. However, by the time notify_all returns, all threads are guaranteed to have been unblocked.
If I only care about threads sleeping until a condition is met and not care a single bit for any mutex acquisition, what can I do?
Nothing. condition_variable requires that access to the actual variable you're checking is controlled via a mutex.

Swapping mutex locks

I'm having trouble with properly "swapping" locks. Consider this situation:
bool HidDevice::wait(const std::function<bool(const Info&)>& predicate)
{
/* A method scoped lock. */
std::unique_lock waitLock(this->waitMutex, std::defer_lock);
/* A scoped, general access, lock. */
{
std::lock_guard lock(this->mutex);
bool exitEarly = false;
/* do some checks... */
if (exitEarly)
return false;
/* Only one thread at a time can execute this method, however
other threads can execute other methods or abort this one. Thus,
general access mutex "this->mutex" should be unlocked (to allow threads
to call other methods) while at the same time, "this->waitMutex" should
be locked to prevent multiple executions of code below. */
waitLock.lock(); // How do I release "this->mutex" here?
}
/* do some stuff... */
/* The main problem is with this event based OS function. It can
only be called once with the data I provide, therefore I need to
have a 2 locks - one blocks multiple method calls (the usual stuff)
and "waitLock" makes sure that only one instance of "osBlockingFunction"
is ruinning at the time. Since this is a thread blocking function,
"this->mutex" must be unlocked at this point. */
bool result = osBlockingFunction(...);
/* In methods, such as "close", "this->waitMutex" and others are then used
to make sure that thread blocking methods have returned and I can safely
modify related data. */
/* do some more stuff... */
return result;
}
How could I solve this "swapping" problem without overly complicating code? I could unlock this->mutex before locking another, however I'm afraid that in that nanosecond, a race condition might occur.
Edit:
Imagine that 3 threads are calling wait method. The first one will lock this->mutex, then this->waitMutex and then will unlock this->mutex. The second one will lock this->mutex and will have to wait for this->waitMutex to be available. It will not unlock this->mutex. The third one will get stuck on locking this->mutex.
I would like to get the last 2 threads to wait for this->waitMutex to be available.
Edit 2:
Expanded example with osBlockingFunction.
It smells like that the design/implementation should be a bit different with std::condition_variable cv on the HidDevice::wait and only one mutex. And as you write "other threads can execute other methods or abort this one" will call cv.notify_one to "abort" this wait. The cv.wait {enter wait & unlocks the mutex} atomically and on cv.notify {exits wait and locks the mutex} atomically. Like that HidDevice::wait is more simple:
bool HidDevice::wait(const std::function<bool(const Info&)>& predicate)
{
std::unique_lock<std::mutex> lock(this->m_Mutex); // Only one mutex.
m_bEarlyExit = false;
this->cv.wait(lock, spurious wake-up check);
if (m_bEarlyExit) // A bool data-member for abort.
return;
/* do some stuff... */
}
My assumption is (according to the name of the function) that on /* do some checks... */ the thread waits until some logic comes true.
"Abort" the wait, will be in the responsibility of other HidDevice function, called by the other thread:
void HidDevice::do_some_checks() /* do some checks... */
{
if ( some checks )
{
if ( other checks )
m_bEarlyExit = true;
this->cv.notify_one();
}
}
Something similar to that.
I recommend creating a little "unlocker" facility. This is a mutex wrapper with inverted semantics. On lock it unlocks and vice-versa:
template <class Lock>
class unlocker
{
Lock& locked_;
public:
unlocker(Lock& lk) : locked_{lk} {}
void lock() {locked_.unlock();}
bool try_lock() {locked_.unlock(); return true;}
void unlock() {locked_.lock();}
};
Now in place of:
waitLock.lock(); // How do I release "this->mutex" here?
You can instead say:
unlocker temp{lock};
std::lock(waitLock, temp);
where lock is a unique_lock instead of a lock_guard holding mutex.
This will lock waitLock and unlock mutex as if by one uninterruptible instruction.
And now, after coding all of that, I can reason that it can be transformed into:
waitLock.lock();
lock.unlock(); // lock must be a unique_lock to do this
Whether the first version is more or less readable is a matter of opinion. The first version is easier to reason about (once one knows what std::lock does). But the second one is simpler. But with the second, the reader has to think more carefully about the correctness.
Update
Just read the edit in the question. This solution does not fix the problem in the edit: The second thread will block the third (and following threads) from making progress in any code that requires mutex but not waitMutex, until the first thread releases waitMutex.
So in this sense, my answer is technically correct, but does not satisfy the desired performance characteristics. I'll leave it up for informational purposes.

Why is there no wait function for condition_variable which does not relock the mutex

Consider the following example.
std::mutex mtx;
std::condition_variable cv;
void f()
{
{
std::unique_lock<std::mutex> lock( mtx );
cv.wait( lock ); // 1
}
std::cout << "f()\n";
}
void g()
{
std::this_thread::sleep_for( 1s );
cv.notify_one();
}
int main()
{
std::thread t1{ f };
std::thread t2{ g };
t2.join();
t1.join();
}
g() "knows" that f() is waiting in the scenario I would like to discuss.
According to cppreference.com there is no need for g() to lock the mutex before calling notify_one. Now in the line marked "1" cv will release the mutex and relock it once the notification is sent. The destructor of lock releases it again immediately after that. This seems to be superfluous especially since locking is expensive. (I know in certain scenarios the mutex needs to be locked. But this is not the case here.)
Why does condition_variable have no function "wait_nolock" which does not relock the mutex once the notification arrives. If the answer is that pthreads do not provide such functionality: Why can`t pthreads be extended for providing it? Is there an alternative for realizing the desired behavior?
You misunderstand what your code does.
Your code on line // 1 is free to not block at all. condition_variables can (and will!) have spurious wakeups -- they can wake up for no good reason at all.
You are responsible for checking if the wakeup is spurious.
Using a condition_variable properly requires 3 things:
A condition_variable
A mutex
Some data guarded by the mutex
The data guarded by the mutex is modified (under the mutex). Then (with the mutex possibly disengaged), the condition_variable is notified.
On the other end, you lock the mutex, then wait on the condition variable. When you wake up, your mutex is relocked, and you test if the wakeup is spurious by looking at the data guarded by the mutex. If it is a valid wakeup, you process and proceed.
If it wasn't a valid wakeup, you go back to waiting.
In your case, you don't have any data guarded, you cannot distinguish spurious wakeups from real ones, and your design is incomplete.
Not surprisingly with the incomplete design you don't see the reason why the mutex is relocked: it is relocked so you can safely check the data to see if the wakeup was spurious or not.
If you want to know why condition variables are designed that way, probably because this design is more efficient than the "reliable" one (for whatever reason), and rather than exposing higher level primitives, C++ exposed the lower level more efficient primitives.
Building a higher level abstraction on top of this isn't hard, but there are design decisions. Here is one built on top of std::experimental::optional:
template<class T>
struct data_passer {
std::experimental::optional<T> data;
bool abort_flag = false;
std::mutex guard;
std::condition_variable signal;
void send( T t ) {
{
std::unique_lock<std::mutex> _(guard);
data = std::move(t);
}
signal.notify_one();
}
void abort() {
{
std::unique_lock<std::mutex> _(guard);
abort_flag = true;
}
signal.notify_all();
}
std::experimental::optional<T> get() {
std::unique_lock<std::mutex> _(guard);
signal.wait( _, [this]()->bool{
return data || abort_flag;
});
if (abort_flag) return {};
T retval = std::move(*data);
data = {};
return retval;
}
};
Now, each send can cause a get to succeed at the other end. If more than one send occurs, only the latest one is consumed by a get. If and when abort_flag is set, instead get() immediately returns {};
The above supports multiple consumers and producers.
An example of how the above might be used is a source of preview state (say, a UI thread), and one or more preview renderers (which are not fast enough to be run in the UI thread).
The preview state dumps a preview state into the data_passer<preview_state> willy-nilly. The renderers compete and one of them grabs it. Then they render it, and pass it back (through whatever mechanism).
If the preview states come faster than the renderers consume them, only the most recent one is of interest, so the earlier ones are discarded. But existing previews aren't aborted just because a new state shows up.
Questions where asked below about race conditions.
If the data being communicated is atomic, can't we do without the mutex on the "send" side?
So something like this:
template<class T>
struct data_passer {
std::atomic<std::experimental::optional<T>> data;
std::atomic<bool> abort_flag = false;
std::mutex guard;
std::condition_variable signal;
void send( T t ) {
data = std::move(t); // 1a
signal.notify_one(); // 1b
}
void abort() {
abort_flag = true; // 1a
signal.notify_all(); // 1b
}
std::experimental::optional<T> get() {
std::unique_lock<std::mutex> _(guard); // 2a
signal.wait( _, [this]()->bool{ // 2b
return data.load() || abort_flag.load(); // 2c
});
if (abort_flag.load()) return {};
T retval = std::move(*data.load());
// data = std::experimental::nullopt; // doesn't make sense
return retval;
}
};
the above fails to work.
We start with the listening thread. It does step 2a, then waits (2b). It evaluates the condition at step 2c, but doesn't return from the lambda yet.
The broadcasting thread then does step 1a (setting the data), then signals the condition variable. At this moment, nobody is waiting on the condition variable (the code in the lambda doesn't count!).
The listening thread then finishes the lambda, and returns "spurious wakeup". It then blocks on the condition variable, and never notices that data was sent.
The std::mutex used while waiting on the condition variable must guard the write to the data "passed" by the condition variable (whatever test you do to determine if the wakeup was spurious), and the read (in the lambda), or the possibility of "lost signals" exists. (At least in a simple implementation: more complex implementations can create lock-free paths for "common cases" and only use the mutex in a double-check. This is beyond the scope of this question.)
Using atomic variables does not get around this problem, because the two operations of "determine if the message was spurious" and "rewait in the condition variable" must be atomic with regards to the "spuriousness" of the message.

Condition variables and lockfree container

Conditional variables use a mutex and the .wait() function unlocks the
mutex so another thread can access the shared data. When the condition
variable is notified it tries to lock the mutex again to use the shared
data.
This pattern is used in the following concurrent_queue example from Anthony Williams:
template<typename Data>
class concurrent_queue
{
private:
boost::condition_variable the_condition_variable;
public:
void wait_for_data()
{
boost::mutex::scoped_lock lock(the_mutex);
while(the_queue.empty())
{
the_condition_variable.wait(lock);
}
}
void push(Data const& data)
{
boost::mutex::scoped_lock lock(the_mutex);
bool const was_empty=the_queue.empty();
the_queue.push(data);
if(was_empty)
{
the_condition_variable.notify_one();
}
}
};
Since the code uses std::queue it's clear that the mutex has to be
locked when accessing the queue.
But let's say instead of std::queue one uses Microsofts
Concurrency::concurrent_queue from PPL. Member functions like empty,
push and try_pop are thread safe. Do I still need to lock a mutex in
this case or can the condition variable be used like this, without
creating any possible race conditions.
My code (that seems to work, but what does that mean in multithreading?) looks like this. I have one producer that pushes items into Microsofts concurrent_queue and one background thread that waits for new items in this queue.
The consumer/background thread:
while(runFlag) //atomic
{
while(the_queue.empty() && runFlag) //wait only when thread should still run
{
boost::mutex mtx; //local mutex thats locked afterwards. awkward.
boost::mutex::scoped_lock lock(mtx);
condition.wait(lock);
}
Data d;
while(!the_queue.empty() && the_queue.try_pop(d))
{
//process data
}
}
The producer/main thread:
const bool was_empty = the_queue.empty();
Data d;
the_queue.push(d);
if(was_empty) cond_var.notify_one();
The shutdown procedure:
bool expected_run_state = true;
if(runLoop.compare_exchange_strong(expected_run_state, false))
{
//atomically set our loop flag to false and
//notify all clients of the queue to wake up and exit
cond_var.notify_all();
}
As said above this code seems to work but that doesn't necessarily mean it's correct. Especially the local mutex that is only used because the condition variable interface forces me to use a mutex, seems like a very bad idea. I wanted to use condition variables since the time between data items added to the queue hard to predict and I would have to create to sleep and wake up periodically like this:
if(the_queue.empty()) Sleep(short_amount_of_time);
Are there any other, maybe OS (in my case: Windows) specific tools, that make a background thread sleep until some condition is met without regularly waking up and checking the condition?
The code is not correct in different scenarios, for example. If the queue has a single element when const bool was_empty = the_queue.empty(); is evaluated, but a thread consumes the element and a different thread tries to consume and waits on the condition, the writer will not notify that thread after inserting the element in the queue.
The key issue is that the fact that all of the operations in an interface are thread safe does not necessarily mean that your use of the interface is safe. If you depend on multiple operations being performed atomically, you need to provide synchronization mechanisms externally.
Are there any other, maybe OS (in my case: Windows) specific tools,
that make a background thread sleep until some condition is met
without regularly waking up and checking the condition?
This is exactly what Events are for
But if you are targeting only Windows platform (Vista+) you should check out
Slim Reader/Writer (SRW) Locks

ls this code thread-safe?

I'm refactoring some time consuming function so that it can be called from a thread, but I'm having trouble wrapping my head around the issue (not very familiar with thread programming).
At any point, the user can cancel and the function will stop. I do not want to kill the thread as soon as the user cancels since it could cause some data integrity problems. Instead, in several places in the function, I will check if the function has been cancelled and, if so, exit. I will only do that where I know it's safe to exit.
The whole code of the function will be within a mutex. This is the pseudo-code I have in mind:
SomeClass::SomeClass() {
cancelled_ = false;
}
void SomeClass::cancelBigSearch() {
cancelled_ = true;
}
void SomeClass::bigSearch() {
mutex.lock();
// ...
// Some code
// ...
// Safe to exit at this point
if (cancelled_) {
mutex.unlock();
cancelled_ = false;
return;
}
// ...
// Some more code
// ...
if (cancelled_) {
mutex.unlock();
cancelled_ = false;
return;
}
// ...
// Again more code
// ...
if (cancelled_) {
mutex.unlock();
cancelled_ = false;
return;
}
mutex.unlock();
}
So when the user starts a search, a new thread calls bigSearch(). If the user cancels, cancelBigSearch() is called and a cancelled_ flag is set. Then, when bigSearch() reaches a point where it's safe to exit, it will exit.
Any idea if this is all thread-safe?
You should lock access to cancelled_ with another mutex, so checking and setting does not happen simultaneously. Other than that, I think your approach is OK
Update: Also, make sure no exceptions can be thrown from SomeClass::bigSearch(), otherwise the mutex might remain in a locked state. To make sure that all return paths unlock the mutex, you might want to surround the processing parts of the code with if (!cancelled_) and return only at the very end of the method (where you have the one unlock() call on the mutex.
Better yet, wrap the mutex in a RAII (acronym for Resource Allocation Is Initialization) object, so no matter how the function ends (exception or otherwise), the mutex is guaranteed to be unlocked.
Yes, this is thread safe. But:
Processors can have separate cache and cache it's own copy of cancelled_, typically mutex synchronization functions applies proper cache synchronization.
Compiler generated code, can make invalid assumptions about Your data locality, this can lead to not update in time cancelled_. Some platform specific commands can help here, or you can simply use other mechanisms.
All these lead to a thread that isn't canceled in time as you wish.
Your code usage pattern is simple "signaling". So you need to transfer signal to thread. Signal patterns allows trigger multiple times same trigger (signal), and clear it later.
This can be simulated using:
atomic operations
mutex protected variables
signal synchronization primitives
It's not thread-safe, because one thread could read cancelled_ at the same time another thread writes to it, which is a data race, which is undefined behaviour.
As others suggested, either use an atomic type for cancelled_ or protect it with another mutex.
You should also use RAII types to lock the mutexes.
e.g.
void SomeClass::cancelBigSearch() {
std::lock_guard<std::mutex> lock(cxlMutex_);
cancelled_ = true;
}
bool SomeClass::cancelled() {
std::lock_guard<std::mutex> lock(cxlMutex_);
if (cancelled_) {
// reset to false, to avoid caller having to lock mutex again to reset it
cancelled_ = false;
return true;
}
return false;
}
void SomeClass::bigSearch() {
std::lock_guard<std::mutex> lock(mutex);
// ...
// Some code
// ...
// Safe to exit at this point
if (cancelled())
return;
// ...
// Some more code
// ...
if (cancelled())
return;
// ...
// Again more code
// ...
if (cancelled())
return;
}