Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Are C++11 objects potentially slower in multi-threaded environments because of the new const?

According to Herb Sutter (http://isocpp.org/blog/2012/12/you-dont-know-const-and-mutable-herb-sutter), in C++11 const methods must not alter the object bit-wise, or must perform internal synchronization (e.g. using a mutex) if they have mutable data members.

Suppose I have a global object that I'm accessing from multiple threads and suppose it has mutable members. For the sake of argument, let's assume that we cannot modify the source of the class (it's provided by a third-party).

In C++98 these threads would use a global mutex to synchronize access to this object. So, an access would require a single mutex lock/unlock.

However, in C++11, any const member function call on this object will invoke internal synchronization as well, so potentially, a single const function call on this object will cost 2 lock/unlock operations (or more, depending on how many functions you call from a single thread). Note that the global mutex is still needed, because const doesn't seem to do anything for writers (except possibly slowing them down as well, if one of the non-const methods calls a const method).

So, my question is: If all of our classes have to be this way in C++ (at least to be usable by STL), doesn't this lead to excessive synchronization measures?

Thanks

Edit: Some clarifications:

  1. It seems that in C++11, you cannot use a class with the standard library unless its const member functions are internally synchronized (or do not perform any writes).

  2. While C++11 doesn't automatically add any synchronization code itself, a standard-library-compliant class doesn't need synchronization in C++98, but needs it in C++11. So, in C++98 you can get away with not doing any internal synchronization for mutable members, but in C++11 you can't.

like image 392
Alex Avatar asked Dec 05 '22 10:12

Alex


2 Answers

in C++11, any const member function call on this object will invoke internal synchronization as well

Why? That synchronisation doesn't just magically appear in the class, it's only there if someone adds it explicitly.

so potentially, a single const function call on this object will cost 2 lock/unlock operations

Only if someone has added an internal mutex to it and you also use an external one ... but why on earth would you do that?

Note that the global mutex is still needed, because const doesn't seem to do anything for writers (except possibly slowing them down as well, if one of the non-const methods calls a const method).

If the class has an internal mutex that's used to make the const members thread-safe then it could also be used for non-const members. If the class doesn't have an internal mutex, then the situation is identical to the C++98 one.

I think you're seeing a problem that doesn't exist.

Herb's "new meaning for const" is not enforced by the language or compiler, it's just design guidance, i.e. an idiom for good code. To follow that guidance you don't add mutexes to every class so const members are allowed to modify mutable members, you avoid mutable members! In the rare cases where you absolutely must have mutable members, either require users to do their own locking (and clearly document the class as requiring external synchronisation) or add internal synchronisation and pay the extra cost ... but those situations should be rare, so it's not true that "C++11 objects are slower because of the new const" because most well-designed objects don't have mutable members anyway.

like image 124
Jonathan Wakely Avatar answered Dec 09 '22 14:12

Jonathan Wakely


Yes, you are absolutely correct. You should make your objects follow these guidelines, and therefore access to them will potentially be slower in C++11. If and only if:

  1. The class has mutable members which const member functions modify.

  2. The object is being accessed from multiple threads.

If you ensure that at least one of these is untrue, then nothing changes. The number of objects that are being accessed from multiple threads should always be as minimal as possible. And the number of classes that have mutable members should be minimal. So you're talking about a minimal set of a minimal set of objects.

And even then... all that is required is that data races will not be broken. Depending on what the mutable data even is, this could simply be an atomic access.

I fail to see the problem here. Few of the standard library objects will have mutable members. I defy you to find a reasonable implementation of basic_string, vector, map, etc that need mutable members.

It seems that in C++11, you cannot use a class with the standard library unless its const member functions are internally synchronized (or do not perform any writes).

This is incorrect. You most certainly can. What you cannot do is attempt to access that class across multiple threads in a way that would "perform any writes" on those mutable members. If you never access that object through that C++11 class across threads in that particular way, you're fine.

So yes, you can use them. But you only get the guarantees that your own class provides. If you use your class through a standard library class in an unreasonable way (like your const member functions not being const or properly synchronized), then that's your fault, not the library's.

So, in C++98 you can get away with not doing any internal synchronization for mutable members, but in C++11 you can't.

That's like saying you can get away with computer crime back in the Roman Empire. Of course you can. They didn't have computers back then; so they didn't know what computer crime was.

C++98/03 did not have the concept of "threading". Thus, the standard has no concept of "internal synchronization", so what you could or could not "get away with" was neither defined nor undefined. It made no more sense to ask that question of the standard than to ask what the hacking laws were during Ceaser's day.

Now that C++11 actually defines this concept and the idea of a race condition, C++11 is able to say when you can "get away with not doing any internal synchronization".

Or, to put it another way, here is how the two standards answer your question: What is the result of a potential data race on a mutable member when accessed via a member function declared const in the standard library?

C++11: There will be no data races on any internal members when accessed by a const function. All standard library implementations of such functions must be implemented in such a way that a data race cannot occur.

C++98/03: What's a data race?

like image 35
Nicol Bolas Avatar answered Dec 09 '22 15:12

Nicol Bolas