Различные потоки С++ имеют одинаковый идентификатор потока в FreeBSD 10.

Я использую библиотеку ведения журналов C++ на компьютере с FreeBSD 10, и у меня возникают проблемы с закрытием потоков при получении сообщения sigint.

A создал проект GitHub для тестирования (ссылка). Если вы соберете его на FreeBSD 10, запустите его и нажмите [ctrl+c], он прекратит работу. Вы можете найти команды сборки, которые я использую ниже.

$ git clone [email protected]:tijme/free-bsd-thread-bug.git
$ cd free-bsd-thread-bug && mkdir -p cmake-build-debug && cd cmake-build-debug
$ cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER="/usr/local/bin/gcc6" -DCMAKE_CXX_COMPILER="/usr/local/bin/g++6"
$ make -dA
$ ./FreeBSDThreadBug

Использованный мной код (также можно найти в репозитории GitHub)

/* main.cpp */

#include "Example.h"
#include <iostream>
#include <csignal>
#include <thread>
#include <chrono>

Example* example = new Example();

void onSignal(int signum)
{
    delete example;
    exit(0);
}

int main() {
    signal(SIGINT, onSignal);

    std::this_thread::sleep_for(std::chrono::milliseconds(5000));

    return 0;
}

/* Example.h */

#ifndef FREEBSDTHREADBUG_EXAMPLE_H
#define FREEBSDTHREADBUG_EXAMPLE_H

#include <thread>
#include <iostream>
#include <chrono>

class Example {

public:
    Example();
    ~Example();
    std::thread threadHandle;
    void threadFunction();
};


#endif //FREEBSDTHREADBUG_EXAMPLE_H

/* Example.cpp */

#include "Example.h"
#include <thread>
#include <chrono>

Example::Example()
{
    std::cout << "Main: starting thread" << std::endl;
    threadHandle = std::thread(&Example::threadFunction, this);
    std::cout << "Main: thread started" << std::endl;
}

Example::~Example()
{
    std::cout << "THIS ID: " << std::this_thread::get_id() << std::endl;
    std::cout << "THREAD ID: " << threadHandle.get_id() << std::endl;

    std::cout << "Main: joining thread" << std::endl;
    threadHandle.join();
    std::cout << "Main: thread joined" << std::endl;
}

void Example::threadFunction() {
    std::cout << "Thread: starting to sleep" << std::endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(2500));
    std::cout << "Thread: sleep finished" << std::endl;
}

Правильный вывод (например, на MacOS Sierra):

Как видите, идентификаторы потоков отличаются, как и ожидалось.

$ ./FreeBSDThreadBug
Main: starting thread
Main: thread started
Thread: starting to sleep
^C
THIS ID: 0x7fffa428a3c0
THREAD ID: 0x70000c044000
Main: joining thread
Thread: sleep finished
Main: thread joined

Неверный вывод (прекращение работы во FreeBSD 10.3):

Идентификаторы потоков здесь одинаковы, что довольно странно.

$ ./FreeBSDThreadBug 
Main: starting thread
Main: thread started
Thread: starting to sleep
^C
THIS ID: 0x801c06800
THREAD ID: 0x801c06800
Main: joining thread
terminate called after throwing an instance of 'std::system_error'
  what():  Resource deadlock avoided
Abort (core dumped)

Дамп памяти

