In some deadlock avoidance algorithms, at runtime we need to ensure we immediately release every lock already acquired, without hesitation, so to speak. Here’s one design my friend Shanyou shared with me.
- Tip: limit the number of “grabbing” functions, functions that explicit grab any lock.
Suppose a grabbing function f1 acquires a lock and calls another function f2 (a red flag to concurrency designers). In such a situation, f2() will use -1 return value to indicate “must release lock”. If f2() calls a grabbing function f3(), and if f3() fails to grab, it propagates “-1” to f2 and f1.
- f3() could use trylock.
- f3() could check lock hierarchy and return -1.
Q: what if a thread somehow fails before releasing mutex?
This is a good question, again. I see only three scenarios:
* If machine loses power, then releasing mutex or not doesn’t matter. No difference.
* If process crashes but the mutex is in shared memory, then we are in trouble. The mutex will be seen as forever in-use. The other process can’t get this mutex. * If process is still alive, I rely on stack unwinding.
Stack unwinding is set up by compiler. The only situation when this compiler-generated stack unwinding is incomplete is — if the failing function is declared noexcept. (In such a case, the failure is your problem since you promised to compiler it should never throw exception.) I will assume we don’t have a noexcept function. Therefore, I assume stack unwinding is robust and all stack objects will be destructed.
If one of the stack objects is a std::unique_lock, then compiler guarantees an unlocked status on destruction. That’s the highest reliability I can achieve.
The #1 usage of atomic<int> is load() and store(). I will use short form “load/store” or “l/s”.
The #2 usage is CAS. Interviewers are mostly interested in this usage, though I won’t bother to remember the function names —
The CAS usage is same as AtomicInteger.java, but the load/store usage is more like the thread-safety feature of Vector.java. To see the need for load/store, we need to realize the simple “int” type assignment is not atomic :
- P1012 [[c++ standard library]] shocked me by affirming that without locks, you can read a “half-written Boolean” .
To solve this problem, atomic<int> uses internal locks (just like Vector.java) to ensure load() and store() is always atomic.
 different from java. https://stackoverflow.com/questions/11459543/should-getters-and-setters-be-synchronized points out that 32-bit int in java is never “half-written”. If you read a shared mutable int in java, you can hit a stale value but never a c++ style half-written value. Therefore, java doesn’t need guarded load()/store() functions on an integer.
Q: are these c++ atomic types lock-free?
A: for load/store — not lock-free. See P 1013
A: for CAS — lock-free CPU instructions are used, if available.
There are many contexts. I only know a few.
1st, let’s look at an socket context. Suppose there are many (like 500 or 50) sockets to process. We don’t want 50 threads. We prefer fewer, perhaps 1 thread to check each “ready” socket, transfer whatever data can be transferred then go back to waiting. In this context, we need either
- /readiness notification/, or
- … Both are compared on P51 [[TCP/IP sockets in C]]
2nd scenario — GUI. Blocking a UI-related thread (like the EDT) would freeze the screen.
3rd, let’s look at some DB request client. The request thread sends a request and it would take a long time to get a response. Blocking the request thread would waste some memory resource but not really CPU resource. It’s often better to deploy this thread to other tasks, if any.
Q: So what other tasks?
A: ANY task, in the thread pool design. The requester thread completes the sending task, and returns to the thread pool. It can pick up unrelated tasks. When the DB server responds, any thread in the pool can pick it up.
This can be seen as a “server bound” system, rather than IO bound or CPU bound. Both the CPU task queue and the IO task queue gets drained quickly.
There’s a buffer in a DB connection, in a file handle, in a socket …
The buffer is a shared mutable object. Consider a read-buffer. The host object knows how much of the data in the buffer was already delivered, to avoid repeated delivery. There’s some kind of watermark, which is moved by a consumer thread.
As all shared mutables, these objects are thread unsafe.
All of these objects can also be allocated on stack and therefore invisible to other threads. Therefore, this could be the basis of a thread-safe design.
In java, mutex is always in heap. Primitive objects can’t be host to a mutex.
In pthreads, mutex object can be allocated anywhere, but I have seen it allocated only in heap or in global area.
In C, you can also allocate mutex in shared memory, creating a cross-process mutex! Not a common practice in java.
Q: what if I allocate a mutex in stack?
A: in java,the actual object is still on heap,though the variable is on stack and invisible to other threads
A: in C, the entire mutex object can be on stack, but such a mutex is useless. Imagine a lock on a door with a single key to be shared, but other people have their own doors, so there’s no synchronization no access control at all:(
This is available in pthreads (pthread_mutex_trylock()).
I believe java and boost::thread are all based on that.
Across all languages, I have never seen any exception. You must enclose wait() in a while-loop.
C++11 has a syntactic sugar — wait(myLock, myPredicate), hiding the while loop.
- notify() doesn’t strictly need the lock;
- wait() always requires the lock.
- c++ wait() takes the lock as argument
- old jdk uses the same object as lock and conditionVar
- jdk5 makes the lock beget the conditionVar
—- original jdk
100% — notify() must be called within synchronized block, otherwise exception. See https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#notify()
—- jdk 5 onwards
99% — https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/Condition.html#signal() says that An implementation may (and typically does) require the lock acquired by the notifying thread.
Not 100% strict.
— compare await():
100% — On the same page, the await() javadoc is 100% strict on this requirement!
0% — notify_all() method does NOT require holding the lock. See http://en.cppreference.com/w/cpp/thread/condition_variable/notify_all
100% — wait(myLock) requires a lock as argument!
100% — Similar to old JDK, the notifying thread must hold the lock. See https://msdn.microsoft.com/en-us/library/system.threading.monitor.pulseall(v=vs.110).aspx
See also blog on c++ atomic<int> — primary usage is load/store, not CAS
lock free isn’t equal to CAS — lock-free construct also include Read-Copy-Update, widely used in linux and other kernels.
atomic isn’t equal to lock-free — For example, c++ atomic classes (actually template) can use locks if the underlying processor doesn’t have CAS instruction.