使用多个线程处理SIGTERM的正确方法

时间:2017-10-27 15:07:15

标签: c++ multithreading posix raspbian

我在Raspberry上有一个多线程程序,我想在其中处理SIGTERM并优先关闭所有内容。问题是我有一个在阻塞套接字上调用recvfrom()的后台线程。根据我对man手册的理解,如果我退出处理程序,所有系统调用都应该被唤醒并返回-1并将errno设置为EINTR。但是在我的情况下,recvfrom电话会一直挂着。

1)总的来说,我理解这一点,在这种情况下,所有能够被信号唤醒的阻塞系统调用的线程都应该被唤醒? 2)可能是操作系统在我的thead上设置了一些特殊的信号掩码吗?

有趣的部分是我正在使用VideoCore原语,而不是pthread,也许这可能是原因?这是一个小测试示例:

#include <iostream>

#include <cstdlib>
#include <cstring>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <signal.h>


#include "interface/vcos/vcos.h"

void SignalHandler(int nSignalNumber)
{
    std::cout << "received signal " << nSignalNumber << std::endl;
}

void* ThreadMain(void* pArgument)
{
    int nSocket = socket(AF_INET, SOCK_DGRAM, 0);
    if (nSocket >= 0)
    {
        sockaddr_in LocalAddress;
        memset(&LocalAddress, 0, sizeof(LocalAddress));
        LocalAddress.sin_family = AF_INET;
        LocalAddress.sin_addr.s_addr = INADDR_ANY;
        LocalAddress.sin_port = htons(1234);
        if (bind(nSocket, reinterpret_cast<sockaddr *>(&LocalAddress), sizeof(LocalAddress)) == 0)
        {
            sockaddr_in SenderAddress;
            socklen_t nSenderAddressSize = sizeof(SenderAddress);
            unsigned char pBuffer[512];
            std::cout << "calling recvfrom()" << std::endl;
            int nBytesReceived = recvfrom(nSocket, pBuffer, sizeof(pBuffer), 0, reinterpret_cast<struct sockaddr *>(&SenderAddress), &nSenderAddressSize);
            if (nBytesReceived == -1)
            {
                if (errno == EINTR)
                {
                    std::cout << "recvfrom() was interrupred by a signal" << std::endl;
                }
                else
                {
                    std::cout << "recvfrom() failed with " << errno << std::endl;
                }
            }
        }
        else
        {
            std::cout << "bind() failed with " << errno << std::endl;
        }
        close(nSocket);
    }
    else
    {
        std::cout << "socket() failed with " << errno << std::endl;
    }
    return NULL;
}

int main(int argc, char** argv)
{
    struct sigaction SignalAction;
    memset(&SignalAction, 0, sizeof(SignalAction));
    SignalAction.sa_handler = SignalHandler;
    sigaction(SIGTERM, &SignalAction, NULL);
    VCOS_THREAD_T Thread;
    VCOS_STATUS_T nVcosStatus = vcos_thread_create(&Thread, "", NULL, ThreadMain, NULL);
    if (nVcosStatus == VCOS_SUCCESS)
    {
        void* pData = NULL;
        vcos_thread_join(&Thread, &pData);
    }
    else
    {
        std::cout << "vcos_thread_create() failed with " << nVcosStatus << std::endl;
    }
    return EXIT_SUCCESS;
}

它可以像这样编译:

g++ test.cpp -I/opt/vc/include -L/opt/vc/lib -lvcos  -o test

当我运行它然后在正在运行的实例上调用kill时,输出为:

calling recvfrom()
received signal 15

并且该过程挂起。如果pthread表现不同,我会尝试。

更新

好的,我更新了示例以生成一个pthread线程,并且一个也没有退出。所以我假设信号没有填充到所有线程?

#include <iostream>

#include <cstdlib>
#include <cstring>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <signal.h>
#include <pthread.h>

#include "interface/vcos/vcos.h"

void SignalHandler(int nSignalNumber)
{
    std::cout << "received signal " << nSignalNumber << std::endl;
}

void* ThreadMain(void* pArgument)
{
    const char* pThreadType = reinterpret_cast<const char*>(pArgument);
    int nSocket = socket(AF_INET, SOCK_DGRAM, 0);
    if (nSocket >= 0)
    {
        sockaddr_in LocalAddress;
        memset(&LocalAddress, 0, sizeof(LocalAddress));
        LocalAddress.sin_family = AF_INET;
        LocalAddress.sin_addr.s_addr = INADDR_ANY;
        LocalAddress.sin_port = htons(pThreadType[0] * 100);
        if (bind(nSocket, reinterpret_cast<sockaddr *>(&LocalAddress), sizeof(LocalAddress)) == 0)
        {
            sockaddr_in SenderAddress;
            socklen_t nSenderAddressSize = sizeof(SenderAddress);
            unsigned char pBuffer[512];
            std::cout << "calling recvfrom()" << std::endl;
            int nBytesReceived = recvfrom(nSocket, pBuffer, sizeof(pBuffer), 0, reinterpret_cast<struct sockaddr *>(&SenderAddress), &nSenderAddressSize);
            if (nBytesReceived == -1)
            {
                if (errno == EINTR)
                {
                    std::cout << "recvfrom() was interrupred by a signal" << std::endl;
                }
                else
                {
                    std::cout << "recvfrom() failed with " << errno << std::endl;
                }
            }
        }
        else
        {
            std::cout << "bind() failed with " << errno << std::endl;
        }
        close(nSocket);
    }
    else
    {
        std::cout << "socket() failed with " << errno << std::endl;
    }
    std::cout << pThreadType << " thread is exiting" << std::endl;
    return NULL;
}

