是否允许主线程在进入 main() 之前生成 POSIX 线程?

我有一个包含线程的对象.我希望对象的命运和线程的命运是一回事.因此构造函数创建一个线程(使用pthread_create),析构函数执行操作以使线程在合理的时间内返回,然后加入线程.只要我不使用静态存储持续时间实例化这些对象之一,它就可以正常工作.如果我在全局或命名空间或静态类范围内实例化这些对象之一,则程序编译良好(gcc 4.8.1)但在运行时立即出现段错误.使用打印语句,我确定主线程在段错误之前甚至没有进入 main() .有什么想法吗?

I have this object that contains a thread. I want the fate of the object and the fate of the thread to be one in the same. So the constructor creates a thread (with pthread_create) and the destructor performs actions to cause the thread to return in a reasonable amount of time and then joins the thread. This is working fine as long as I don't instantiate one of these objects with static storage duration. If I instantiate one of these objects at global or namespace or static class scope the program compiles fine (gcc 4.8.1) but immediately segfaults upon running. With print statements I have determined that the main thread doesn't even enter main() before the segfault. Any ideas?

更新:还在构造函数的第一行添加了一个打印语句(所以在调用 pthread_create 之前),甚至在段错误之前没有打印,但是构造函数确实使用了初始化列表,所以可能是什么原因造成的?

Update: Also added a print statement to the first line of the constructor (so before pthread_create is called), and not even that gets printed before the segfault BUT the constructor does use an initialization list so it is possible something there is causing it?

这里是构造函数:

worker::worker(size_t buffer_size):
m_head(nullptr),m_tail(nullptr),
m_buffer_A(operator new(buffer_size)),
m_buffer_B(operator new(buffer_size)),
m_next(m_buffer_A),
m_buffer_size(buffer_size),
m_pause_gate(true),
m_worker_thread([this]()->void{ thread_func(); }),
m_running(true)
{
    print("this wont get printed b4 segfault");
    scoped_lock lock(worker_lock);
    m_worker_thread.start();
    all_workers.push_back(this);
}

和析构函数:

worker::~worker()
{
    {
        scoped_lock lock(worker_lock);
        auto w=all_workers.begin();
        while(w!=all_workers.end())
        {
            if(*w==this)
            {
                break;
            }
            ++w;
        }
        all_workers.erase(w);
    }

    {
        scoped_lock lock(m_lock);
        m_running=false;
    }

    m_sem.release();
    m_pause_gate.open();

    m_worker_thread.join();

    operator delete(m_buffer_A);
    operator delete(m_buffer_B);
}

更新 2:

好的,我想通了.我的打印功能是原子的,同样使用在其他地方定义的外部命名空间范围互斥锁来保护 cout.我改为纯 cout 并打印在 ctor 的开头.显然,在尝试访问它们之前,这些静态存储持续时间互斥锁都没有被初始化.所以是的,这可能是凯西的答案.

Okay I figured it out. My print function is atomic and likewise protects cout with an extern namespace scope mutex defined elsewhere. I changed to just plain cout and it printed at the beginning of the ctor. Apparently none of these static storage duration mutexes are getting initialized before things are trying to access them. So yeah it is probably Casey's answer.

我只是不想为复杂的对象和静态存储持续时间而烦恼.反正也没什么大不了的.

I'm just not going to bother with complex objects and static storage duration. It's no big deal anyway.

推荐答案

C++11 §3.6.2 中描述了非局部变量的初始化,第 2 段中有很多可怕的东西与线程有关:

Initialization of non-local variables is described in C++11 §3.6.2, there's a ton of scary stuff in paragraph 2 that has to do with threads:

如果程序启动线程(30.3),则变量的后续初始化相对于在不同翻译单元中定义的变量的初始化是无序的.否则,变量的初始化相对于在不同翻译单元中定义的变量的初始化是不确定的.如果程序启动一个线程,则变量的后续无序初始化相对于其他所有动态初始化都是无序的.

If a program starts a thread (30.3), the subsequent initialization of a variable is unsequenced with respect to the initialization of a variable defined in a different translation unit. Otherwise, the initialization of a variable is indeterminately sequenced with respect to the initialization of a variable defined in a different translation unit. If a program starts a thread, the subsequent unordered initialization of a variable is unsequenced with respect to every other dynamic initialization.

我解释变量的后续无序初始化相对于所有其他动态初始化是无序的"意味着生成的线程无法访问具有动态初始化的任何变量,该动态初始化在线程生成之前未初始化而不会导致数据竞争.如果该线程无法以某种方式与 main 同步,那么您基本上是双手捂着眼睛在雷区中跳舞.

I interpret "the subsequent unordered initialization of a variable is unsequenced with respect to every other dynamic initialization" to mean that the spawned thread cannot access any variable with dynamic initialization that was not initialized before the thread was spawned without causing a data race. If that thread doesn't somehow synchronize with main, you're basically dancing through a minefield with your hands over your eyes.

我强烈建议您通读并理解 3.6 的全部内容;即使没有线程,在 main 启动之前要做很多事情也是一个巨大的 PITA.

I'd strongly suggest you read through and understand all of 3.6; even without threads it's a huge PITA to do much before main starts.

相关文章