What is the difference in boost::shared_mutex vs boost::upgrade_mutex? - c++

I was wondering what the difference is in boost::shared_mutex and boost::upgrade_mutex. I wanted to make a multi threaded application that will have multiple frequent readers and one non frequent writer.
I could just use a standard shared_mutex and a shared_lock and a unique_lock. Although this is likely to give me writer starvation.
What I want is:
If a reader has a shared lock and a writer is waiting on the lock that no other readers will be given access and they will have to wait on the shared lock.
I can find very little information on boost::upgrade_mutex, but I think this is what I want?
I made this example (please ignore that the couts are happening outside of a lock etc and that I can't control which thread runs first):
#include <iostream>
#include <thread>
#include <boost/thread/locks.hpp>
#include <boost/thread/shared_mutex.hpp>
int main()
{
boost::shared_mutex mutex;
boost::shared_lock<boost::shared_mutex> read_lock(mutex);
std::thread t1([&mutex]()
{
std::cout << "Thread trying to get the unique lock!" << std::endl;
boost::unique_lock<boost::shared_mutex> write_lock(mutex);
std::cout << "Thread got the unique lock!" << std::endl;
});
std::thread t2([&mutex]()
{
std::cout << "Thread trying to get the shared lock!" << std::endl;
boost::shared_lock<boost::shared_mutex> read_lock(mutex);
std::cout << "Thread got the shared lock!" << std::endl;
});
// To make sure the threads ask for the lock before unlocking
sleep(1);
std::cout << "Unlocking first lock" << std::endl;
read_lock.unlock();
t1.join();
t2.join();
}
From my testing, if t1 runs before t2, t2 also waits on read_lock.unlock(); before proceeding. This is what I want!
I then changed boost::upgrade_mutex to boost::shared_mutex (in the template param of the locks as well) and I am seeing exactly the same behaviour. I can't find in the documentation if this is guaranteed or what the difference is.

Reading the documentation, the upgrade_mutex is a shared_mutex with extra functionality considered upgrading. See https://www.boost.org/doc/libs/1_81_0/doc/html/thread/synchronization.html#thread.synchronization.mutex_concepts.upgrade_lockable
The UpgradeLockable concept is a refinement of the SharedLockable concept that allows for upgradable ownership as well as shared ownership and exclusive ownership. This is an extension to the multiple-reader / single-write model provided by the SharedLockable concept: a single thread may have upgradable ownership at the same time as others have shared ownership. The thread with upgradable ownership may at any time attempt to upgrade that ownership to exclusive ownership. If no other threads have shared ownership, the upgrade is completed immediately, and the thread now has exclusive ownership, which must be relinquished by a call to unlock(), just as if it had been acquired by a call to lock().
The thing you cannot do with shared_lock is upgrading from shared locking to unique locking, without unlocking. The upgrade_lock is something in-between a shared and a unique lock. You can only have a single upgrade lock, though at the same time multiple shared locks can be taken.
You can ask to upgrade the upgrade lock to a unique lock, which will only succeed after all shared locks are released. (Other operations are also possible, though they are less relevant to understand the difference)

Related

Does it make sense to use different mutexes with the same condition variable?

The documentation of the notify_one() function of condition variable at cppreference.com states the following
The notifying thread does not need to hold the lock on the same mutex as the one held by the waiting thread(s); in fact doing so is a pessimization, since the notified thread would immediately block again, waiting for the notifying thread to release the lock.
The first part of the sentence is strange, if I hold different mutexes in the notifying and notified threads, then the mutexes have no real meaning as the there is no 'blocking' operation here. In fact, if different mutexes are held, then the likelihood that a spurious wake up could cause the notification to be missed is possible! I get the impression that we might as well not lock on the notifying thread in such a case. Can someone clarify this?
Consider the following from cppreference page on condition variables as an example.
std::mutex m; // this is supposed to be a pessimization
std::condition_variable cv;
std::string data;
bool ready = false;
bool processed = false;
void worker_thread()
{
// Wait until main() sends data
std::unique_lock<std::mutex> lk(m); // a different, local std::mutex is supposedly better
cv.wait(lk, []{return ready;});
// after the wait, we own the lock.
std::cout << "Worker thread is processing data\n";
data += " after processing";
// Send data back to main()
processed = true;
std::cout << "Worker thread signals data processing completed\n";
lk.unlock();
cv.notify_one();
}
int main()
{
std::thread worker(worker_thread);
data = "Example data";
// send data to the worker thread
{
std::lock_guard<std::mutex> lk(m); // a different, local std::mutex is supposedly better
ready = true;
std::cout << "main() signals data ready for processing\n";
}
cv.notify_one();
// wait for the worker
{
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, []{return processed;});
}
std::cout << "Back in main(), data = " << data << '\n';
worker.join();
}
PS. I saw a few questions that are similarly titled but they refer to different aspects of the problem.
I think the wording of cppreference is somewhat awkward here. I think they were just trying to differentiate the mutex used in conjunction with the condition variable from other unrelated mutexes.
It makes no sense to use a condition variable with different mutexes. The mutex is used to make any change to the actual semantic condition (in the example it is just the variable ready) atomic and it must therefore be held whenever the condition is updated or checked. Also it is needed to ensure that a waiting thread that is unblocked can immediately check the condition without again running into race conditions.
I understand it as follows:
It is OK, not to hold the lock on the mutex associated with the condition variable when notify_one is called, or any mutex at all, however it is OK to hold other mutexes for different reasons.
The pessimisation is not that only one mutex is used, but to hold this mutex for longer than necessary when you know that another thread is supposed to immediately try to acquire the mutex after being notified.
I think that my interpretation agrees with the explanation given in cppreference on condition variable:
The thread that intends to modify the shared variable has to
acquire a std::mutex (typically via std::lock_guard)
perform the modification while the lock is held
execute notify_one or notify_all on the std::condition_variable (the lock does not need to be held for notification)
Even if the shared variable is atomic, it must be modified under the mutex in order to correctly publish the modification to the waiting thread.
Any thread that intends to wait on std::condition_variable has to
acquire a std::unique_lock<std::mutex>, on the same mutex as used to protect the shared variable
Furthermore the standard expressly forbids using different mutexes for wait, wait_­for, or wait_­until:
lock.mutex() returns the same value for each of the lock arguments supplied by all concurrently waiting (via wait, wait_­for, or wait_­until) threads.
The notifying thread does not need to hold the lock on the same mutex as the one held by the waiting thread(s);
That's misleading. The problem is the word, "same." They should have said, "...does not need to hold the lock on any mutex..." That's the real point. There's an important reason why the waiting thread should have a mutex locked when it enters the wait() call: It's awaiting some change in some shared data structure, and it needs the mutex to be locked when it accesses the structure to check whether or not the awaited change actually has happened.
The notify()ing thread probably needs to lock the same mutex in order to effect that change, but the correctness of the program won't depend on whether it calls notify() before or after it releases the mutex.

