I have read some stuff about 'this' being unsafe in the initializer list of the constructor. I have a rather large app and I traced some undefined behavior to using std::thread with 'this' in the initializer list of a constructor. When I moved the std::thread construction out of the initializer list and into the constructor the app works without errors.
I tried to reproduce the problem with an example but it runs perfectly. Can anybody explain why the combo of std::thread, 'this' and initializer list might give undefined behavior. I think it has to do with 'this' not being fully initialized but being copied (is it?) when you call the std::thread constructor but that is just guessing.
I would like to be able to reproduce the problem. Tried with g++4.9.2 and g++5.4 on ubuntu 16.04. I compiled with -g and -O0 to debug. Also tried with -O2 and -O3.
#include <chrono>
#include <condition_variable>
#include <memory>
#include <mutex>
#include <thread>
//g++ -std=c++11 -g -O0 -pthread init_thread.cpp -o init_thread
/* ------------------------------------------------------------------------- +
| TEMPLATE FUNCTIONS
+ ------------------------------------------------------------------------- */
#if __cplusplus < 201402L
template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
#endif
/* ------------------------------------------------------------------------- +
| CLASSES
+ ------------------------------------------------------------------------- */
class X
{
public:
X() = delete;
X(int x) : x_(x)
{
t_ = std::thread(&X::foo, this);
printf("Ctor X\n");
}
~X()
{
{
std::lock_guard<std::mutex> lck(mtxState_);
state_ = State::terminated;
}
cv_.notify_one();
if (t_.joinable())
{
t_.join();
}
printf("Dtor X\n");
}
private:
enum class State
{
suspended,
running,
terminated
};
int x_;
mutable std::mutex mtxState_;
State state_ { State::running };
mutable std::condition_variable cv_;
std::thread t_;
void foo()
{
while (state_ != State::terminated)
{
switch (state_)
{
case State::suspended:
{
std::unique_lock<std::mutex> lck(mtxState_);
cv_.wait(lck, [this] { return state_ != State::suspended; });
}
break;
case State::running:
{
printf("do something X...\n");
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
default:
break;
}
}
};
};
class I
{
public:
I() = delete;
I(int i) {};
};
class A : I
{
public:
A() = delete;
A(int a) : I(a), a_(a), x_obj_with_thread_(make_unique<X>(15)), t_(std::thread(&A::foo, this))
{
printf("Ctor A\n");
}
~A()
{
{
std::lock_guard<std::mutex> lck(mtxState_);
state_ = State::terminated;
}
cv_.notify_one();
if (t_.joinable())
{
t_.join();
}
printf("Dtor A\n");
}
private:
enum class State
{
suspended,
terminated
};
int a_;
mutable std::mutex mtxState_;
State state_ { State::suspended };
mutable std::condition_variable cv_;
std::thread t_;
std::unique_ptr<X> x_obj_with_thread_;
void foo()
{
while (state_ != State::terminated)
{
switch (state_)
{
case State::suspended:
{
std::unique_lock<std::mutex> lck(mtxState_);
cv_.wait(lck, [this] { return state_ != State::suspended; });
}
break;
default:
printf("do something A...\n");
break;
}
}
};
};
class B : I
{
public:
B() = delete;
B(int b) : I(b), b_(b), x_obj_with_thread_(make_unique<X>(15))
{
t_ = std::thread(&B::bar, this);
printf("Ctor B\n");
}
~B()
{
{
std::lock_guard<std::mutex> lck(mtxState_);
state_ = State::terminated;
}
cv_.notify_one();
if (t_.joinable())
{
t_.join();
}
printf("Dtor B\n");
}
private:
enum class State
{
suspended,
terminated
};
int b_;
mutable std::mutex mtxState_;
State state_ { State::suspended };
mutable std::condition_variable cv_;
std::thread t_;
std::unique_ptr<X> x_obj_with_thread_;
void bar()
{
while (state_ != State::terminated)
{
switch (state_)
{
case State::suspended:
{
std::unique_lock<std::mutex> lck(mtxState_);
cv_.wait(lck, [this] { return state_ != State::suspended; });
}
break;
default:
printf("do something B...\n");
break;
}
}
};
};
void testA()
{
for (int i=0; i < 100000; i++)
{
printf("A iteration %i\n", i);
A a(15);
}
}
void testB()
{
for (int i=0; i < 100000; i++)
{
printf("B iteration %i\n", i);
B b(15);
}
}
int main()
{
std::thread a(testA);
std::thread b(testB);
a.join();
b.join();
return 0;
}
Aucun commentaire:
Enregistrer un commentaire