访问由 shared_ptr 持有的类的原子成员

Posted

技术标签:

【中文标题】访问由 shared_ptr 持有的类的原子成员【英文标题】:Accessing an atomic member of a class held by a shared_ptr 【发布时间】:2018-06-09 05:04:36 【问题描述】:

我正在尝试创建一个小类,以便我促进两个线程之间的通信。

这些线程很可能会比创建上述类的上下文寿命更长,因为它们在线程池中排队。

到目前为止我所尝试的 (on coliru as well):

class A    

public:
    A(int maxVal) : maxValue(maxVal)     
    bool IsOverMax() const  return cur >= maxValue;     
    void Increase()  cur++; 

private:
    const int  maxValue;    
    atomic_int cur 0 ;
;

可能的用法:

void checking(const shared_ptr<A> counter)

    while(!counter->IsOverMax())
    
        cout<<"Working\n";         // do work
        std::this_thread::sleep_for(10ms);
    


void counting(shared_ptr<A> counter)

    while (!counter->IsOverMax())
    
        cout<<"Counting\n";
        counter->Increase(); // does this fall under `...uses a non-const member function of shared_ptr then a data race will occur`?  http://en.cppreference.com/w/cpp/memory/shared_ptr/atomic
        std::this_thread::sleep_for(9ms);
    


int main() 
 

    unique_ptr<thread> t1Ptr;
    unique_ptr<thread> t2Ptr;

    
        auto aPtr = make_shared<A>(100); // This might be out of scope before t1 and t2 end
        t1Ptr.reset(new thread(checking, aPtr)); // To simbolize that t1,t2 will outlive the scope in which aPtr was originaly created
        t2Ptr.reset(new thread(counting, aPtr));
    

    t2Ptr->join();
    t1Ptr->join();
    //cout<< aPtr->IsOverMax();

我担心的原因是documentation 说:

如果多个执行线程在没有同步的情况下访问同一个 std::shared_ptr 对象,并且这些访问中的任何一个使用了 shared_ptr 的非 const 成员函数,那么将发生数据竞争,除非所有此类访问都是通过这些函数执行,它们是相应原子访问函数(std::atomic_load、std::atomic_store 等)的重载

所以Increase 是一个非常量函数,对于这种情况,aPtr 的副本是否是the same std::shared_ptr? 此代码是线程安全的吗? 这对于非原子对象是否可行(例如使用 std::mutex 锁定对常规 int 的读取和写入)? 无论如何,为什么?

【问题讨论】:

【参考方案1】:

该文档是在讨论shared_ptr 的成员函数,而不是你的类的成员函数。 shared_ptr 对象的副本是不同的对象。

我相信代码是线程安全的,因为在不同线程上写入和读取的唯一可变变量是cur,并且该变量是原子的。

如果cur 不是原子的,并且在Increase()IsOverMax() 中对其的访问受到锁定std::mutex 的保护,那么该代码也将是线程安全的。

【讨论】:

【参考方案2】:

因此,Increase 是一个非常量函数,aPtr 的副本是否与此上下文中的 std::shared_ptr 相同?

std::thread 创建时,aPtr 是按值传递的。因此,保证:

您不会引入数据竞争,因为每个线程都有自己的 shared_ptr 实例(尽管它们管理同一个对象 A)。 您所指的文档描述了多个线程在同一个 shared_ptr 实例上运行的场景。 在这种情况下,只能调用 const 成员函数(见下文),或者需要同步。 shared_ptr 引用计数在 aPtr 超出 main 的范围之前递增

所以是的,这是使用shared_ptr 的正确方法。

这段代码是线程安全的吗?

您的代码不会引入数据竞争,既不能访问shared_ptr 实例,也不能访问托管对象A。 这意味着多个线程不会对同一内存位置执行冲突的、非原子的读取和写入操作。

但是,请记住,在 checking() 中,对 IsOverMax() 的调用与随后的实际工作是分开的 (Increase() 可以在 IsOverMax() 之后但在 'do work' 之前由第二个线程调用)。因此,您可以在 cur 已超过其最大值时“工作”。 这是否是一个问题取决于您的规范,但它被称为竞争条件,不一定是编程错误(与导致未定义行为的数据竞争不同)。

这对于非原子对象是否可行(例如使用 std::mutex 锁定对常规 int 的读取和写入)?

cur 可以是普通的int(非原子),如果你用std::mutex 保护它。必须为写 读访问锁定互斥锁,以防止数据争用。

关于在多线程共享的对象上调用const 成员函数的备注。 单独使用const 并不能保证不会引入数据竞争。 在这种情况下,保证适用于shared_ptr const 成员函数,因为文档是这样说的。 我在 C++ 标准 中找不到该保证是否适用于 标准库

中的所有 const 成员函数

【讨论】:

以上是关于访问由 shared_ptr 持有的类的原子成员的主要内容,如果未能解决你的问题,请参考以下文章

类的 boost::shared_ptr 作为结构成员

使用 boost::shared_ptr 访问静态成员变量

接口访问权限

boost::variant 中持有的类的复制构造函数的问题

boost shared_ptr 初始化为类成员

智能指针是否线程安全