相互调用的互斥函数
Mutually exclusive functions calling each other
我有两个函数 foo
和 bar
,这两个函数应该是互斥的,因为它们对相同的数据进行操作。但是 foo
从 bar
复制了很多代码,所以我想重构 foo
来调用 bar
.
这是一个问题,因为我不能对这两个函数使用一个互斥量,因为 foo
在调用 bar
时会死锁。所以而不是 "mutually exclusive" 我只想要 "mutually exclusive from different threads".
有实现这个的模式吗?我正在使用 C++,如果我需要像 shared_mutex.
这样的东西,我可以使用 C++14/boost
定义一个私有 "unlocked" 函数并在 foo
和 bar
中使用它:
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();
});
}
我有两个函数 foo
和 bar
,这两个函数应该是互斥的,因为它们对相同的数据进行操作。但是 foo
从 bar
复制了很多代码,所以我想重构 foo
来调用 bar
.
这是一个问题,因为我不能对这两个函数使用一个互斥量,因为 foo
在调用 bar
时会死锁。所以而不是 "mutually exclusive" 我只想要 "mutually exclusive from different threads".
有实现这个的模式吗?我正在使用 C++,如果我需要像 shared_mutex.
这样的东西,我可以使用 C++14/boost定义一个私有 "unlocked" 函数并在 foo
和 bar
中使用它:
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();
});
}