int main(int argc, char** argv)
{
    struct sigaction SignalAction;
    memset(&SignalAction, 0, sizeof(SignalAction));
    SignalAction.sa_handler = SignalHandler;
    sigaction(SIGTERM, &SignalAction, NULL);
    VCOS_THREAD_T VcosThread;
    VCOS_STATUS_T nVcosStatus = vcos_thread_create(&VcosThread, "", NULL, ThreadMain, const_cast<char*>("vcos"));
    bool bJoinVcosThread = false;
    if (nVcosStatus == VCOS_SUCCESS)
    {
        bJoinVcosThread = true;
    }
    else
    {
        std::cout << "vcos_thread_create() failed with " << nVcosStatus << std::endl;
    }
    pthread_t PthreadThread;
    int nPthreadStatus = pthread_create(&PthreadThread, NULL, ThreadMain, const_cast<char*>("pthread"));
    bool bJoinPthreadThread = false;
    if (nPthreadStatus == 0)
    {
        bJoinPthreadThread = true;
    }
    else
    {
        std::cout << "pthread_create() failed with " << nPthreadStatus << std::endl;
    }
    if (bJoinVcosThread)
    {
        void* pData = NULL;
        vcos_thread_join(&VcosThread, &pData);
    }
    if (bJoinPthreadThread)
    {
        void* pData = NULL;
        pthread_join(PthreadThread, &pData);
    }
    return EXIT_SUCCESS;
}

3 个答案:

答案 0 :(得分:5)

SIGTERM之类的信号仅提供给流程中的一个线程。唯一的前提条件是所选线程必须未屏蔽信号,或者必须使用sigwait等待它。其他线程不会直接通知信号已经传递。

将信号与线程组合在一起的常用方法是使用一个单独的线程来处理信号,并使用线程同步机制(如条件变量)通知其他线程。

对于中断文件I / O,这可能是不够的,因为在检查终止请求和进行系统调用以执行I / O操作之间存在竞争条件。一些语言运行时库使用带有pollepoll的非阻塞I / O和一个特殊的文件描述符,它可以在信号传递时使用(使用前面提到的基于线程的方法,或者某些东西)特定于Linux的signalfd)。其他人尝试使用readwrite系统调用直接使用复杂的舞蹈来避免这种开销,该舞蹈使用dup2将文件描述符替换为总是导致I / O失败的文件描述符,从而避免竞争条件(但需要的簿记相当复杂)。

答案 1 :(得分:1)

public class MyCustomCertificate extends X509Certificate { // wraps a X509Certificate private X509Certificate cert; public MyCustomCertificate(X509Certificate cert) { this.cert = cert; } public void myCustomFunctionality1() { // do something with this.cert } public void myCustomFunctionality2() { // do something with this.cert } // delegate all X509Certificate methods to the wrapped certificate public PublicKey getPublicKey() { return this.cert.getPublicKey(); } public Principal getSubjectDN() { return this.cert.getSubjectDN(); } // and so on... } 的{​​{3}}读取:

  

如果在系统调用或库函数调用被阻止时调用信号处理程序,则:

     
      
  • 在信号处理程序返回后自动重新启动调用;或

  •   
  • 调用失败并显示错误EINTR。

  •   
     

这两种行为中的哪一种取决于接口以及是否使用SA_RESTART标志建立了信号处理程序(请参阅sigaction(2))。 UNIX系统&lt; ...&gt;

的详细信息各不相同

下面几行,signal列在默认情况下使用recvfrom行为的函数中。 (注意:如果套接字超时,则会禁用此行为。)

因此,您应填写SA_RESTART结构的sa_flags字段,以小心避免设置sigaction标记。

答案 2 :(得分:1)

处理阻塞套接字的好方法 - 查看socket(7) - (甚至是非阻塞套接字)是使用多路复用系统调用,如poll(2)(或过时的select(2) ... 。)

关于信号,请务必阅读signal(7)signal-safety(7)

使用event loop处理信号的常用方法(使用poll(2))是有一个信号处理程序,只需write(2) - pipe(7)上的字节自身(您将在初始化时设置管道,并在事件循环中轮询它。 Qt文档解释了how and why。您也可以使用特定于Linux的signalfd(2)