Any proper way to achieve locks in a situation like this?

I have an array of objects that I want to operate on in threads, but I also want to be able to access at times. This feels like a hacky way to achieve my goal, but is there a better way to do something like this?:
*the basic goal is to have 2 locks. One that allows all individual threads to work concurrently while blocking access from the array until they are all finished, and one that allows shuts down access from the thread to ensure that no objects are touched by other threads while a function runs.
atomic<int> inThreadCount;
atomic<int> arrayLock;
map<string, MyObj*> myMap;
mutex mu1;
class MyObj{
mutex mu2;
int myInt;
public:
void update(bool shouldLowerCount){
mu2.lock();
myInt++;
if (shouldLowerCount)
inThreadCount--;
mu2.unlock();
}
}
//Some operation that requires all threads to finish first
//and doesn't allow threads to access the objects while running
void GetSnapshot(){
mu1.lock();
arrayLock++;
while (inThreadCount > 0)
Sleep(0);
map<string, MyObj *>::iterator it = myMap.begin();
auto t = time(nullptr);
auto tm = *localtime(&t);
cout << put_time(&tm, "%d-%m-%Y %H-%M-%S") << endl;
for( ; it != myMap.end(); ++it){
cout << it->first << ":" << it->second->counter);
}
arrayLock--;
mu1.unlock();
}
void updateObject(MyObj* myObj){
while (arrayLock > 0)
Sleep(0);
inThreadCount++;
async(std::launch::async, myObj->update(true));
}
PS, I realize that there is a tiny window of opportunity for error between Sleep() and arrayLock/inThreadCount++. That's part of the problem I want to solve!
I think you're asking for a shared mutex.
A shared mutex (or read-write mutex) allows many threads to lock an object in parallel while also allowing one thread at a time to lock it exclusively.
In simple terms, if a thread requests shared access it is granted unless a thread holds the object exclusively. A thread is granted exclusivity when the object isn't held (shared or exclusively) by any other thread.
It's common use is read-write exclusivity. See shared access to read and exclusive access to write. That's valid because a data race can only occur when two ore more threads access the same data and at least one of them is a write operation. Multiple readers is not a data race.
There are normally overheads in implementing a shared lock as opposed to an exclusive lock, and the model only normally helps where there are 'many' readers
that read 'frequently' and write operations are 'infrequent'. What 'many', 'frequent' and 'infrequent' mean depends on the platform and problem in hand.
That's exactly what a shared mutex is for.
C++17 supports that out of the box with std::shared_mutex but I noticed the question is tagged C++11.
Some implementations have offered that for some time (it's a classic locking strategy)
Or you can try boost::shared_mutex<>.
NB: One of the challenges in a shared-lock is to avoid live-lock on the writer.
If there are many readers that read frequently it can be easy the writer to get 'locked out' indefinitely and never progress (or progress very slowly).
A good shared lock will provide some guarantee of the writer eventually getting a turn. That may be absolute priority (no writers allowed to start after a thread starts waithing

Deadlocks related to scheduling

On the Oracle docs for multithreading they have this paragraph about deadlocks when trying to require a lock:
Because there is no guaranteed order in which locks are acquired, a
problem in threaded programs is that a particular thread never
acquires a lock, even though it seems that it should.
This usually happens when the thread that holds the lock releases it,
lets a small amount of time pass, and then reacquires it. Because the
lock was released, it might seem that the other thread should acquire
the lock. But, because nothing blocks the thread holding the lock, it
continues to run from the time it releases the lock until it
reacquires the lock, and so no other thread is run.
Just to make sure I understood this, I tried to write this out in code (let me know if this is a correct interpretation):
#include <mutex>
#include <chrono>
#include <thread>
#include <iostream>
std::mutex m;
void f()
{
std::unique_lock<std::mutex> lock(m); // acquire the lock
std::cout << std::this_thread::get_id() << " now has the mutex\n";
lock.unlock(); // release the lock
std::this_thread::sleep_for(std::chrono::seconds(2)); // sleep for a while
lock.lock(); // reacquire the lock
std::cout << std::this_thread::get_id() << " has the mutex after sleep\n";
}
int main()
{
std::thread(f).join(); // thread A
std::thread(f).join(); // thread B
}
So what the quote above is saying is that the time during which the lock is released and the thread is sleeping (like the code above) is not sufficient to guarantee a thread waiting on the lock to acquire it? How does this relate to deadlocks?
The document is addressing a specific kind of fairness problem and is lumping it into its discussions about deadlock. The document correctly defines deadlock to mean a "permanent blocking of a set of threads". It then describes a slightly less obvious way of achieving the permanent blocking condition.
In the scenario described, assume two threads attempt to acquire the same lock simultaneously. Only one will win, so call it W, and the other loses, so call it L. The loser is put to sleep to wait its turn to get the lock.
The quoted text says that L may never get a chance to get the lock, even if it is released by W.
The reason this might happen is the lock does not impose fairness over which thread has acquired it. The lock is more than happy to let W acquire and release it forever. If W is implemented in such a way that it does not need to context switch after it releases the lock, it may just end up acquiring the lock again before L has a chance to wake up to see if the lock is available.
So, in the code below, if W_thread wins the initial race against L_thread, L_thread is effectively deadlocked, even though in theory it could acquire the lock between iterations of W_thread.
void W_thread () {
for (;;) {
std::unique_lock<std::mutex> lock(m);
//...
}
}
void L_thread () {
std::unique_lock<std::mutex> lock(m);
//...
}
The document recommends using thr_yield() to force a context switch to another thread. This API is specific to Solaris/SunOS. The POSIX version of it is called sched_yield(), although some UNIX versions (including Linux) provide a wrapper called pthread_yield().
In C++11, this is accomplished via std::this_thread::yield().

Is there a data race in this code?

In this page, this sample code is written to explain how to use notify_one:
#include <iostream>
#include <condition_variable>
#include <thread>
#include <chrono>
std::condition_variable cv;
std::mutex cv_m;
int i = 0;
bool done = false;
void waits()
{
std::unique_lock<std::mutex> lk(cv_m);
std::cout << "Waiting... \n";
cv.wait(lk, []{return i == 1;});
std::cout << "...finished waiting. i == 1\n";
done = true;
}
void signals()
{
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "Notifying...\n";
cv.notify_one();
std::unique_lock<std::mutex> lk(cv_m);
i = 1;
while (!done) {
lk.unlock();
std::this_thread::sleep_for(std::chrono::seconds(1));
lk.lock();
std::cerr << "Notifying again...\n";
cv.notify_one();
}
}
int main()
{
std::thread t1(waits), t2(signals);
t1.join(); t2.join();
}
However, valgrind (helgrind, actually) complains that:
Probably a race condition: condition variable 0x605420 has been
signaled but the associated mutex 0x605460 is not locked by the
signalling thread.
If the second threads runs before the first one and reaches cv.notify_one(); before anyone else, it will signals other threads without any lock being hold.
I am actually learning how to use these condition variables and trying to understand who should lock/unlock the mutex associated with them. So my question is: is this code doing things right? or is helgrind wrong?
[Truth in advertising: I was, until very recently, the architect of a commercial data-race and memory error detector that "competes" with Helgrind/Valgrind.]
There is no data race in your code. Helgrind is issuing this warning because of a subtlety about the way condition variables work. There is some discussion about it in the "hints" section of the Helgrind manual. Briefly: Helgrind is doing happens-before data race detection. It derives the "happens-before" relation by observing the order in which your code is calling pthread_mutex_lock/unlock and pthread_cond_wait/signal (these are the C primitives upon which the C++11 primitives are implemented.)
If you follow the discipline that your cv.notify_one() calls are always protected by the same mutex that surrounds the corresponding cv.wait() calls then Helgrind knows that the mutexes will enforce the correct happens-before relationship and so everything will be okay.
In your case Helgrind is complaining about the initial (gratuitous) cv.notify_one() call at the top of signals(), before you acquire the lock on cv_m. It knows that this is the kind of situation that can confuse it (although the real confusion is that it might later report false positives, so the warning message here is a bit misleading.)
Please note the advice to "use semaphores instead of condition_variables" in the hints section of the Helgrind manual is horrible advice. Semaphores are much harder to check for correctness than condition variables both for tools and for humans. Semaphores are "too general" in the sense that there are all sorts of invariants you can not rely on. The same thread that "locks" with a semaphore doesn't have to be the thread that "unlocks". Two threads that "wait" on a non-binary semaphore may or may not have a happens-before relationship. So semaphores are pretty much useless if you are trying to reason (or automatically detect) deadlock or data race conditions.
Better advice is to use condition variables to signal/wait, but to make sure that you follow a discipline where all calls on a particular condition variable happen within critical sections protected by the same mutex.
In your case, there is no problem.
In general, issues can arise if there is a third thread that may be using the same mutex, or if it's possible that the waiting thread may destroy the condition variable when it finishes running.
It's just safer to lock the mutex while signalling, to ensure that the signalling code entirely runs before the woken up code.
Edit: Turns out this isn't true, but it's being preserved here because #dauphic's explanation of why it isn't true in the comments is helpful.
You have reversed the order of unlock and lock in your while loop, such that you are attempting to unlock the mutex before you have ever locked it, which would seem consistent with the valgrind message you are seeing.

does boost::thread::timed_join(0) acquire a lock?

I need to check if my boost::thread I've created is running from another thread. This SO post explains you can do this by calling:
boost::posix_time::seconds waitTime(0);
myBoostThread.timed_join(waitTime);
I can't have any critical sections in my client thread. Can I guarantee that timed_join() with 0 time argument be lock free?
Boost.Thread provides no guarantees about a lock-free timed_join(). However, the implementation, which is always subject to change:
Boost.Thread acquires a mutex for pthreads, then performs a timed wait on a condition variable.
Boost.Thread calls WaitForMultipleObjects for windows. Its documentation indicates that it will always return immediately. However, I do not know if the underlying OS implementation is lock-free.
For an alternative, consider using atomic operations. While Boost 1.52 does not currently provide a public atomic library, both Boost.Smart_Ptr and Boost.Interprocess have atomic integers within their detail namespace. However, neither of these guarantee lock-free implementations, and one of the configurations for Boost.Smart_Ptr will lock with pthread mutex. Thus, you may need to consult your compiler and system's documentation to identify a lock-free implementation.
Nevertheless, here is a small example using boost::detail::atomic_count:
#include <boost/chrono.pp>
#include <boost/detail/atomic_count.hpp>
#include <boost/thread.hpp>
// Use RAII to perform cleanup.
struct count_guard
{
count_guard(boost::detail::atomic_count& count) : count_(count) {}
~count_guard() { --count_; }
boost::detail::atomic_count& count_;
};
void thread_main(boost::detail::atomic_count& count)
{
// Place the guard on the stack. When the thread exits through either normal
// means or the stack unwinding from an exception, the atomic count will be
// decremented.
count_guard decrement_on_exit(count);
boost::this_thread::sleep_for(boost::chrono::seconds(5));
}
int main()
{
boost::detail::atomic_count count(1);
boost::thread t(thread_main, boost::ref(count));
// Check the count to determine if the thread has exited.
while (0 != count)
{
std::cout << "Sleeping for 2 seconds." << std::endl;
boost::this_thread::sleep_for(boost::chrono::seconds(2));
}
}
In this case, the at_thread_exit() extension could be used as an alternative to using RAII.
No, there is no such guarantee.
Even if the boost implementation is completely lock free (I haven't checked), there is no guarantee that the underlying OS implementation is completely lock free.
That said, if locks were used here, I would find it unlikely that they will cause any significant delay in the application, so I would not hesitate using timed_join unless there is a hard real-time deadline to meet (which does not equate to UI responsiveness).