【问题标题】:Do I have to block signals on the main thread to handle cancel point on another thread?我是否必须阻止主线程上的信号才能处理另一个线程上的取消点?
【发布时间】:2021-06-14 19:37:52
【问题描述】:

当我在专用线程中运行的 TCP 服务器上工作时,我注意到信号处理中的奇怪行为。我准备了以下 MWE(我使用 cerr 来避免调试打印的竞争条件):

#include <signal.h>
#include <unistd.h>

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

using namespace std;

#undef THREAD

class RaiiObject
{
public:
    RaiiObject() { cerr << "RaiiObject ctor" << endl; }
    ~RaiiObject() { cerr << "RaiiObject dtor" << endl; }
};

static void signalHandler(int sig)
{
    write(2, "Signal\n", 7);
}

static void blockSigint()
{
    sigset_t blockset;

    sigemptyset(&blockset);
    sigaddset(&blockset, SIGINT);
    sigprocmask(SIG_BLOCK, &blockset, NULL);
}

static void setSigintHandler()
{
    struct sigaction sa;
    sa.sa_handler = signalHandler;
    sa.sa_flags = 0;
    sigemptyset(&sa.sa_mask);
    sigaction(SIGINT, &sa, NULL);
}

void runSelect()
{
    sigset_t emptyset;
    sigemptyset(&emptyset);

    setSigintHandler();

    RaiiObject RaiiObject{};
    fd_set fdRead;

    while (true) {
        cerr << "Loop iteration" << endl;
        FD_ZERO(&fdRead);
        FD_SET(0, &fdRead);
        while (true) {
            if (pselect(FD_SETSIZE, &fdRead, NULL, NULL, NULL, &emptyset) > 0) {
                cerr << "Select" << endl;
            } else {
                cerr << "Select break" << endl;
                return;
            }
        }
    }
}

int main()
{
    cerr << "Main start" << endl;

#ifdef THREAD
    cerr << "Thread start" << endl;
    //blockSigint();
    thread{runSelect}.join();
#else
    runSelect();
#endif

    cerr << "Main exit" << endl;

    return EXIT_SUCCESS;
}

