在C ++ 11中安全吗?

我听说const C ++ 11中的线程安全。 真的吗?

这是否意味着const现在相当于Java的synchronized

他们用完了关键字吗?


我听说const C ++ 11中的线程安全。 真的吗?

这是有点真...

这就是标准语言在线程安全方面所要说的:

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

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

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

  • 在给定的东西上同时执行两个或更多的动作; 和
  • 其中至少有一个是写作。
  • 标准库建立在此基础上,进一步:

    [17.6.5.9/1]本节规定了实施应符合的防止数据竞争的要求(1.10)。 除非另有说明,否则每个标准库函数都应符合各项要求 在下面指定的情况以外的情况下,实现可能会阻止数据竞争。

    [17.6.5.9/3] A C ++标准库函数应不直接或间接地修改对象(1.10)比当前线程,除非对象通过该函数的非- const参数,包括直接或间接访问的其它线程访问this

    简单地说它期望对const对象的操作是线程安全的。 这意味着只要对自己类型的const对象进行操作,标准库也不会引入数据竞争

  • 完全由读取组成 - 也就是说,没有写入 - ; 要么
  • 内部同步写入。
  • 如果这种期望不适用于您的某个类型,则直接或间接与标准库的任何组件一起使用可能会导致数据竞争。 总之,从标准库的角度来看, const意思是线程安全的。 需要注意的是,这仅仅是一个契约,它不会被编译器强制执行,如果你破坏它,你会得到未定义的行为,并且你是独立的。 const是否存在不会影响代码生成 - 至少不会影响数据竞争 - 。

    这是否意味着const现在相当于Java的synchronized

    没有 。 一点也不...

    考虑下面代表矩形的过于简化的类:

    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;
        }
    };
    

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

    请注意,这并不意味着rect是线程安全的。 事实上,它很容易看到,如果一个呼叫area是在同一时间,一个呼叫发生set_size在给定的rect ,然后area最终可能会计算基于一个古老的宽度和一个新的高度的结果(甚至在乱码上)。

    但是,这是rectrect不是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;
        }
    };
    

    [如果这个例子看起来太过人造,你可以用一个非常大的动态分配的整数来替换int ,这个整数本质上不是线程安全的,并且乘法代价极高。]

    成员函数area不再是线程安全的,它现在正在执行写操作,并且不在内部进行同步。 这是个问题吗? 对area的调用可能会作为另一个对象的复制构造函数的一部分发生,这样的构造函数可能已经被标准容器上的某个操作调用,并且此时标准库期望这个操作表现为对数据的读取比赛。 但我们正在写作!

    只要我们将标准容器rect或间接地放置在标准容器中,我们即将与标准库签订合同。 为了继续在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;
        }
    };
    

    请注意,我们使area函数是线程安全的,但rect仍然不是线程安全的。 在调用set_size area调用可能仍然计算出错误的值,因为widthheight的分配不受互斥锁的保护。

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

    他们用完了关键字吗?

    对,他们是。 自从第一天起,它们就已经用完了关键字。


    来源:你不知道constmutable - Herb Sutter

    链接地址: http://www.djcxy.com/p/40477.html

    上一篇: safe in C++11?

    下一篇: what C++ idioms are deprecated in C++11