I'm not quite sure why std::unique_lock<std::mutex> is useful over just using a normal lock. An example in the code I'm looking at is:
{//aquire lock
std::unique_lock<std::mutex> lock(queue_mutex);
//add task
tasks.push_back(std::function<void()>(f));
}//release lock
why would this preferred over
queue_mutex.lock();
//add task
//...
queue_mutex.unlock();
do these snippets of code accomplish the same thing?
[Do] these snippets of code accomplish the same thing?
No.
The first one will release the lock at the end of the block, no matter what the block is. The second will not release the lock at the end if the critical section is exited with a break, continue, return, goto, exception, or any other kind of non-local jump that I'm forgetting about.
The use of unique_lock offers resiliency in the face of changes and errors.
If you change the flow to add intermediate "jumps" (return for example)
If an exception is thrown
...
in any case, the lock is automatically released.
On the other hand, if you attempt to do it manually, you may miss a case. And even if you don't right now, a later edit might.
Note: this is a usual idiom in C++, referred to as SBRM (Scoped Bound Resources Management) where you tie down a clean-up action to stack unwinding so you are assured that, unless crash/ungraceful exit, it is executed.
It also shows off RAII (Resources Acquisition is Initialization) since the very construction of unique_lock acquires the resource (here the mutex). Despite its name, this acronym is also colloquially used to refer to deterministic release at destruction time, which covers a broader scope than SBRM since it refers to all kind of deterministic releases, not only those based on stack unwinding.
Related
I looked at this question: Is seastar::thread a stackful coroutine?
In a comment to the answer, Botond Dénes wrote:
That said, seastar::thread still has its (niche) uses in the post-coroutine world as it supports things like waiting for futures in destructors and catch clauses, allowing for using RAII and cleaner error handling. This is something that coroutines don't and can't support.
Could someone elaborate on this? What are the cases when 'things like waiting for futures in destructors and catch clauses' are impossible with stackless coroutines, but possible with seastar::thread (and alike)?
And more generally, what are the advantages of seastar::thread over C++20 stackless coroutines? Do all stackful coroutine implementations (e.g. those in Boost) have these same advantages?
You have probably heard about the RAII (resource acquisition is initialization) idiom in C++, which is very useful for ensuring that resources get released even in case of exceptions. In the classic, synchronous, C++ world, here is an example:
{
someobject o = get_an_o();
// At this point, "o" is holding some resource (e.g., an open file)
call_some_function(o);
return;
// "o" is destroyed if call_some_function() throws, or before the return.
// When it does, the file it was holding is automatically closed.
}
Now, let's consider asynchronous code using Seastar. Imagine that get_an_o() takes some time to do its work (e.g., open a disk file), and returns a future<someobject>. Well, you might think that you can just use stackless coroutines like this:
someobject o = co_await get_an_o();
// At this point, "o" is holding some resource (e.g., an open file)
call_some_function(o);
return;
But there's a problem here... Although the someobject constructor could be made asynchronous (by using a factory function, get_an_o()in this example), thesomeobject` destructor is still synchronous... It gets called by C++ when unwinding the stack, and it can't return a future! So if the object's destructor does need to wait (e.g., to flush the file), you can't use RAII.
Using a seastar::thread allows you to still use RAII because the destructor now can wait. This is because in a seastar::thread any code - including a destructor, may use get() on a future. This doesn't return a future from the destructor (which we can't) - instead it just "pauses" the stackful coroutine (switches to other tasks and later returns back to this stack when the future resolves).
In general it is a good practice to declare a swap and move noexcept as that allows to provide some exception guarantee.
At the same time writing a thread-safe class often implies adding a mutex protecting the internal resources from races.
If I want to implement a swap function for such a class the straightforward solution is to lock in a safe way the resources of both arguments of the swap and then perform the resource swap as, for example, clearly answered in the answer to this question: Implementing swap for class with std::mutex .
The problem with such an algorithm is that a mutex lock is not noexcept, therefore swap cannot, strictly speaking, be noexcept. Is there a solution to safely swap two objects of a class with a mutex?
The only possibility that comes to my mind is to store the resource as a handle so that the swap becomes a simple pointer swap which can be done atomically.
Otherwise one could consider the lock exceptions as unrecoverable error which should anyway terminate the program, but this solution feels like just a way to put the dust under the carpet.
EDIT:
As came out in the comments, I know that the exceptions thrown by the mutexes are not arbitrary but then the question can be rephrased as such:
Are there robust practices to limit the situation a mutex can throw to those when it is actually an unrecoverable OS problem?
What comes to my mind is to check, in the swap algorithm, whether the two objects to swap are not the same. That is a clear deadlock situation which will trigger an exception in the best case scenario but can be easily checked for.
Are there other similar triggers which one can safely check to make a swap function robust and practically noexcept for all the situation that matter?
On POSIX systems it is common for std::mutex to be a thin wrapper around pthread_mutex_t, for which lock and unlock function can fail when:
There is an attempt to acquire already owned lock
The mutex object is not initialized or has been destroyed already
Both of the above are UB in C++ and are not even guaranteed to be returned by POSIX. On Windows both are UB if std::mutex is a wrapper around SRWLOCK.
So it seems that the main point of allowing lock and unlock functions to throw is to signal about errors in program, not to make programmer expect and handle them.
This is confirmed by the recommended locking pattern: the destructor ~unique_lock is noexcept(true), but is supposed to call unlock which is noexcept(false). That means if exception is thrown by unlock function, the whole program gets terminated by std::terminate.
The standard also mentions this:
The error conditions for error codes, if any, reported by member
functions of the mutex types shall be:
(4.1) — resource_unavailable_try_again — if any native handle type
manipulated is not available.
(4.2) — operation_not_permitted — if the thread does not have the
privilege to perform the operation.
(4.3) — invalid_argument — if any native handle type manipulated as
part of mutex construction is incorrect
In theory you might encounter operation_not_permitted error, but situations when this happens are not really defined in the standard.
So unless you cause UB in your program related to the std::mutex usage or use the mutex in some OS-specific scenario, quality implementations of lock and unlock should never throw.
Among the common implementations, there is at least one that might be of low quality: std::mutex implemented on top of CRITICAL_SECTION in old versions of Windows (I think Windows XP and earlier) can throw after failing to lazily allocate internal event during contention. On the other hand, even earlier versions allocated this event during initialization to prevent failing later, so std::mutex::mutex constructor might need to throw there (even though it is noexcept(true) in the standard).
I have a socket shared between 4 threads and I wanted to use the RAII principle for acquiring and releasing the mutex.
The ground realities
I am using the pthread library.
I cannot use Boost.
I cannot use anything newer than C++03.
I cannot use exceptions.
The Background
Instead of having to lock the mutex for the socket everytime before using it, and then unlocking the mutex right afterwards, I thought I could write a scoped_lock() which would lock the mutex, and once it goes out of scope, it would automatically unlock the mutex.
So, quite simply I do a lock in the constructor and an unlock in the destructor, as shown here.
ScopedLock::ScopedLock(pthread_mutex_t& mutex, int& errorCode)
: m_Mutex(mutex)
{
errorCode = m_lock();
}
ScopedLock::~ScopedLock()
{
errorCode = m_unlock();
}
where m_lock() and m_unlock() are quite simply two wrapper functions around the pthread_mutex_lock() and the pthread_mutex_unlock() functions respectively, with some additional tracelines/logging.
In this way, I would not have to write at least two unlock statements, one for the good case and one for the bad case (at least one, could be more bad-paths in some situations).
The Problem
The problem that I have bumped into and the thing that I don't like about this scheme is the destructor.
I have diligiently done for every function the error-handling, but from the destructor of this ScopedLock(), I cannot inform the caller about any errors that might be returned my m_unlock().
This is a fundamental problem with RAII, but in this case, you're in luck. pthread_unlock only fails if you set up the mutex wrong (EINVAL) or if you're attempting to unlock an error checking mutex from a thread that doesn't own it (EPERM). These errors are indications of bugs in your code, not runtime errors that you should be taking into account. asserting errorCode==0 is a reasonable strategy in this case.
Assuming
no undefined behaviour occurs,
no deadlocks occur,
mutexes are locked and unlocked in the correct order by the correct threads the correct number of times,
non-recursive mutexes are not locked multiple times,
locking recursive mutexes does not exceed the maximum level of ownership,
no predicates passed to condition variables throw, and
only clocks, time points, and durations provided by the standard library are used with the std:: mutexes and condition variables
is it guaranteed that operating on the different types of std:: mutexes and condition variables (other than on constructing them) does not throw any exceptions (especially of type std::system_error)?
For example, in case of methods like:
void MyClass::setVariable() {
std::lock_guard<std::mutex> const guard(m_mutex);
m_var = 42; // m_var is of type int
m_conditionVariable.notify_all();
}
void MyClass::waitVariable() {
std::unique_lock<std::mutex> lock(m_mutex);
m_conditionVariable.wait(lock, [this]() noexcept { return m_var == 42; });
}
Is it safe to assume noexcept or should one write some try-catch blocks around the callsites? Or are there any caveats?
Please consider all types of mutexes and condition variables in C++11, C++14 and later.
Short answer: No (sorry)
Any of these operations will throw std::system_error if the underlying synchronisation object fails to perform its operation.
This is because correct operation of synchronisation primitives depends on:
available system resources.
some other part of the program not invalidating the primitive
Although in fairness, if (1) is happening it's probably time to redesign the application or run it on a less-loaded machine.
And if (2) is happening, the program is not logically consistent.
That being said,
or should one write some try-catch blocks around the callsites?
Also no.
You should write try/catch blocks under the following conditions:
Where the program is in a position to do something useful about the error condition (such as repairing it or asking the user if he wants to try again)
You would like to add some information to the error and re-throw it in order to provide a diagnostic breadcrumb trail (nested exceptions, for example)
You wish to log the failure and carry on.
Otherwise, the whole point of c++ exception handling is that you allow RAII to take care of resource reacquisition and allow the exception to flow up the call stack until is finds a handler that wants to handle it.
example of creating a breadcrumb trail:
void wait_for_object()
try
{
_x.wait(); // let's say it throws a system_error on a loaded system
}
catch(...)
{
std::throw_with_nested(std::runtime_error(__func__));
}
Thank's to the link T.C. provided now I'd say yes — your code should be safe. Since in the future standard device_or_resource_busy will be removed and as the author of the issue says that this situation can't occur in any reasonable way then there are only 2 possibilities for lock to throw:
(13.1) — operation_not_permitted — if the thread does not have the
privilege to perform the operation.
(13.2) — resource_deadlock_would_occur — if the implementation detects
that a deadlock would occur.
And both of these situations are excluded by your preconditions. So your code should be safe to use noexcept.
I'm confused by the example given by Leo Davidson in is Ccriticalsection usable in production?. Leo gives three code blocks introduced as "Wrong (his example)", "Right", and "Even better (so you get RAII)".
After dismissing the first block as "Wrong", Leo acknowledges later that this is something that can occur if a function that obtains a lock calls another function which obtains the same lock. Fine - there is a real danger here to avoid, and the example is not so much "wrong" as an easy trap to fall into through careless programming.
But the second and third examples confuse me completely... because we have one sync object (the CCriticalSection crit) which is used for two CSingleLock locks... implying that crit is not a lockable thing at all, but only the mechanism which does the locking for an independent object or objects. The trouble is, there is a comment saying "crit is unlocked now" right at the end... which contradicts that implication. Also... other comments qualify themselves by the need to test IsLocked()... when in my understanding, the CCriticalSection cannot timeout, and will only ever return if IsLocked() is TRUE.
The Microsoft documentation I have scanned is really not clear about what role the CSyncObject plays and the CSingleLock or CMultiLock plays. That's my main concern. Can anyone point to documentation that definitively says you can create two locks using a single sync object as Leo has suggested here?
After dismissing the first block as
"Wrong", Leo acknowledges later that
this is something that can occur if a
function that obtains a lock calls
another function which obtains the
same lock. Fine - there is a real
danger here to avoid, and the example
is not so much "wrong" as an easy trap
to fall into through careless
programming.
The "wrong" first block is always wrong and should never be something you do, whether explicitly or by accident. You cannot use a CSingleLock to obtain multiple locks at the same time.
As its name suggests, CSingleLock is an object which manages one lock on one synchronization object. (The underlying synchronization object may be capable of being locked multiple times, but not via just a single CSingleLock.)
I meant that the other two code-blocks were situations you could run into legitimately.
You never need to lock the same CCriticalSection if you already have a lock on it (since you only need one lock to know you own the object), but you may lock it multiple times (usually as a result of holding the lock, then calling a function which gets the lock itself in case it is called by something that doesn't already have it). That's fine.
But the second and third examples
confuse me completely... because we
have one sync object (the
CCriticalSection crit) which is used
for two CSingleLock locks... implying
that crit is not a lockable thing at
all, but only the mechanism which does
the locking for an independent object
or objects.
You can lock a CCriticalSection directly (and multiple times if you want to). It has Lock and Unlock methods for doing that.
If you do that, though, you have to ensure that you have matching Unlock calls for every one of your Lock calls. It can be easy to miss one (especially if you use early returns or exceptions where an Unlock later in a function may be bypassed entirely).
Using a CSingleLock to lock a CCriticalSection is usually better because it will release the lock it holds automatically when it goes out of scope (including if you return early, throw an exception or whatever).
Can anyone point to documentation that
definitively says you can create two
locks using a single sync object as
Leo has suggested here?
Although I couldn't find the source, CCriticalSection (like most MFC objects) is almost certainly a very thin wrapper around the Win32 equivalent, in this case CRITICAL_SECTION. The documentation on EnterCriticalSection tells you:
After a thread has ownership of a
critical section, it can make
additional calls to
EnterCriticalSection or
TryEnterCriticalSection without
blocking its execution. This prevents
a thread from deadlocking itself while
waiting for a critical section that it
already owns. The thread enters the
critical section each time
EnterCriticalSection and
TryEnterCriticalSection succeed. A
thread must call LeaveCriticalSection
once for each time that it entered the
critical section.