用多个线程处理SIGTERM的正确方法
Proper way to handle SIGTERM with multiple threads
我在Raspberry上有一个多线程程序,我想在其中处理SIGTERM并优雅地关闭一切。问题是我有一个后台线程在阻塞套接字上调用了recvfrom()
。根据我从手册页中的理解,如果我退出我的处理程序,所有的系统调用都应该被唤醒,并返回-1和errno
设置为EINTR
。然而,在我的情况下,recvfrom
呼叫一直处于挂起状态。
1) 总的来说,我理解得对吗?在这种情况下,所有有阻塞系统调用的线程都应该被信号唤醒?2) 可能是操作系统在我的电脑上设置了一些特殊的信号屏蔽吗?
有趣的是,我使用的是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;
}
像SIGTERM
这样的信号只提交给进程中的一个线程。唯一的前提条件是所选线程必须没有屏蔽信号,或者必须使用sigwait
等待信号。其他线程将不会被直接通知信号已经被传递。
将信号与线程组合的一种常见方法是有一个单独的线程,该线程仅处理信号,并使用线程同步机制(如条件变量)通知其他线程。
对于中断文件I/O,这可能是不够的,因为在检查终止请求和进行系统调用以执行I/O操作之间存在竞争条件。一些语言运行时库使用poll
或epoll
的非阻塞I/O,并带有一个特殊的文件描述符,该描述符在信号传递时即可准备就绪(可以使用前面提到的基于线程的方法,也可以使用类似signalfd
的Linux特定方法)。其他人则试图通过直接使用read
和write
系统调用来避免这种开销,这种调用使用dup2
将文件描述符替换为总是导致I/O失败的描述符,从而避免了竞争条件(但所需的记账相当复杂)。
signal
的手册页显示:
如果在系统调用或库函数调用被阻止时调用了信号处理程序,则:
在信号处理程序返回后,调用会自动重新启动;或
调用失败,并出现错误EINTR。
发生这两种行为中的哪一种取决于接口以及信号处理程序是否使用SA_RESTART标志建立(请参见sigaction(2))。详细信息因UNIX系统而异<…>
下面几行中,recvfrom
列在默认情况下使用SA_RESTART
行为的函数中。(注意:如果套接字超时,此行为将被禁用。)
因此,您应该填充sigaction
结构的sa_flags
字段,以小心地避免设置SA_RESTART
标志。
处理阻塞套接字(参见套接字(7)-(甚至是非阻塞套接字)的一个好方法是使用类似poll(2)(或过时的select(2)…)的多路复用系统调用
关于信号,请务必阅读信号(7)和信号安全(7)。
用一些事件循环(使用poll(2))处理信号的一种常见方法是使用一个信号处理程序,它只需将管道(7)上的一个字节写(2)-s到self(您将在初始化时设置管道,并在事件循环中对其进行轮询)。Qt文档解释了如何以及为什么。您还可以使用特定于Linux的signalfd(2)。
- 为不同配置设置MSVC_RUNTIME_LIBRARY的正确方法是什么
- 通过方法访问结构
- 最小硬币更换问题(自上而下方法)
- C++为构建时间获取QDateTime的可靠方法
- 在C#中处理C++指针而不使用unsafe的最佳方法
- 处理多个异常集合的C++方法
- 如果C++类在类方法中具有动态分配,但没有构造函数/析构函数或任何非静态成员,那么它仍然是POD类型吗
- 有什么方法可以遍历结构吗
- 当类在C++中定义时,有什么方法可以"register"类吗?
- 在C++中,将大的无符号浮点数四舍五入为整数的最佳方法是什么
- 实现无开销push_back的最佳方法是什么
- 使用std::函数映射对象方法
- 有符号的int和int-有没有一种方法可以在C++中区分它们
- C++从另一个类访问公共静态向量的正确方法是什么
- C++优先级队列,按对象的唯一指针的特定方法升序排列
- 没有为自己的结构调用列表推回方法
- 有没有什么方法可以使用一个函数中定义的常量变量,也可以由c++中同一程序中的其他函数使用
- 在类定义之后定义一个私有方法
- 枚举环境变量的惯用C++14/C++17方法
- 用多个线程处理SIGTERM的正确方法