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&amp;。不是陷阱。 @Jarod42:是的,这就是意图。否则,我认为,我必须解锁析构函数中的互斥锁。有什么更好的选择吗? 互斥锁在operator=末尾解锁,所以返回bracket_operator_proxy&amp;应该没问题。 BTW std::vector&lt;std::atomic&lt;double&gt;&gt; 似乎是一个不错的选择。 我在您的代码中看不到任何缺陷 和@Jarod42 一样,我也会使用std::vector<:atomic>>。在不影响相同矢量元素的情况下允许并发工作,更少的编码(和更少的出错机会)。 【参考方案1】:

拥有operator-&gt;(非常有用)后,您需要返回一个-&gt; 代理,该代理将锁的生命周期延长到语句结束,并使您面临单线程死锁。

查看线程安全的 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++ 线程安全括号运算符代理的主要内容,如果未能解决你的问题,请参考以下文章

+= 运算符在 Python 中是线程安全的吗?

如何安全地终止线程? (使用指针)C++

C++ 是不是有任何线程安全可以写入(比没有锁的线程安全类似物更快)组件?

犰狳 C++ 矩阵线程安全吗

C++ 中的线程安全单例实现

C++ 线程安全的单例模式总结