当我编译一个单线程程序 (#undef THREAD) 时,我可以使用 Ctrl-C 正确终止 runSelect() 函数:

Main start
RaiiObject ctor
Loop iteration
^CSignal
Select break
RaiiObject dtor
Main exit

但是当我编译一个多线程 (#define THREAD) 程序时,它会挂在信号处理程序上:

Main start
RaiiObject ctor
Loop iteration
^CSignal

只有当我用blockSigint() 阻塞主线程上的信号时,程序才能再次按我的意愿工作。

我用strace -tt -f检查了程序,我注意到工作版本使用pselect6()ERESTARTNOHAND

14:46:53.543360 write(2, "Loop iteration", 14Loop iteration) = 14
14:46:53.543482 write(2, "\n", 1
)       = 1
14:46:53.543586 pselect6(1024, [0], NULL, NULL, NULL, {[], 8}) = ? ERESTARTNOHAND (To be restarted if no handler)
14:46:55.286989 --- SIGINT {si_signo=SIGINT, si_code=SI_USER, si_pid=2707461, si_uid=1000} ---
14:46:55.287120 write(2, "Signal\n", 7Signal
) = 7
14:46:55.287327 rt_sigreturn({mask=[]}) = -1 EINTR (Interrupted system call)
14:46:55.287569 write(2, "Select break", 12Select break) = 12
14:46:55.287760 write(2, "\n", 1

但损坏的版本使用futex():

[pid 3469011] 14:48:37.211792 write(2, "Loop iteration", 14Loop iteration) = 14
[pid 3469011] 14:48:37.211916 write(2, "\n", 1
) = 1
[pid 3469011] 14:48:37.212031 pselect6(1024, [0], NULL, NULL, NULL, {[], 8} <unfinished ...>
[pid 3469010] 14:48:40.046146 <... futex resumed>) = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
[pid 3469010] 14:48:40.046256 --- SIGINT {si_signo=SIGINT, si_code=SI_USER, si_pid=2707461, si_uid=1000} ---
[pid 3469010] 14:48:40.046354 write(2, "Signal\n", 7Signal
) = 7
[pid 3469010] 14:48:40.046588 rt_sigreturn({mask=[]}) = -1 EINTR (Interrupted system call)
[pid 3469010] 14:48:40.046821 futex(0x7f4e5c16b9d0, FUTEX_WAIT, 3469011, NULL) = ? ERESTARTSYS (To be restarted if SA_RESTART is set)

【问题讨论】:

  • cerr &lt;&lt; "Signal: " &lt;&lt; sig &lt;&lt; endl; -- 游戏结束。 iostreams 中没有任何东西是信号安全的。您不能在信号处理程序中执行此操作。
  • @SamVarshavchik 我怀疑这是这里的问题。似乎信号正在传递到主线程
  • 哦,我完全不怀疑信号正在“传递”。这不是问题所在。
  • @SamVarshavchik 我已经更改了信号处理程序,现在我使用了信号安全的write(),但它并没有改变任何东西(即使我使用了一个空的处理程序)。
  • unix.stackexchange.com/questions/225687那里有有用的背景资料吗?这也指向SO stackoverflow.com/questions/11679568

标签: c++ linux multithreading select signals


【解决方案1】:

我是否必须在主线程上阻塞信号才能处理另一个线程上的取消点?

您只需要在那些期望处理它们的线程中允许(取消屏蔽)信号,并在其他线程中阻止它们。

操作系统将deliver a process-directed signal to any thread 可以接收它。您的终端的 SIGINT 被发送到前台进程组中的每个进程,操作系统决定每个进程的哪个线程将接收它。

如果您只有两个线程,其中一个在 pselect 中具有原子未屏蔽的 SIGINT,而另一个已阻止 SIGINT,则操作系统会将 SIGINT 传递给前者。如果两者(或两者都不能)处理 SIGINT,操作系统将选择其中一个。

警告:当两个线程都屏蔽 INT 时,您的代码可能会“错过”生成的 SIGINT:

time  thr1        thr2
----  ----------  ------
  0   block(INT)   - 
  1   run thread  (awake)    <---- SIGINT
  3   join()      pselect()
  4   ...         ...

如果信号到达 thr2 的pselect 之外,操作系统会发现两个线程都阻塞了信号。在这种情况下,操作系统可以选择它喜欢的任何线程来让信号保持挂起,并且可以选择永远不会解除阻塞的 thr1。 SIGINT 将丢失。

这对您的应用程序可能没问题,也可能不是。

【讨论】:

  • 所以我有一个问题,因为sigaction() 将信号处理程序连接到所有线程,而sigprocmask() 有帮助,因为它仅在当前线程中禁用信号?
  • 信号处理是全局的,而信号掩码是线程特定的(并且是继承的)。
【解决方案2】:

如您所见,我的问题是sigaction() 已将信号处理程序连接到main() 线程和runSelect() 线程,因此main() 可以捕获SIGINT 信号。

现在我准备了一个版本,其中只有主线程处理 SIGINT 信号并将 SIGUSR1 信号发送到具有pthread_kill() 的特定线程。

#include <signal.h>
#include <unistd.h>

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

using namespace std;

pthread_t nativeHandle;

class RaiiObject
{
public:
    RaiiObject() { cerr << "RaiiObject ctor" << endl; }
    ~RaiiObject() { cerr << "RaiiObject dtor" << endl; }
};

static void sigintHandler(int)
{
    write(2, "INT\n", 4);
    pthread_kill(nativeHandle, SIGUSR1);
}

static void sigusrHandler(int)
{
    write(2, "USR\n", 4);
}

static void blockSigint()
{
    sigset_t blockset;

    sigemptyset(&blockset);
    sigaddset(&blockset, SIGINT);
    sigprocmask(SIG_BLOCK, &blockset, NULL);
}

static void setSigintHandler()
{
    struct sigaction sa;
    sa.sa_handler = sigintHandler;
    sa.sa_flags = 0;
    sigemptyset(&sa.sa_mask);
    sigaction(SIGINT, &sa, NULL);
}

static void setSigusrHandler()
{
    struct sigaction sa;
    sa.sa_handler = sigusrHandler;
    sa.sa_flags = 0;
    sigemptyset(&sa.sa_mask);
    sigaction(SIGUSR1, &sa, NULL);
}

void runSelect()
{
    sigset_t emptyset;
    sigemptyset(&emptyset);

    blockSigint();
    setSigusrHandler();

    RaiiObject RaiiObject{};
    fd_set fdRead;

    while (true) {
        cerr << "Loop iteration" << endl;
        FD_ZERO(&fdRead);
        FD_SET(0, &fdRead);
        while (true) {
            if (pselect(FD_SETSIZE, &fdRead, NULL, NULL, NULL, &emptyset) > 0) {
                cerr << "Select" << endl;
                return;
            } else {
                cerr << "Select break" << endl;
                return;
            }
        }
    }
}

int main()
{
    cerr << "Main start" << endl;

    cerr << "Thread start" << endl;
    thread runSelectThread{runSelect};
    nativeHandle = runSelectThread.native_handle();
    setSigintHandler();
    runSelectThread.join();

    cerr << "Main exit" << endl;

    return EXIT_SUCCESS;
}
Main start
Thread start
RaiiObject ctor
Loop iteration
^CINT
USR
Select break
RaiiObject dtor
Main exit

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多