我听说const
手段线程安全的C ++ 11。真的吗?
这是否const
等于Java的现在synchronized
?
他们的关键字用完了吗?
const
意味着线程安全。那将是无稽之谈,因为否则这意味着您应该能够继续并将每个线程安全的方法标记为const
。相反,我们真正要问的问题是const
IMPLIES线程安全的,这就是此讨论的目的。
我听说const
手段线程安全的C ++ 11。真的吗?
这是否const
等于Java的现在synchronized
?
他们的关键字用完了吗?
const
意味着线程安全。那将是无稽之谈,因为否则这意味着您应该能够继续并将每个线程安全的方法标记为const
。相反,我们真正要问的问题是const
IMPLIES线程安全的,这就是此讨论的目的。
Answers:
我听说
const
手段线程安全的C ++ 11。真的吗?
这是有点真...
这是标准语言在线程安全性方面的说法:
[1.10 / 4]如果 两个表达式求值之一修改一个内存位置(1.7),而另一个表达式访问或修改相同的内存位置,则它们会冲突。
[1.10 / 21] 如果程序的执行在不同线程中包含两个冲突的动作,则其中至少一个不是原子的,并且两个动作都没有发生在另一个线程上,则该程序的执行将引起数据争用。任何此类数据争用都会导致未定义的行为。
这仅是发生数据争用的充分条件:
该标准库建立在那,越走越了一下:
[17.6.5.9/1] 本节指定实现必须满足的要求,以防止数据争用(1.10)。除非另有说明,否则每个标准库函数都应满足每个要求。在以下指定情况以外的情况下,实现可能会阻止数据争用。
[17.6.5.9/3] 除非通过函数的非 const参数直接或间接访问对象,否则C ++标准库函数不得直接或间接修改可被当前线程以外的线程访问的对象(1.10)
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
最终可能会根据旧的宽度和新的高度(甚至是乱码)来计算其结果。
但是,这是正常的,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
仍可能最终计算错误的值,因为分配给width
和height
不被互斥保护。
如果我们确实想要线程安全 rect
,则可以使用同步原语来保护非线程安全的 rect
。
他们的关键字用完了吗?
对,他们是。从第一天开始,他们的关键字就用光了。
来源:你不知道const
和mutable
- 香草萨特
std::string
措辞已经禁止了COW。我不记得具体细节,但是……