std :: atomic达到什么程度?

时间:2019-11-28 22:23:05

标签: c++ c++11

在Sean Parent https://youtu.be/W2tWOdzgXHA的C ++ Seasoning视频中,在33:41开始谈论“没有原始同步原语”时,他举了一个例子来说明使用原始同步原语我们会弄错。该示例是写类的错误副本:

template <typename T>
class bad_cow {
    struct object_t {
        explicit object_t(const T& x) : data_m(x) { ++count_m; }
        atomic<int> count_m;
        T data_m;
    };
    object_t* object_m;

public:
    explicit bad_cow(const T& x) : object_m(new object_t(x)) { }
    ~bad_cow() { if (0 == --object_m->count_m) delete object_m; }
    bad_cow(const bad_cow& x) : object_m(x.object_m) { ++object_m->count_m; }

    bad_cow& operator=(const T& x) {
        if (object_m->count_m == 1) {
            // label #2
            object_m->data_m = x; 
        } else {
            object_t* tmp = new object_t(x);
            --object_m->count_m; // bug #1
            // this solves bug #1:
            // if (0 == --object_m->count_m) delete object_m;
            object_m = tmp;
        }
        return *this;
    }
};

然后,他要求观众找到错误,即他确认的错误#1。

但是我猜一个更明显的错误是,当某个线程将要执行执行我用标签#2表示的代码行时,突然之间,另一个线程只是破坏了对象和析构函数被称为,将删除object_m。因此,第一个线程将遇到一个已删除的内存位置。

我是对的吗?我好像不是这样!

2 个答案:

答案 0 :(得分:3)

  

其他一些线程只是破坏对象,而析构函数是   调用,删除object_m。因此,第一个线程将遇到一个   删除的内存位置。

     

我是对的吗?我好像不是这样!

假定程序的其余部分不是错误的,那应该不会发生,因为每个线程都应该有自己的引用计数对象,该对象引用data_m对象。因此,如果线程B具有引用数据对象的bad_cow对象,则线程A无法(或至少不应)删除该对象,因为count_m字段永远不会降为零,因为只要至少有一个引用计数对象指向它。

当然,一个有缺陷的程序可能会遇到您建议的竞争条件-例如,一个线程可能仅持有指向数据对象的原始指针,而不是增加其引用计数的bad_cow;否则,多虫线程可能会在对象上显式调用delete,而不是依靠bad_cow类来正确处理删除。

答案 1 :(得分:1)

您的异议并不成立,因为此时*this指向该对象并且计数为1。除非有人没有正确玩此游戏,否则计数器不能为0(但在这种情况下,任何事物都可以还是发生)。

另一个类似的异议可能是,当您分配给*this并且正在执行的代码在#2分支内时,另一个线程复制了*this;即使第二个线程只是在读取指向的对象,也可能由于分配而使其突然发生变化。这种情况下的问题是,在进行突变的线程中输入count时,if为1,但之后立即增加。

但是,这也是一个不好的反对意见,因为此代码处理指向对象的并发(例如std::shared_ptr这样),但是您不允许突变和读取bad_cow的单个实例不同线程的类。换句话说,bad_cow的单个实例不能在多个线程中使用,如果其中一些是编写者而不添加同步。可以安全地从不同线程使用bad_cow指向同一存储的不同实例(当然,在修复#1之后)。