Is there any idiomatic explicit use of mutex::lock

2019-05-06 16:35发布

问题:

The recommended way to use a mutex for locking a critical region of code is via RAII, i.e.

mutex_type mutex;
{ // start of critical region
  std::lock_guard<mutex_type> lock(mutex);   // first statement in critical region
  // ... do critical stuff, may throw an exception
} // end of critical region

so that when an exception is thrown within the critical region, the mutex will still be unlocked (by the destructor of std::lock_guard). However, this way the members mutex::lock() and mutex::unlock() are never explicitly called by user code.

Q What, if any, is the major idiomatic explicit use of mutex::lock()?

I'm asking, for otherwise there is no point in having mutex::lock() a public member promoting bad code (one which avoids std::lock_guard).

Edit Since both std::lock_guard<> and std::mutex are defined in the same header, std::mutex could easily befriend std::lock_guard<std::mutex and have its lock() and unlock() methods protected:

class mutex      // use only with lock_guard<mutex>
{
  friend class lock_guard<mutex>;         // support acquire-release semantic via RAII
  friend class scoped_lock_guard<mutex>;  // for supporting more complicated semantic,
                                          //     possibly remembering the mutex state.
  // ...
protected:
  void lock();
  bool try_lock();
  void unlock();       
};

class raw_mutex  // use if you absolutely must explicitly lock, try_lock, or unlock
: public mutex
{
public:
  using mutex::lock;
  using mutex::try_lock;
  using mutex::unlock;
};

An argument towards answering my question is simply that the only exception-safe way to use mutex::lock() is via RAII. Thus the only sensible explicit use must involve only noexcept methods between the calls to lock (or try_lock) and unlock. However, since noexcept is only suggestive and holds no promise whatsover, such use would be unsafe. Q correct?

回答1:

lock_guard isn't the only thing that needs to call lock/unlock on a mutex. unique_lock, lock, try_lock and condition_variable_any all have to work on mutexes as well. And that is just the standard types. Friendship in this case introduces a tight coupling that becomes a hinderance.



回答2:

RAII can be (and should be) used when there is a static scope to which the lifetime of a resource can be bound, i.e. the resource should be initialized when the scope is entered and freed when the scope is exited.

There are situations, however, when there is no such static scope. To illustrate this, consider variables as resources. We use automatic variables when there is a static scope to which we can bound them, but occasionally we need dynamic variables to control when they are created and destroyed.

The same can happen when we use mutexes for mutual exclusion. Consider this task:

Your program controls two resources, it has to read and execute a sequence of commands. Possible commands are:

  • lock resource #1 : 1>
  • lock resource #2 : 2>
  • unlock resource #1 : 1<
  • unlock resource #2 : 2<
  • write x to resource #1 : 1x
  • write x to resource #2 : 2x

your program can expect inputs like 1> 1a 2> 2b 1c 1< 2d 2<, which makes the use of static scopes for RAII impossible if the program obeys the commands (the scopes would have to overlap partially). So I think this illustrates one situation when explicit lock/unlock is necessary.


There is at least one other possible scenario which comes from the fact that mutual exclusion is not the only situation when one could use a mutex: it can be used for synchronisation as well.

Consider two parallel processes, P and Q, each with a designated point in their code. We require that Q cannot pass its designated point until P reaches its own. This requirement can be met by using a mutex initialised to locked state and placing a lock() operation right before the designated point of Q and an unlock() right after the designated point of P. It can be easily verified that this setup solves the problem.

Here lock() is placed in one process and unlock() in another, again there is obviously no static scope that can enclose them, so we need both to be accessible independently.