K-b*_*llo 115 c++ const c++-faq thread-safety c++11
我听说const
意味着线程安全的C++ 11.真的吗?
这是否意味着const
现在是等效的Java的synchronized
?
他们的关键字用完了吗?
K-b*_*llo 130
我听说
const
意味着线程安全的C++ 11.真的吗?
这有点真实......
这就是标准语言对线程安全的看法:
[1.10/4]如果其中一个修改了内存位置(1.7)而另一个访问或修改了相同的内存位置,则 两个表达式评估会发生冲突.
[1.10/21] 程序的执行包含数据竞争,如果它在不同的线程中包含两个冲突的动作,其中至少有一个不是原子的,并且都不会在另一个之前发生.任何此类数据争用都会导致未定义的行为.
这不过是数据竞争发生的充分条件:
该标准库建立在那,越走越了一下:
[17.6.5.9/1] 本节规定了实现为防止数据竞争而应满足的要求(1.10).除非另有说明,否则每个标准库函数均应满足各项要求 在下面指定的情况下,实现可能会阻止数据争用.
[17.6.5.9/3] 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;
}
};
Run Code Online (Sandbox Code Playgroud)
该成员函数 area
是线程安全的 ; 不是因为它const
,而是因为它完全由读操作组成.没有涉及写入,并且至少需要一次写入才能进行数据竞争.这意味着您可以根据需要area
从多个线程调用,并且您将始终获得正确的结果.
请注意,这并不意味着它rect
是线程安全的.实际上,很容易看出如果一个调用area
是set_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
.
他们的关键字用完了吗?
对,他们是.从第一天开始,它们就已经用完了关键字.
来源:你不知道const
和mutable
- 赫伯萨特
归档时间: |
|
查看次数: |
14182 次 |
最近记录: |