简介:对于同步,C#提供System.Threading.Monitor
类,提供线程同步例程,例如Enter()
,Exit()
,TryEnter()
等
此外,还有lock
语句确保在关键代码块被丢弃时,通过正常执行流或异常来锁定锁:
private static readonly obj = new Object();
lock(obj) {
...
}
问题:在C ++中,为此目的,我们得到了RAII包装器std::lock_guard
和std::unique_lock
,它们不适用于Monitor
类,而是适用于类型Lockable
概念。但是,我认为这种方法在语法上比C#实现它的方式更弱,原因如下:
您使用无法重复使用的变量名污染本地范围。这可以通过添加新的范围来抵消,例如
{
std::unique_lock<std::mutex> lck{ mtx };
...
}
但我发现这种符号看起来很尴尬。更让我感到困扰的是这是有效的C ++:
std::unique_lock<std::mutex>{ mtx ]; // note there is no name to the lock!
...
因此,如果忘记给锁定卫士一个正确的名字,这个陈述将被解释为一个名为&#34; mtx&#34;的变量声明。类型std::unique_lock<std::mutex>
,没有任何锁定!
我想在C ++中实现类似C#的lock
语句。在C ++ 17中,这可以很容易地完成:
#define LOCK(mutex) if(std::lock_guard<decltype(mutex)> My_Lock_{ mutex }; true)
std::mutex mtx;
LOCK(mtx) {
...
}
问:我如何在C ++ 11/14中实现它?
答案 0 :(得分:2)
暂且不说&#34;如果你这样做&#34;,请按照以下方式:
虽然它并不完全相同,因为它需要一个分号,它足够接近我觉得我可能会呈现它。这个纯C ++ 14解决方案基本上只是定义宏来启动一个立即执行的lambda:
template<typename MTX>
struct my_lock_holder {
MTX& mtx;
my_lock_holder(MTX& m) : mtx{m} {}
};
template<typename MTX, typename F>
void operator+(my_lock_holder<MTX>&& h, F&& f) {
std::lock_guard<MTX> guard{h.mtx};
std::forward<F>(f)();
}
#define LOCK(mtx) my_lock_holder<decltype(mtx)>{mtx} + [&]
my_lock_holder
稍后会获取互斥锁参考,并允许我们重载operator+
。这个想法是操作员创建警卫并执行lambda。正如您所看到的,宏定义了一个默认的引用捕获,因此lambda将能够引用封闭范围内的任何内容。然后它非常直接:
std::mutex mtx;
LOCK(mtx) {
}; // Note the semi-colon
你可以看到它构建 live 。
答案 1 :(得分:1)
受到StoryTeller的好主意的启发,我认为自己找到了一个可行的解决方案,尽管它有点像&#34; hack&#34;:
template <typename T>
struct Weird_lock final : private std::lock_guard<T> {
bool flip;
Weird_lock(T& m) : std::lock_guard<T>{ m }, flip{ true } { }
operator bool() noexcept {
bool old = flip;
flip = false;
return old;
}
};
#define LOCK(mutex) for(Weird_lock<decltype(mutex)> W__l__{ mutex }; W__l__;)
好消息是它最终不需要分号。不好的是需要额外的bool
,但是从我在godbolt.org中看到的情况来看,编译器无论如何都会优化它。
答案 2 :(得分:0)
我建议你这样做:
#define UNIQUE_NAME(name) name##__COUNTER__
#define LOCK(mutex) std::lock_guard<decltype(mutex)> UNIQUE_NAME(My_Lock){ mutex };
使用 COUNTER 预处理程序符号将生成一个您根本不关心的唯一变量名称。