Some years ago I found myself wondering if a not supposed thread safe class was being used by multiple threads without being synchronized. In that occasion I wrote about it here Threading mess! and here Threading mess (2)!.
At that time (9 years ago) we had no threads neither atomic in the c++ standard and the solution proposed was based on pthreads and on gcc atomic builtins.
I think it's time to refresh the implementation using some C++11 features.
The idea is very simple, upon entering a critical section (part of code that should not be executed concurrently) we should save the current thread id resetting the stored value as soon the thread leaves the critical section. If a thread tries to enter a critical section but we already have a thread id saved then we have detected the collision.
The technique is very effective and at that time I wrote for the Chromium project the class ThreadCollisionWarner thread_collision_warner.h and thread_collision_warner.cc using the described technique.
Basically what you need to do is to add to your classes a "FakeMutex" and then "locking" it where it's needed as you would do with a real mutex. It's called Fake Mutex because it will not suspend a thread if another one is active but it will assert(false) instead.
If you want to use this technique in your project I suggest to use the implementation done in Chromium.
Examples of uses:
the macros DFAKE_MUTEX, DFAKE_SCOPED_LOCK, DFAKE_SCOPED_RECURSIVE_LOCK and DFAKE_SCOPED_LOCK_THREAD_LOCKED are defined only if compiled in DEBUG mode removing from your production code the atomic overhead.
The modern simplified version of Chromium ThreadCollisionWarner proposed in Threading mess (2)! is reported here.
Monday, May 8, 2017
Friday, March 31, 2017
Structured Binding (C++17 inside)
Let's see how structured binding introduced in C++17 will change the way to interact with std::pair, std::tuple, std::array and such:
Unfortunately if you need to do something more fancy with your class it has to support the get<>() functions, and you need to reopen the std namespace to specialize std::tuple_size and std::tuple_element.
Given the following user defined type (note a and b here are private members):
std::pair<int,float> foo();
auto [a,b] = foo();
it will replaces:
std::pair<int,float> foo();
int a;
float b;
std::tie(a,b) = foo();
in case you are running an obfuscated code contest this new swap can scrub up well (please don't try this at home):
std::tie(a,b) = std::make_pair(b,a);
The decomposition works with c array and std::array as well:
int a[4] = { 1, 2, 3, 4};
auto [b,c,d,e] = a;
std::array<int, 4> a;
auto [b,c,d,e] = a;
and this is what you can do using a ranged for loop:
std::map myMap;
...
for (const auto & [k,v] : myMap) {
}
I bet someone in c++ committee has become recently a python enthusiast.
Now if you wonder what your structures like this:
stuct X {
int theInt = 3;
float thePi = 3.14;
};
auto [a,b] = x;
shall provide to make the decomposition working the response is: a plain nothing. That will work indeed off the shelf.Unfortunately if you need to do something more fancy with your class it has to support the get<>() functions, and you need to reopen the std namespace to specialize std::tuple_size and std::tuple_element.
Given the following user defined type (note a and b here are private members):
class Y {
public:
int foo() const {
return a;
}
float bar() const {
return b;
}
private:
int a = 3;
float b = 3.14;
};
you need to provide the gets<>() functions:
template <int N> auto get(Y const &);
template <> auto get<0>(Y const & aY) {
return aY.foo();
}
template <> auto get<1>(Y const & aY) {
return aY.bar();
}
and then you need to reopen the std namespace (one of those few allowed cases):
namespace std {
template<>
struct std::tuple_size<Y> {
static const size_t value = 2;
};
template<size_t I>
struct std::tuple_element<I, Y> {
using type = decltype(get<I>(declval<Y>()));
};
}
Note the partial specialization for std::tuple_element, you don't need to hard code the type of each index, it's enough to "deduce" it using the get function. You did a lot of work in order to have your class supporting the decomposition, in this case c++17 can save you some work taking advantage of a new c++17 feature, the "constexpr if", just writing a single version of get<>():
template<int N>
auto get(Y const & aY) {
static_assert(N==0 || N==1);
if constexpr (N == 0) {
return aY.foo();
} else if constexpr (N == 1) {
return aY.bar();
}
}
If you want use/experiment with those new language features go for clang++ (I tried only version 5.0 but it should work with the 4.0 as well) and you need to specify -std=gnu++1z
Saturday, March 18, 2017
Concepts!
Let's start from something very easy, you have the simple function:
soon you realize you need another one but for float and before you even need the third one you write it like this:
looks like you are done, everything goes well until, while compiling your huge project, the compiler gives the following error:
right, you think, the type Point needs to have defined the operator+, after defining it and after some precious minutes you get now another error:
and finally this is the last error and fixing it fixes your whole compilation.
Sometime that Point type is defined in an header that makes your entire project to recompile every time you add a missing feature.
Let see how can concepts can save our time and some headache.
Basically that sumScale function has a strong requirement on the type T. It should be a summable type (it has to support the operator+) and it should be scalable (it has to support operator* with an int), we can express these two concepts in the following way:
and then use this defined concept rewriting the sumScale function:
doing so the error would have been a more useful one:
wow, within a single iteration the compiler was able to gives us all the information we needed in order to fix the issue. In case you missed it I'll report for convenience the old and the new version of sumScale function.
and this is, in my humble opinion, one of the main advantages of concepts: simplify the generic programming taking rid of the cumbersome template syntax.
Let's go back now to our concept:
this concept is the refining of a Scalable concept indeed we can define the SummableScalable concept writing first a Summable concept then refining it in the following way:
even better we can combine two concepts Summale + Scalable obtaining a third one:
I believe when we will get the concepts available on our preferred compiler, for instance the concepts didn't make C++17 and today (at my knowledge) the concepts are implemented only in GCC 6 (using the flag -fconcepts), it will change the face of c++ code even more the c++11 specification did.
soon you realize you need another one but for float and before you even need the third one you write it like this:
looks like you are done, everything goes well until, while compiling your huge project, the compiler gives the following error:
right, you think, the type Point needs to have defined the operator+, after defining it and after some precious minutes you get now another error:
and finally this is the last error and fixing it fixes your whole compilation.
Sometime that Point type is defined in an header that makes your entire project to recompile every time you add a missing feature.
Let see how can concepts can save our time and some headache.
Basically that sumScale function has a strong requirement on the type T. It should be a summable type (it has to support the operator+) and it should be scalable (it has to support operator* with an int), we can express these two concepts in the following way:
and then use this defined concept rewriting the sumScale function:
doing so the error would have been a more useful one:
wow, within a single iteration the compiler was able to gives us all the information we needed in order to fix the issue. In case you missed it I'll report for convenience the old and the new version of sumScale function.
and this is, in my humble opinion, one of the main advantages of concepts: simplify the generic programming taking rid of the cumbersome template syntax.
Let's go back now to our concept:
this concept is the refining of a Scalable concept indeed we can define the SummableScalable concept writing first a Summable concept then refining it in the following way:
even better we can combine two concepts Summale + Scalable obtaining a third one:
I believe when we will get the concepts available on our preferred compiler, for instance the concepts didn't make C++17 and today (at my knowledge) the concepts are implemented only in GCC 6 (using the flag -fconcepts), it will change the face of c++ code even more the c++11 specification did.
2 comments:
Labels:
c++,
c++11,
c++17,
concepts
Location:
Lugano, Switzerland
Subscribe to:
Posts (Atom)