简体   繁体   English

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

[英]Does const mean thread-safe in C++11?

I hear that const means thread-safe in C++11 .我听说constC++11 中意味着线程安全 Is that true?真的吗?

Does that mean const is now the equivalent of Java 's synchronized ?这是否意味着const现在等同于Javasynchronized

Are they running out of keywords ?他们的关键字用完了吗?

I hear that const means thread-safe in C++11 .我听说constC++11 中意味着线程安全 Is that true?真的吗?

It is somewhat true...有点真实...

This is what the Standard Language has to say on thread-safety:这就是标准语言关于线程安全的说法:

[1.10/4] Two expression evaluations conflict if one of them modifies a memory location (1.7) and the other one accesses or modifies the same memory location. [1.10/4]如果其中一个修改了内存位置 (1.7) 而另一个访问或修改了相同的内存位置,则两个表达式评估会发生冲突

[1.10/21] The execution of a program contains a data race if it contains two conflicting actions in different threads, at least one of which is not atomic, and neither happens before the other. [1.10/21]如果一个程序在不同的线程中包含两个相互冲突的动作,那么它的执行就会包含数据竞争,其中至少一个不是原子的,并且两者都不在另一个之前发生。 Any such data race results in undefined behavior.任何此类数据竞争都会导致未定义的行为。

which is nothing else than the sufficient condition for a data race to occur:这只不过是发生数据竞争的充分条件:

  1. There are two or more actions being performed at the same time on a given thing;对给定事物同时执行两个或多个动作; and
  2. At least one of them is a write.其中至少一个是写入。

The Standard Library builds on that, going a bit further:标准库以此为基础,更进一步:

[17.6.5.9/1] This section specifies requirements that implementations shall meet to prevent data races (1.10). [17.6.5.9/1]本节规定了实现应满足的要求,以防止数据竞争(1.10)。 Every standard library function shall meet each requirement unless otherwise specified.除非另有说明,否则每个标准库函数都应满足每个要求。 Implementations may prevent data races in cases other than those specified below.在下面指定的情况以外的情况下,实现可以防止数据竞争。

[17.6.5.9/3] A C++ standard library function shall not directly or indirectly modify objects (1.10) accessible by threads other than the current thread unless the objects are accessed directly or indirectly via the function's non- const arguments, including this . [17.6.5.9/3] C++ 标准库函数不应直接或间接修改当前线程以外的线程可访问的对象 (1.10),除非这些对象是通过函数的非const参数直接或间接访问的,包括this

which in simple words says that it expects operations on const objects to be thread-safe .简而言之,它希望对const对象的操作是线程安全的 This means that the Standard Library won't introduce a data race as long as operations on const objects of your own types either这意味着只要对您自己类型的const对象进行操作,标准库就不会引入数据竞争

  1. Consist entirely of reads --that is, there are no writes--;完全由读取组成——也就是说,没有写入——; or或者
  2. Internally synchronizes writes.在内部同步写入。

If this expectation does not hold for one of your types, then using it directly or indirectly together with any component of the Standard Library may result in a data race .如果这种期望不适用于您的一种类型,那么直接或间接将它与标准库的任何组件一起使用可能会导致数据竞争 In conclusion, const does mean thread-safe from the Standard Library point of view.总之,从标准库的角度来看, const确实意味着线程安全 It is important to note that this is merely a contract and it won't be enforced by the compiler, if you break it you get undefined behavior and you are on your own.重要的是要注意,这只是一个合同,编译器不会强制执行,如果你违反它,你会得到未定义的行为,你是自己的。 Whether const is present or not will not affect code generation --at least not in respect to data races --. const是否存在不会影响代码生成——至少不会影响数据竞争——。

Does that mean const is now the equivalent of Java 's synchronized ?这是否意味着const现在等同于Javasynchronized

No .没有 Not at all...一点也不...

Consider the following overly simplified class representing a rectangle:考虑以下表示矩形的过于简化的类:

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

The member-function area is thread-safe ;成员函数area线程安全的 not because its const , but because it consist entirely of read operations.不是因为它的const ,而是因为它完全由读取操作组成。 There are no writes involved, and at least one write involved is necessary for a data race to occur.不涉及写入,并且至少需要涉及一次写入才能发生数据竞争 That means that you can call area from as many threads as you want and you will get correct results all the time.这意味着您可以根据需要从任意数量的线程中调用area ,并且您将始终获得正确的结果。

