Starvation with upgrade_lock

2019-04-09 17:10发布

问题:

I am trying to use Boost's upgrade_lock (using this example, but I run into a starvation issue.

I am actually using the code from this post, but I wanted an up-to-date discussion. I run 400 threads after the WorkerKiller. I run into the exact same problem as anoneironaut, the author of the mentionned post.

I have seen the proposition from Howard Hinnant, but I don't really want to include more external code (moreover I cannot get his to compile as of now) and a comment posted 6 months later states that "Boost uses a fair implementation now" (Dec 3 '12).

The Boost 1.55 documentation states that:

Note the the lack of reader-writer priority policies in shared_mutex. This is 
due to an algorithm credited to Alexander Terekhov which lets the OS decide 
which thread is the next to get the lock without caring whether a unique lock or 
shared lock is being sought. This results in a complete lack of reader or writer
starvation. It is simply fair.". 

And the algorithm credited to Alexander Terekhov is the one that Howard Hinnant talks about, so I would expect the 1.55 boost implementation to behave like in Howard Hinnant's answer, which is not the case. It behaves exactly like in the question.

Why is it the case that my WorkerKiller suffers of starvation?

UPDATE: It was observed with this code on:

  • Debian x64, Boost 1.55 (both the Debian version and one compiled from sources), with both clang++ and g++
  • Ubuntu x64, Boost 1.54, with both clang++ (3.4-1ubuntu1) and g++ (4.8.1-10ubuntu9)

回答1:

This is a subtle one. The difference involves the concepts of shared and upgradable ownerships, and their implementations in Boost.

Let's first get the concepts of shared ownership and upgradable ownership sorted out. For a SharedLockable, a thread must decide beforehand whether it wants to change the object (requiring exclusive ownership) or only read from it (shared ownership suffices). If a thread with shared ownership decides it wants to change the object, it first must release its shared lock on the object and then construct a new, exclusive lock. In between these two steps, the thread holds no locks at all on the object. Attempting to construct an exclusive lock from a thread that already holds a shared lock will deadlock, as the exclusive lock constructor will block until all shared locks have been released.

UpgradeLockable overcomes this limitation, by allowing to upgrade a shared lock to an exclusive lock without releasing it. That is, the thread keeps an active lock on the mutex at all times, prohibiting other threads from obtaining an exclusive lock in the meantime. Besides that, UpgradeLockable still allows all operations from SharedLockable, the former concept is a superset of the latter. The question you linked to is only concerned with the SharedLockable concept.

Neither concept, as specified by Boost, requires an implementation to be fair. However, the shared_mutex, which is Boost's minimal implementation for a SharedLockable does give the fairness guarantees quoted in your question. Note that this is an additional guarantee to what the concept actually requires.

Unfortunately, the minimal implementation for upgradable ownership, the upgrade_mutex, does not give this additional gurantee. It still implements the shared ownership concept as a requirement for upgradable ownership, but since fairness is not required for a conforming implementation, they do not provide it.

As pointed out by Howard in the comments, Terekhov's algorithm can be trivially adjusted to work with upgradable locks as well, it's just that the Boost implementation does not support this currently.