Core was generated by `FreeBSDThreadBug'.
Program terminated with signal SIGABRT, Aborted.
#0  0x00000008012d335a in thr_kill () from /lib/libc.so.7
[Current thread is 1 (LWP 100146)]
(gdb) bt full
#0  0x00000008012d335a in thr_kill () from /lib/libc.so.7
No symbol table info available.
#1  0x00000008012d3346 in raise () from /lib/libc.so.7
No symbol table info available.
#2  0x00000008012d32c9 in abort () from /lib/libc.so.7
No symbol table info available.
#3  0x0000000800ad8afd in __gnu_cxx::__verbose_terminate_handler () at /wrkdirs/usr/ports/lang/gcc6/work/gcc-6.3.0/libstdc++-v3/libsupc++/vterminate.cc:95
        terminating = true
        t = <optimized out>
#4  0x0000000800ad5b48 in __cxxabiv1::__terminate (handler=<optimized out>) at /wrkdirs/usr/ports/lang/gcc6/work/gcc-6.3.0/libstdc++-v3/libsupc++/eh_terminate.cc:47
No locals.
#5  0x0000000800ad5bb1 in std::terminate () at /wrkdirs/usr/ports/lang/gcc6/work/gcc-6.3.0/libstdc++-v3/libsupc++/eh_terminate.cc:57
No locals.
#6  0x0000000800ad5dc8 in __cxxabiv1::__cxa_throw (obj=obj@entry=0x80200e0a0, tinfo=0x800dd0bc0 <typeinfo for std::system_error>, dest=0x800b073b0 <std::system_error::~system_error()>)
    at /wrkdirs/usr/ports/lang/gcc6/work/gcc-6.3.0/libstdc++-v3/libsupc++/eh_throw.cc:87
        globals = <optimized out>
#7  0x0000000800b04cd1 in std::__throw_system_error (__i=11) at /wrkdirs/usr/ports/lang/gcc6/work/gcc-6.3.0/libstdc++-v3/src/c++11/functexcept.cc:130
No locals.
#8  0x0000000800b0792c in std::thread::join (this=0x801c5c058) at /wrkdirs/usr/ports/lang/gcc6/work/gcc-6.3.0/libstdc++-v3/src/c++11/thread.cc:139
        __e = <optimized out>
#9  0x00000000004016fc in Example::~Example (this=0x801c5c058, __in_chrg=<optimized out>) at /root/FreeBSDThreadBug/Example.cpp:18
No locals.
#10 0x00000000004010b7 in onSignal (signum=2) at /root/FreeBSDThreadBug/main.cpp:11
No locals.
#11 0x000000080082fb4a in ?? () from /lib/libthr.so.3
No symbol table info available.
#12 0x000000080082f22c in ?? () from /lib/libthr.so.3
No symbol table info available.
#13 <signal handler called>
No symbol table info available.
#14 0x00000008012efb5a in _nanosleep () from /lib/libc.so.7
No symbol table info available.
#15 0x000000080082cc4c in ?? () from /lib/libthr.so.3
No symbol table info available.
#16 0x000000000040155d in std::this_thread::sleep_for<long, std::ratio<1l, 1000l> > (__rtime=...) at /usr/local/lib/gcc6/include/c++/thread:322
        __s = {__r = 2}
        __ns = {__r = 500000000}
        __ts = {tv_sec = 1, tv_nsec = 126917539}
#17 0x000000000040177a in Example::threadFunction (this=0x801c5c058) at /root/FreeBSDThreadBug/Example.cpp:24
No locals.
#18 0x0000000000402432 in std::__invoke_impl<void, void (Example::* const&)(), Example*>(std::__invoke_memfun_deref, void (Example::* const&)(), Example*&&) (
    __f=@0x801c5e050: (void (Example::*)(Example * const)) 0x40172c <Example::threadFunction()>, __t=<unknown type in /root/FreeBSDThreadBug/cmake-build-debug/FreeBSDThreadBug, CU 0x552f, DIE 0xb2d7>)
    at /usr/local/lib/gcc6/include/c++/functional:227
No locals.
#19 0x00000000004023bf in std::__invoke<void (Example::* const&)(), Example*>(void (Example::* const&)(), Example*&&) (__fn=@0x801c5e050: (void (Example::*)(Example * const)) 0x40172c <Example::threadFunction()>, 
    __args#0=<unknown type in /root/FreeBSDThreadBug/cmake-build-debug/FreeBSDThreadBug, CU 0x552f, DIE 0xb2d7>) at /usr/local/lib/gcc6/include/c++/functional:251
No locals.
#20 0x0000000000402370 in std::_Mem_fn_base<void (Example::*)(), true>::operator()<Example*>(Example*&&) const (this=0x801c5e050, 
    __args#0=<unknown type in /root/FreeBSDThreadBug/cmake-build-debug/FreeBSDThreadBug, CU 0x552f, DIE 0xb2d7>) at /usr/local/lib/gcc6/include/c++/functional:604
No locals.
#21 0x000000000040233b in std::_Bind_simple<std::_Mem_fn<void (Example::*)()> (Example*)>::_M_invoke<0ul>(std::_Index_tuple<0ul>) (this=0x801c5e048) at /usr/local/lib/gcc6/include/c++/functional:1391
No locals.
#22 0x0000000000402289 in std::_Bind_simple<std::_Mem_fn<void (Example::*)()> (Example*)>::operator()() (this=0x801c5e048) at /usr/local/lib/gcc6/include/c++/functional:1380
No locals.
#23 0x0000000000402268 in std::thread::_State_impl<std::_Bind_simple<std::_Mem_fn<void (Example::*)()> (Example*)> >::_M_run() (this=0x801c5e040) at /usr/local/lib/gcc6/include/c++/thread:196
No locals.
#24 0x0000000800b0769f in std::execute_native_thread_routine (__p=0x801c5e040) at /wrkdirs/usr/ports/lang/gcc6/work/gcc-6.3.0/libstdc++-v3/src/c++11/thread.cc:83
        __t = std::unique_ptr<std::thread::_State> containing 0x801c5e040
#25 0x000000080082a855 in ?? () from /lib/libthr.so.3
No symbol table info available.
#26 0x0000000000000000 in ?? ()
No symbol table info available.
Backtrace stopped: Cannot access memory at address 0x7fffdfffe000

Информация о системе

$ freebsd-version
10.3-RELEASE

$ /usr/local/bin/gcc6 --version
gcc6 (FreeBSD Ports Collection) 6.3.0

$ /usr/local/bin/g++6 --version
g++6 (FreeBSD Ports Collection) 6.3.0

$ cmake --version
cmake version 3.7.2

Оригинальную задачу, которую я создал, можно найти на GitHub (ссылка), однако пока нет исправления.

Я надеюсь, что кто-то сможет помочь мне решить эту проблему. Заранее спасибо.


person Tijme    schedule 30.05.2017    source источник
comment
Вы довольно ограничены в том, что вы можете делать внутри обработчика сигнала, и я предполагаю, что вызов delete — это одна из многих вещей, которые вы не должны там делать.   -  person    schedule 31.05.2017
comment
Возможно, вам следует сообщить об этой ошибке сопровождающему LibC FreeBSD, а не публиковать ее здесь.   -  person Henri Menke    schedule 31.05.2017
comment
Также из en.cppreference.com/w/c/program/signal » Поведение не определено, если signal используется в многопоточной программе. Не требуется быть потокобезопасным.«   -  person Henri Menke    schedule 31.05.2017
comment
Рассмотрите возможность печати std::this_thread::get_id() в начале Example::threadFunction() — это определенно поможет вашему расследованию.   -  person iehrlich    schedule 31.05.2017
comment
В качестве примера того, почему управление памятью внутри обработчика сигналов плохо, представьте, что произойдет, если вы выполняете функцию new в то же время, когда обработчик выполняет вашу delete. Хаос, тупик, собаки и кошки, живущие вместе...   -  person Zan Lynx    schedule 31.05.2017


Ответы (1)


Это не баг, это фича.

Вы не получаете гарантии того, куда будет доставлен ваш сигнал, и набор вещей, которые вам разрешено делать в обработчике сигнала, ограничен.

См. sigaction(3) для получения подробной информации о том, что вы можете делать (и что вы не можете делать ничего другого). Ваша программа делает много вещей, которые не разрешены в обработчике сигналов.

Правильнее всего сигнализировать о чем-то еще в вашей программе и возвращаться из обработчика сигнала. Примером техники для этого является «трюк с собственной трубкой». Создайте трубу и держите ручки на обоих концах. Чтение с одного конца в вашей обычной обработке ввода-вывода. Если вы получаете сигнал, в обработчике сигнала записывайте байт на другой конец канала и возвращайтесь. Когда байт считывается из канала, вы знаете, что сигнал прибыл, и вы можете безопасно выполнять расширенную обработку.

Обновлять:

Как указал Майкл Берр, вы можете заблокировать определенные потоки от получения определенных сигналов, используя pthread_sigmask(3). Однако, чтобы решить основную проблему здесь, вам все равно не нужно выполнять работу в обработчике сигнала.

person janm    schedule 31.05.2017
comment
Хотя это не происходит автоматически, следует отметить, что в POSIX программа может организовать доставку сигналов в конкретный поток (или, скорее, может организовать доставку сигналов не в определенные потоки). . Поток может «дисквалифицировать» себя от вызова для обработки одного или нескольких сигналов с помощью функции pthread_sigmask(). - person Michael Burr; 31.05.2017
comment
@MichaelBurr Да, я согласен, что pthread_sigmask() может изменить место доставки сигнала. Хотя ОП по-прежнему должен выполнять работу вне обработчика сигналов! (Я знаю, что ты это знаешь...) - person janm; 31.05.2017
comment
Спасибо, я посмотрю, как исправить это в ближайшее время. Не странно ли, что он работает на других ОС и более поздних версиях FreeBSD? - person Tijme; 31.05.2017
comment
Это одна из разочаровывающих вещей в неопределенном поведении: оно часто приводит к желаемому поведению. Но затем это в конечном итоге не работает, и вы остаетесь ни с чем, кроме людей, говорящих вам, что вы не должны были ожидать, что это когда-либо сработает. - person Michael Burr; 31.05.2017
comment
Спасибо за помощь, узнал кое-что новое! Я исправил это с помощью трюка с собственной трубой :) - person Tijme; 31.05.2017
comment
@Tijme Рад, что это помогло! - person janm; 01.06.2017