Is the main thread allowed to spawn a POSIX thread before it enters main()?
Asked Answered
Z

4

13

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?

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?

Here is the constructor:

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);
}

And destructor:

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);
}

Update 2:

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.

Zermatt answered 14/2, 2014 at 4:48 Comment(7)
This sounds bizarre and dangerous (so I'm not surprised that it's causing problems), however I've never heard of anything like it so I can't say it's absolutely wrong. I'm curious to hear what others have to say.Sandhog
global and static objects are created even before executing main() method. My suggestion would be to add more debug prints in constructor and destructor of your classBethel
Check that the thread is not accessing anything from the object which is destroyed when the object is destroyed. Otherwise make sure that the destructor function first terminates the thread.Dulcet
all_workers appears to be another global object with static storage duration. Are you sure it is initialized before your worker object?Moats
@Moats it sure is. And nope, is there even any way to be sure? What I do know is that it segfaults before the print statement though.Zermatt
Always use the unbuffered stderr to log debug messages, or at least suffix the message with a \n so the output stream will be flushed upon printing the message.Prettypretty
@Zermatt If they are defined in the same translation unit, they should be initialized in declaration order (barring any weirdness involving explicit template instantiation).Moats
M
8

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:

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.

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.

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.

Moats answered 14/2, 2014 at 5:48 Comment(2)
so, this is static order fiasco on steroidsWenz
@BЈовић It's like the Mechagodzilla version of the static initialization order fiasco.Moats
A
0

What happens before entering main will be platform specific, but here is a link on how main() executes on Linux

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

The useful snipet is

__libc_start_main initializes necessary stuffs, especially C library(such as malloc) and thread environment and calls our main.

For more information look up __libc_start_main

Not sure how this behaves on Windows, but it seems like any standard C library call before entering main is not a good idea

Adrea answered 14/2, 2014 at 5:27 Comment(1)
Windows also have similar mechanism. mainCRTStartup is the function which gets executed first, initializes CRT and calls programs' mainBethel
D
0

There may be many ways to do that. See the snippet below where the constructor of class A called before main because we have declared an object of class A at global scope: (I have expanded the example to demonstrate how a thread can be created before main executes)

#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;
}
Dulcet answered 14/2, 2014 at 5:36 Comment(2)
your constructor is not accessing global objectsWenz
The object 'a' is global and also t_id is global.Dulcet
H
0

I found this question after I posted my own question about threads. Reviewing my question might be helpful to others. I found that when I allocated an object creating a thread in the constructor at global scope I got strange behavior, but if I moved the objection creation just inside main() things worked as I expected. That seems to be consistent with comments on this question.

Hagio answered 13/5, 2022 at 17:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.