const在C++ 11中是否意味着线程安全?

K-b*_*llo 115 c++ const c++-faq thread-safety c++11

我听说const意味着线程安全C++ 11.真的吗?

这是否意味着const现在是等效的Javasynchronized

他们的关键字用完了吗?

K-b*_*llo 130

我听说const意味着线程安全C++ 11.真的吗?

有点真实......

这就是标准语言对线程安全的看法:

[1.10/4]如果其中一个修改了内存位置(1.7)而另一个访问或修改了相同的内存位置,则 两个表达式评估会发生冲突.

[1.10/21] 程序的执行包含数据竞争,如果它在不同的线程中包含两个冲突的动作,其中至少有一个不是原子的,并且都不会在另一个之前发生.任何此类数据争用都会导致未定义的行为.

这不过是数据竞争发生的充分条件:

  1. 在给定的事物上同时执行两个或更多个动作; 和
  2. 其中至少有一个是写作.

标准库建立在那,越走越了一下:

[17.6.5.9/1] 本节规定了实现为防止数据竞争而应满足的要求(1.10).除非另有说明,否则每个标准库函数均应满足各项要求 在下面指定的情况下,实现可能会阻止数据争用.

[17.6.5.9/3] C++标准库函数不应直接或间接修改除当前线程以外的线程可访问的对象(1.10),除非通过函数的非 const参数直接或间接访问对象,包括this.

简单来说,它希望const对象上的操作是线程安全的.这意味着只要对您自己类型的对象进行操作,标准库就不会引入数据争用const

  1. 完全由读取组成 - 也就是说,没有写入 - ; 要么
  2. 内部同步写入.

如果此类期望不适用于您的某种类型,则直接或间接与标准库的任何组件一起使用可能会导致数据竞争.总之,从标准库的角度来看,const确实意味着线程安全.重要的是要注意,这仅仅是一个合同,并且它不会被编译器强制执行,如果你打破它就会得到未定义的行为并且你是独立的.是否存在不会影响代码生成 - 至少不会影响数据竞争 - .const

这是否意味着const现在是等效的Javasynchronized

.一点也不...

考虑以下过度简化的表示矩形的类:

class rect {
    int width = 0, height = 0;

public:
    /*...*/
    void set_size( int new_width, int new_height ) {
        width = new_width;
        height = new_height;
    }
    int area() const {
        return width * height;
    }
};
Run Code Online (Sandbox Code Playgroud)

成员函数 area线程安全的 ; 不是因为它const,而是因为它完全由读操作组成.没有涉及写入,并且至少需要一次写入才能进行数据竞争.这意味着您可以根据需要area从多个线程调用,并且您将始终获得正确的结果.

请注意,这并不意味着它rect线程安全的.实际上,很容易看出如果一个调用areaset_size在一个给定的调用的同时发生的rect,那么area最终可能会根据旧的宽度和新的高度(甚至是乱码值)计算其结果.

但这没关系,rect并不是const因为它毕竟不是线程安全的.const rect另一方面,声明的对象将是线程安全的,因为不可能写入(如果你正在考虑 - const_cast最初声明的东西,const那么你会得到未定义的行为,就是这样).

那么这意味着什么呢?

让我们假设 - 为了论证 - 乘法运算成本极高,我们在可能的情况下更好地避免它们.我们只能在请求时计算区域,然后在以后再次请求时缓存它:

class rect {
    int width = 0, height = 0;

    mutable int cached_area = 0;
    mutable bool cached_area_valid = true;

public:
    /*...*/
    void set_size( int new_width, int new_height ) {
        cached_area_valid = ( width == new_width && height == new_height );
        width = new_width;
        height = new_height;
    }
    int area() const {
        if( !cached_area_valid ) {
            cached_area = width;
            cached_area *= height;
            cached_area_valid = true;
        }
        return cached_area;
    }
};
Run Code Online (Sandbox Code Playgroud)

[如果这个例子看起来太人工了,你可以int通过一个非常大的动态分配整数替代,这个整数本质上是非线程安全的,并且乘法非常昂贵.

成员函数 area不再是线程安全的,现在正在做的写入,而不是内部同步.这是个问题吗?调用area可能作为另一个对象的复制构造函数的一部分发生,这样的构造函数可能已被标准容器上的某些操作调用,此时标准库期望此操作表现为关于数据争用的读取.但我们正在写作!

当我们把rect一个标准集装箱 --directly或indirectly--我们进入了一个合同标准库.为了const在仍然遵守该合同的同时继续在函数中进行写入,我们需要在内部同步这些写入:

class rect {
    int width = 0, height = 0;

    mutable std::mutex cache_mutex;
    mutable int cached_area = 0;
    mutable bool cached_area_valid = true;

public:
    /*...*/
    void set_size( int new_width, int new_height ) {
        if( new_width != width || new_height != height )
        {
            std::lock_guard< std::mutex > guard( cache_mutex );

            cached_area_valid = false;
        }
        width = new_width;
        height = new_height;
    }
    int area() const {
        std::lock_guard< std::mutex > guard( cache_mutex );

        if( !cached_area_valid ) {
            cached_area = width;
            cached_area *= height;
            cached_area_valid = true;
        }
        return cached_area;
    }
};
Run Code Online (Sandbox Code Playgroud)

请注意,我们使area函数是线程安全的,但rect仍然不是线程安全的.在调用可能仍然最终计算错误值area的同时发生的呼叫set_size,因为分配给互斥锁width并且height不受互斥锁的保护.

如果我们真的想要一个线程安全的 rect,我们将使用同步原语来保护非线程安全 rect.

他们的关键字用完了吗?

对,他们是.从第一天开始,它们就已经用完了关键字.


来源:你不知道constmutable - 赫伯萨特

  • @Ben Voigt:据我所知,`std :: string`的_C++ 11_规范的措辞已经禁止_COW_.我记不清具体细节了...... (6认同)
  • @BenVoigt:不.它只会阻止这些东西不同步 - 即不是线程安全的.C++ 11已经明确禁止COW-这个特殊段落与此无关,并且不会禁止COW. (3认同)
  • 在我看来,存在一个逻辑上的差距.[17.6.5.9/3]"不得直接或间接地修改",禁止"过多"; 它应该说"不得直接或间接引入数据竞争",*除非*原子写入某处定义*不*为"修改".但我无处可寻. (2认同)