Является ли основной поток разрешенным для создания потока POSIX до того, как он войдет в main()?
У меня есть этот объект, содержащий поток. Я хочу, чтобы судьба объекта и судьба нити были едины в одном. Поэтому конструктор создает поток (с pthread_create
), и деструктор выполняет действия, чтобы поток возвращался в течение разумного промежутка времени и затем соединял поток. Это работает нормально, пока я не создаю экземпляр одного из этих объектов со статической продолжительностью хранения. Если я создаю экземпляр одного из этих объектов в глобальном пространстве или пространстве имен или в статическом классе, программа компилирует fine (gcc 4.8.1), но сразу же переходит в режим работы. С заявлениями печати я определил, что основной поток даже не вводит main() перед segfault. Любые идеи?
Обновление: также добавлен оператор печати в первую строку конструктора (поэтому до вызова 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:
Хорошо, я понял это. Моя функция печати является атомарной, а также защищает cout
с мьютексом области внешнего пространства имен, определенным в другом месте. Я изменился до простого cout
, и он был напечатан в начале ctor. По-видимому, ни один из этих мьютексов продолжительности статического хранения не инициализируется, прежде чем что-то пытается получить к ним доступ. Так что, вероятно, это ответ Кейси.
Я просто не буду беспокоиться о сложных объектах и продолжительности статического хранения. В любом случае это не имеет большого значения.
Ответы
Ответ 1
Инициализация нелокальных переменных описана в С++ 11 §3.6.2, там тонна страшного материала в параграфе 2, связанная с потоками:
Если программа запускает поток (30.3), последующая инициализация переменной не зависит от инициализации переменной, определенной в другой единицы перевода. В противном случае инициализация переменной неопределенно секвенируется относительно инициализации переменной, определенной в другой единицы перевода. Если программа запускает поток, последующая неупорядоченная инициализация переменной не зависит от каждой другой динамической инициализации.
Я интерпретирую "последующая неупорядоченная инициализация переменной не зависит от каждой другой динамической инициализации" означает, что порожденный поток не может получить доступ к любой переменной с динамической инициализацией, которая не была инициализирована до того, как поток был порожден, не вызывая гонки данных, Если этот поток никоим образом не синхронизируется с main
, вы в основном танцуете на минном поле руками своими глазами.
Я настоятельно рекомендую вам прочитать и понять все 3,6; даже без потоков это огромный PITA, чтобы сделать многое до начала main
.
Ответ 2
Что произойдет, прежде чем вводить main будет специфичным для платформы, но вот ссылка на то, как main() выполняется в Linux
http://linuxgazette.net/84/hawk.html
Полезный снайпер
__ libc_start_main инициализирует необходимые материалы, особенно библиотеку C (например, malloc) и среду потоков, и вызывает нашу основную.
Для получения дополнительной информации найдите __libc_start_main
Не уверен, как это ведет себя в Windows, но кажется, что любой стандартный вызов библиотеки C перед входом на главную не является хорошей идеей.
Ответ 3
Там может быть много способов сделать это. См. Фрагмент ниже, где конструктор класса 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;
}