C++ 线程安全括号运算符代理
Posted
技术标签:
【中文标题】C++ 线程安全括号运算符代理【英文标题】:C++ thread-safe bracket operator proxy 【发布时间】:2016-04-08 23:11:55 【问题描述】:给定一个标准向量的简单包装器,以线程安全的方式实现operator[]
以便能够像往常一样设置内容的好方法是什么?
struct bracket_operator_proxy;
struct example
auto operator[](size_t i) const return bracket_operator_proxy(v, m, i);
private:
std::vector<double> v;
std::mutex m;
;
这是我对bracket_operator_proxy
的快速而天真的尝试:
struct bracket_operator_proxy
bracket_operator_proxy(std::vector<double>& v, std::mutex& m, int i)
: v(v), m(m), i(i)
operator double() const
std::lock_guard<std::mutex> l(m);
return v[i];
auto operator=(double d)
std::lock_guard<std::mutex> l(m);
v[i] = d;
return d;
//... further assignment operators ...
private:
std::vector<double>& v;
std::mutex& m;
int i;
;
这已经足够了吗?还是我错过了什么会炸掉我的腿?
【问题讨论】:
您目前禁止(example[i] = 4.2) = 42;
作为您的 operator =
返回 double
而不是 bracket_operator_proxy&
。不是陷阱。
@Jarod42:是的,这就是意图。否则,我认为,我必须解锁析构函数中的互斥锁。有什么更好的选择吗?
互斥锁在operator=
末尾解锁,所以返回bracket_operator_proxy&
应该没问题。 BTW std::vector<std::atomic<double>>
似乎是一个不错的选择。
我在您的代码中看不到任何缺陷
和@Jarod42 一样,我也会使用std::vector<:atomic>>。在不影响相同矢量元素的情况下允许并发工作,更少的编码(和更少的出错机会)。
【参考方案1】:
拥有operator->
(非常有用)后,您需要返回一个->
代理,该代理将锁的生命周期延长到语句结束,并使您面临单线程死锁。
查看线程安全的 monads/functor/wrapper,例如 the one here。它不会使锁完全透明,但它们不应该是。
不要在线程之间共享数据
如果您共享数据,请使其不可变
如果必须对其进行突变,请通过已知安全设计的瓶颈隔离访问。一个消息队列说。
如果做不到,请考虑重新设计
真的。可能是原子的?
有一组有限的函数来明确管理锁
好的,现在像上面一样包装在 reader/writer monad 中,使用简单的复合操作
编写神奇地获得锁的代码,看起来就像非线程交互代码,从而使您的读者误以为安全和效率
偏好递减。
线程安全的危险和困难的部分不是语法笨拙的事实。正是基于锁的线程安全几乎不可能被证明是正确和安全的。使语法更易于使用并不是一个高价值的目标。
例如,v[i]=v[i+1]
缺乏同步:在读取和写入之间,任何事情都可能发生变化。更别说“i
是一个有效的索引吗?”
【讨论】:
感谢您的回答、线程安全包装器的链接以及可能出错的示例。不知何故,在我看来,多线程必须是丑陋的...... 鉴于我提供了使用指针而不是引用并提供了合理的copy constructor 和类赋值运算符,它是否成为线程安全的? (我知道你说几乎不可能证明,所以请跟随你的直觉)。问题是我已经在我的库中的不同位置使用了operator[]
,并进一步鼓励用户扩展功能——然后imo应该尽可能简单。
@davidhigh 单线程代码和多线程代码,在多线程代码中,您在不同位置获取和释放锁,而其他线程修改内容,是根本不同的野兽。像a[i] = a[i-1]+1
这样简单,在多线程代码中与单线程代码的含义完全不同(单线程后置条件是a[i]
比a[i-1]
大一个,但那不是真的在多线程代码中)。编译现有代码是编写多线程代码的一种可怕方式。如果您有超过 1 个锁或不可重入锁,它几乎总是会被锁定。以上是关于C++ 线程安全括号运算符代理的主要内容,如果未能解决你的问题,请参考以下文章