记一次多线程排障

4x6d6z
deleted

问题:一个简单的SPSC模型,消费者线程执行一次循环就退出。

问题代码大致是这样:

class DumperThread
{
protected:
    DumpQueue queue_;
    std::thread thread_;
    alignas(64) std::atomic_bool running {false};

public:
    DumperThread(): queue_(), thread_(&DumperThread::run, this) {}

    void run()
    {
        running.store(true);
        while (running.load())
        {
            auto fut = std::move(queue_.pop());
            fut.get();
        }
    }
}

分析

调试初期发现 runningqueue_.pop() 时变为 false,百思不得其解。一开始考虑可能是 queue 里面哪里写越界了,或者 DumperThread 对象本身析构了。

多次调试偶然注意到 running 变为 false 的时机不确定,开始考虑可能是数据竞争。再仔细分析代码,发现 thread_(&DumperThread::run, this) 在初始化时构造,构造后线程已经可能开始运行。由于C++的初始化顺序是按照声明顺序,因此 runningthread_ 之后初始化,可能在 run 内部的赋值之后初始化,从而造成线程状态错误,循环退出。

修改

修改也很简单,将两者声明顺序调换,确保初始化 running 之后线程才可能开始运行:

protected:
    DumpQueue queue_;
    alignas(64) std::atomic_bool running {false};
    std::thread thread_;