mercredi 17 mai 2017

ctor initializer list with std::thread and this

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