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


115

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

这是否const等于Java的现在synchronized

他们的关键字用完了吗?


1
C ++常见问题解答通常由C ++社区管理,您可以在我们的聊天室中向我们询问意见。
2013年

@DeadMG:我不了解C ++常见问题及其礼节,有人在评论中建议。
K-ballo

2
您在哪里听到const表示线程安全?
Mark B

2
@Mark B:Herb SutterBjarne StroustrupStandard C ++ Foundation上是这样说的,请参阅答案底部的链接。
K-ballo

注意事项:真正的问题不在于是否const 意味着线程安全。那将是无稽之谈,因为否则这意味着您应该能够继续并将每个线程安全的方法标记为const。相反,我们真正要问的问题是const IMPLIES线程安全的,这就是此讨论的目的。
user541686 '19

Answers:


131

我听说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] 除非通过函数的非 const参数直接或间接访问对象,否则C ++标准库函数不得直接或间接修改可被当前线程以外的线程访问的对象(1.10)this

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

  1. 完全由读取(即没有写入)组成;要么
  2. 内部同步写入。

如果此期望不适用于您的一种类型,则将其与标准库的任何组件直接或间接使用可能会导致数据争用。总之,从标准库的角度来看,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在同一时间发生的rectarea最终可能会根据旧的宽度和新的高度(甚至是乱码)来计算其结果。

但是,这是正常的,rect不是const所以它不是甚至有望成为线程安全的毕竟。const rect另一方面,声明的对象将是线程安全的,因为无法进行写操作(如果您正在考虑const_cast-ing最初声明的内容,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一个标准集装箱 --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;
    }
};

请注意,我们使该area函数成为线程安全的,但rect仍然不是线程安全的。呼叫到area在同一时间,一个调用发生set_size仍可能最终计算错误的值,因为分配给widthheight不被互斥保护。

如果我们确实想要线程安全 rect,则可以使用同步原语来保护非线程安全的 rect

他们的关键字用完了吗?

对,他们是。从第一天开始,他们的关键字就用光了。


来源你不知道constmutable - 香草萨特


6
@Ben Voigt:据我了解,C ++ 11规范的std::string措辞已经禁止了COW。我不记得具体细节,但是……
K-ballo

3
@BenVoigt:否。这只会防止此类事情不同步-即不是线程安全的。C ++ 11已经明确禁止COW了-但是,此特定段落与此无关,并且不会禁止COW。
2013年

2
在我看来,存在逻辑上的差距。[17.6.5.9/3]禁止“太多”,说“不得直接或间接修改”;应该说“不得直接或间接地引入数据争”,除非原子写入某处定义为“修改”。但是我在任何地方都找不到。
安迪·普罗

1
我可能在这里更清楚地表达了我的意思:isocpp.org/blog/2012/12/…仍然感谢您尝试提供帮助。
安迪·普罗

1
有时我想知道是谁(或直接参与其中的人)实际负责写下诸如此类的标准段落。
pepper_chico
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.