在 C++ 中模拟 C# lock 语句
Posted
技术标签:
【中文标题】在 C++ 中模拟 C# lock 语句【英文标题】:Emulate C# lock statement in C++ 【发布时间】:2017-12-01 22:42:20 【问题描述】:简介:对于同步,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!
...
因此,如果忘记为锁守卫提供正确的名称,该语句将被解释为类型为std::unique_lock<std::mutex>
的名为“mtx”的变量声明,而没有任何锁定!
我想在 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 中实现这一点?
【问题讨论】:
题外话,但明智的说法是:那些标识符(包含双下划线)are reserved to the implementation for any use。 @StoryTeller A'ight,我只是想确保这个名字足够晦涩,不会与任何东西发生冲突。 您可能应该习惯 C# 和 C++ 是完全不同的语言,而不是试图与语言对抗。我认为std::unique_lock<std::mutex> lock mtx ;
非常“自然”,但private static readonly obj = new Object();
看起来非常复杂。你为什么不需要一个宏呢? :-)
@BoPersson 这只是一个变量初始化,它在不同的场合看起来会非常不同。虽然是两种不同的语言,但我认为尝试在另一种语言中采用在一种语言中运行良好的东西并没有错。
这取决于您对语言进行了多大程度的修改以使其看起来很熟悉。来自 Pascal 的人可能会尝试使用 #define begin
和 #define end
来让自己有宾至如归的感觉,但很快就不得不放弃了。
【参考方案1】:
抛开“你应该这样做”,这里是如何:
虽然不完全一样,因为它需要一个分号,但它足够接近,我觉得我可以展示它。这个纯 C++14 解决方案基本上只是定义了一个宏来启动一个立即执行的 lambda:
template<typename MTX>
struct my_lock_holder
MTX& mtx;
my_lock_holder(MTX& m) : mtxm
;
template<typename MTX, typename F>
void operator+(my_lock_holder<MTX>&& h, F&& f)
std::lock_guard<MTX> guardh.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。
【讨论】:
这绝对有用且非常聪明,因此点赞。类似的事情也可以用 C++11 完成吗? @Jodocus - 你知道,我认为我没有使用任何 C++14 特定功能。我只是没有在 C++11 模式下尝试过。它可能会很好地工作。【参考方案2】:受 StoryTeller 的好主意的启发,我想我自己找到了一个可行的解决方案,尽管有点“黑客”:
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 中看到的情况来看,编译器无论如何都会优化它。
【讨论】:
【参考方案3】:我建议你这样做:
#define UNIQUE_NAME(name) name##__COUNTER__
#define LOCK(mutex) std::lock_guard<decltype(mutex)> UNIQUE_NAME(My_Lock) mutex ;
使用 COUNTER 预处理器符号将生成一个您根本不关心的唯一变量名。
【讨论】:
这里的问题是,在锁作用域结束时,锁不会被释放,但是当包含LOCK(mutex)语句的作用域离开时。以上是关于在 C++ 中模拟 C# lock 语句的主要内容,如果未能解决你的问题,请参考以下文章