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

时间:2014-02-14 04:48:16

标签: c++ multithreading c++11 segmentation-fault pthreads

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

更新:还在构造函数的第一行添加了一个print语句(所以在调用pthread_create之前),甚至在segfault之前都没有打印但是构造函数确实使用了初始化列表,因此它是可能的有什么东西导致它?

这是构造函数:

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:

好吧我明白了。我的print函数是原子的,同样用其他地方定义的extern命名空间作用域mutex来保护cout。我改为普通cout,并在ctor的开头打印。显然,在尝试访问它们之前,这些静态存储持续时间互斥体都没有被初始化。所以是的,这可能是凯西的答案。

我只是不打扰复杂的对象和静态存储持续时间。无论如何,这没什么大不了的。

3 个答案:

答案 0 :(得分:6)

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

  

如果程序启动一个线程(30.3),则对于在不同转换单元中定义的变量的初始化,后续的变量初始化是未排序的。否则,对于在不同转换单元中定义的变量的初始化,变量的初始化是不确定地排序的。如果程序启动一个线程,则对于每个其他动态初始化,后续无序的变量初始化都是无序的。

我解释“后续的无序初始化变量对于其他所有动态初始化都没有排序”意味着生成的线程无法访问任何变量,动态初始化在线程生成之前未初始化而不会导致数据竞争。如果那个线程没有以某种方式与main同步,你基本上是用手捂着眼睛在雷区跳舞。

我强烈建议你仔细阅读并理解3.6;即使没有线程,在main开始之前,它也是一个巨大的PITA。

答案 1 :(得分:0)

在进入main之前会发生什么事情是特定于平台的,但这里有一个关于main()在Linux上执行的链接

http://linuxgazette.net/84/hawk.html

有用的snipet是

  

__ libc_start_main初始化必要的东西,特别是C库(如malloc)和线程环境,并调用我们的主。

有关更多信息,请查看__libc_start_main

不确定这在Windows上是如何表现的,但在进入main之前似乎任何标准C库调用都不是一个好主意

答案 2 :(得分:0)

可能有很多方法可以做到这一点。请参阅下面的代码段,其中A类的构造函数在main之前调用,因为我们已在全局范围内声明了类A的对象:(我已经扩展了示例以演示如何在main执行之前创建线程)

#include <iostream>
#include <stdlib.h>
#include <pthread.h>
using namespace std;

void *fun(void *x)
{
    while (true) {
        cout << "Thread\n";
        sleep(2);
    }
}

pthread_t t_id;
class A
{
    public: 
        A() 
        { 
            cout << "Hello before main \n " ;
            pthread_create(&t_id, 0, fun, 0);
            sleep(6);
        }
};

A a;
int main()
{
    cout << "I am main\n";
    sleep(40);
    return 0;
}