相互调用的互斥函数

Mutually exclusive functions calling each other

我有两个函数 foobar,这两个函数应该是互斥的,因为它们对相同的数据进行操作。但是 foobar 复制了很多代码,所以我想重构 foo 来调用 bar.

这是一个问题,因为我不能对这两个函数使用一个互斥量,因为 foo 在调用 bar 时会死锁。所以而不是 "mutually exclusive" 我只想要 "mutually exclusive from different threads".

有实现这个的模式吗?我正在使用 C++,如果我需要像 shared_mutex.

这样的东西,我可以使用 C++14/boost

定义一个私有 "unlocked" 函数并在 foobar 中使用它:

void bar_unlocked()
{
    // assert that mx_ is locked
    // real work
}

void bar()
{
    std::lock_guard<std::mutex> lock(mx_);
    bar_unlocked();
}

void foo()
{
    std::lock_guard<std::mutex> lock(mx_);
    // stuff
    bar_unlocked();
    // more stuff
}

另一种方式 - 这种方式的优点是您可以证明锁已被占用:

void bar_impl(std::unique_lock<std::mutex> lock)
{
   assert(lock.owns_lock());
    // real work
}

void bar()
{
    bar_impl(std::unique_lock<std::mutex>(mx_));
}

void foo()
{
    // stuff
    bar_impl(std::unique_lock<std::mutex>(mx_));
    // more stuff
}

理由:

std::mutex 不是(标准规定的)可移动的,但 std::unique_lock<std::mutex> 是。出于这个原因,我们可以将锁移到被调用者中,然后 return 将其返回给调用者(如果需要)。

这使我们能够在调用链的每个阶段证明锁的所有权。

此外,一旦优化器介入,很可能所有的锁移动都会被优化掉。这为我们提供了两全其美的方式 - 可证明的所有权和最大性能。

一个更完整的例子:

#include <mutex>
#include <cassert>
#include <functional>

struct actor
{
  //
  // public interface
  //

  // perform a simple synchronous action
  void simple_action()
  {
    impl_simple_action(take_lock());
  }

  /// perform an action either now or asynchronously in the future
  /// hander() is called when the action is complete
  /// handler is a latch - i.e. it will be called exactly once
  /// @pre an existing handler must not be pending
  void complex_action(std::function<void()> handler)
  {
    impl_complex_action(take_lock(), std::move(handler));
  }

  private:

  //
  // private external interface (for callbacks)
  //
  void my_callback()
  {
    auto lock = take_lock();
    assert(!_condition_met);
    _condition_met = true;
    impl_condition_met(std::move(lock));
  }


  // private interface

  using mutex_type = std::mutex;
  using lock_type = std::unique_lock<mutex_type>;

  void impl_simple_action(const lock_type& lock)
  {
    // assert preconditions
    assert(lock.owns_lock());
    // actions here
  }

  void impl_complex_action(lock_type my_lock, std::function<void()> handler)
  {
    _handler = std::move(handler);
    if (_condition_met)
    {
      return impl_condition_met(std::move(my_lock));
    }
    else {
      // initiate some action that will result in my_callback() being called
      // some time later
    }
  }

  void impl_condition_met(lock_type lock)
  {
      assert(lock.owns_lock());
      assert(_condition_met);
      if(_handler)
      {
        _condition_met = false;
        auto copy = std::move(_handler);
        // unlock here because the callback may call back into our public interface
        lock.unlock();
        copy();
      }
  }



  auto take_lock() const -> lock_type
  {
    return lock_type(_mutex);
  }


  mutable mutex_type _mutex;

  std::function<void()> _handler = {};
  bool _condition_met = false;
};

void act(actor& a)
{
  a.complex_action([&a]{ 
    // other stuff...
    // note: calling another public interface function of a
    // during a handler initiated by a
    // the unlock() in impl_condition_met() makes this safe.
    a.simple_action(); 
  });

}