const是否意味着C ++ 11中的线程安全?
我听说const
在C ++ 11中是线程安全的 。 真的吗?
这是否意味着const
现在相当于Java的synchronized
?
他们用完了关键字吗?
我听说
const
在C ++ 11中是线程安全的 。 真的吗?
这是真的…
这就是标准语言在线程安全方面所要说的:
[1.10 / 4]如果其中一个修改一个内存位置(1.7)而另一个访问或修改相同的内存位置,则两个expression式评估冲突 。
[1.10 / 21]一个程序的执行包含一个数据竞争,如果它在不同的线程中包含两个冲突的动作,其中至less有一个不是primefaces的,也不会发生在另一个之前。 任何这样的数据竞争都会导致未定义的行为。
这不是数据竞赛发生的充分条件:
- 在一件事物上同时进行两个或两个以上的行动; 和
- 至less有一个是写。
标准库build立在这一点上,进一步:
[17.6.5.9/1]本节规定了为防止数据竞争而需要满足的要求(1.10)。 除非另有规定,否则每个标准库函数都应符合每个要求 在下面指定的情况下,实现可能会阻止数据竞争。
[17.6.5.9/3] C ++标准库函数不能直接或间接修改当前线程以外的线程可访问的对象(1.10),除非直接或间接通过函数的非常量参数(包括
this
访问对象。
简单地说,它期望对const
对象的操作是线程安全的 。 这意味着只要对自己types的const
对象进行操作, 标准库也不会引入数据竞争
- 完全由读取 – 也就是说,没有写入 – 要么
- 内部同步写入。
如果这种期望不适用于您的某个types,则直接或间接地与标准库的任何组件一起使用可能会导致数据竞争 。 总之,从标准库的angular度来看, const
意思是线程安全的。 需要注意的是,这只是一个契约 ,不会被编译器强制执行,如果你破坏它,你会得到一个未定义的行为,而你是自己的。 const
是否存在不会影响代码的生成,至less不会影响数据竞争 。
这是否意味着
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
,而是因为它完全由读操作组成。 不涉及写操作,至less需要一次写操作才能使数据竞争发生。 这意味着您可以从任意多个线程中调用area
,并且您将始终获得正确的结果。
请注意,这并不意味着rect
是线程安全的 。 事实上,很容易看到,如果一个area
的调用是在一个给定set_size
调用的set_size
,那么该area
最终可能会根据旧的宽度和新的高度来计算其结果在乱码上)。
但是,这是rect
, rect
不是const
所以它甚至不希望是线程安全的 。 另一方面,声明const rect
的对象将是线程安全的,因为不可能写入(如果你正在考虑const_cast
最初声明为const
东西,那么你会得到未定义的行为 ,就是这样)。
那么这是什么意思?
让我们假设 – 为了争论 – 倍增操作是非常昂贵的,我们最好尽可能避免它们。 我们可以只在需要的时候计算这个区域,然后caching它,以防将来再次请求:
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; } };
[如果这个例子看起来太过人造,你可以用一个非常大的dynamic分配的整数来replaceint
,这个整数本质上是非线程安全的 ,而且乘法代价极高。]
成员函数 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
调用可能仍然计算错误的值,因为width
和height
的分配不受互斥锁的保护。
如果我们真的想要一个线程安全 rect
,我们将使用同步原语来保护非线程安全 rect
。
他们用完了关键字吗?
对,他们是。 自从第一天起,它们就已经用完了关键字 。
来源 : 你不知道const
和mutable
– 香草萨特