Note that this doesn't mean that rect is thread-safe .请注意,这并不意味着rect线程安全的 In fact, its easy to see how if a call to area were to happen at the same time that a call to set_size on a given rect , then area could end up computing its result based on an old width and a new height (or even on garbled values).事实上,它很容易看到,如果一个呼叫area是在同一时间,一个呼叫发生set_size在给定的rect ,然后area最终可能会计算基于一个古老的宽度和一个新的高度的结果(甚至关于乱码值)。

But that is alright, rect isn't const so its not even expected to be thread-safe after all.但这没关系, rect不是const所以它毕竟不是线程安全的 An object declared const rect , on the other hand, would be thread-safe since no writes are possible (and if you are considering const_cast -ing something originally declared const then you get undefined-behavior and that's it).另一方面,声明为const rect的对象将是线程安全的,因为不可能写入(如果您正在考虑const_cast -ing 最初声明为const东西,那么您会得到未定义的行为,仅此而已)。

So what does it mean then?那么这意味着什么呢?

Let's assume --for the sake of argument-- that multiplication operations are extremely costly and we better avoid them when possible.让我们假设——为了论证——乘法运算的成本非常高,我们最好尽可能避免它们。 We could compute the area only if it is requested, and then cache it in case it is requested again in the future:我们可以仅在请求时计算该区域,然后将其缓存以备将来再次请求时:

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

[If this example seems too artificial, you could mentally replace int by a very large dynamically allocated integer which is inherently non thread-safe and for which multiplications are extremely costly.] [如果这个例子看起来太人为,你可以用一个非常大的动态分配的整数替换int ,这个整数本质上是非线程安全的,并且乘法非常昂贵。]

The member-function area is no longer thread-safe , it is doing writes now and is not internally synchronized.成员函数area不再是线程安全的,它现在正在执行写入操作并且内部不同步。 Is it a problem?这是个问题吗? The call to area may happen as part of a copy-constructor of another object, such constructor could have been called by some operation on a standard container , and at that point the standard library expects this operation to behave as a read in regard to data races .area的调用可能作为另一个对象的复制构造函数的一部分发生,这样的构造函数可能已被标准容器上的某些操作调用,并且此时标准库希望此操作表现为对数据读取比赛 But we are doing writes!但我们正在写!

As soon as we put a rect in a standard container --directly or indirectly-- we are entering a contract with the Standard Library .一旦我们将rect放入标准容器中—— rect或间接——我们就与标准库签订合同 To keep doing writes in a const function while still honoring that contract, we need to internally synchronize those writes:为了继续在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;
    }
};

Note that we made the area function thread-safe , but the rect still isn't thread-safe .请注意,我们创建了area函数thread-safe ,但rect仍然不是thread-safe A call to area happening at the same time that a call to set_size may still end up computing the wrong value, since the assignments to width and height are not protected by the mutex.area的调用与对set_size的调用set_sizeset_size可能仍会计算出错误的值,因为对widthheight的分配不受互斥锁保护。

If we really wanted a thread-safe rect , we would use a synchronization primitive to protect the non-thread-safe rect .如果我们真的想要一个线程安全的rect ,我们会使用同步原语来保护非线程安全的rect

Are they running out of keywords ?他们的关键字用完了吗?

Yes, they are.对,他们是。 They have been running out of keywords since day one.从第一天起,他们的关键字就用完了。


Source : You don't know const and mutable - Herb Sutter来源你不知道constmutable - Herb Sutter

This is an addition to K-ballo's answer.这是 K-ballo 答案的补充。

The term thread-safe is abused in this context.在这种情况下,术语线程安全被滥用。 The correct wording is: a const function implies thread-safe bitwise const or internally synchronised , as stated by Herb Sutter (29:43) himself正确的措辞是:如Herb Sutter (29:43) 自己所述, const 函数意味着 线程安全的 按位 const内部同步

It should be thread-safe to call a const function from multiple threads simultaneously, without calling a non-const function at the same time in another thread.同时从多个线程调用 const 函数应该是线程安全的,不是在另一个线程中同时调用非常量函数。

So, a const function should not (and will not most of the time) be really thread-safe, as it may read memory (without internal synchronisation) that could be changed by another non-const function.因此,const 函数不应该(并且在大多数情况下不会)真正是线程安全的,因为它可能会读取可由另一个非常量函数更改的内存(没有内部同步)。 In general, this is not thread-safe as a data race occurs even if only one thread is writing (and another reading the data).通常,这不是线程安全的,因为即使只有一个线程正在写入(另一个线程正在读取数据)也会发生数据竞争。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM