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

Rud*_*lis 6 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;
}
Run Code Online (Sandbox Code Playgroud)

它可以像这样编译:

g++ test.cpp -I/opt/vc/include -L/opt/vc/lib -lvcos  -o test
Run Code Online (Sandbox Code Playgroud)

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

calling recvfrom()
received signal 15
Run Code Online (Sandbox Code Playgroud)

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

UPDATE

好的,我更新了示例以生成一个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;
}
Run Code Online (Sandbox Code Playgroud)

Flo*_*mer 5

诸如的信号SIGTERM仅在过程中提交给一个线程.唯一的前提条件是所选线程必须要么没有屏蔽信号,要么必须等待它sigwait.其他线程不会直接通知信号已经传递.

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

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