我正在用C编写与AF_UNIX套接字一起使用的多线程服务器程序。 服务器的基本结构是:
select()
调用侦听各种套接字(新连接和已连接的客户端)。
select()
揭示了可能在连接套接字上进行的读取:主线程调用accept()
,并将返回的文件描述符放入fd_set
(读取集)中。select()
揭示可能在已连接的套接字上读取的内容:主线程从fd_set
(读取集)中删除就绪文件描述符,并将它们放入线程安全队列中。fd_set
(我编写了使该操作成为线程安全的函数),并再次在队列中等待新请求。此例程以无限周期重复,直到引发SIGINT。 必须在SIGUSR1上执行另一个功能,而不能退出循环。
我对此表示怀疑,因为如果引发SIGINT,则我的程序将以EINTR = Interrupted system call
退出。
我知道pselect()
调用和“自管”技巧,但我不知道如何使事情在多线程情况下正常工作。
我正在寻找一种(兼容POSIX的)信号管理,该信号管理可以防止在主线程在EINTR
上等待时发生pselect()
错误。
我发布了一些代码进行澄清:
我在这里设置信号处理程序(忽略errorConsolePrint
函数)
if(signal(SIGINT, &on_SIGINT) == SIG_ERR)
{
errorConsolePrint("File: %s; Line: %d; ", "Setting SIGINT handler", __FILE__, __LINE__);
exit(EXIT_FAILURE);
}
if(signal(SIGTERM, &on_SIGINT) == SIG_ERR)
{
errorConsolePrint("File: %s; Line: %d; ", "Setting SIGINT handler", __FILE__, __LINE__);
exit(EXIT_FAILURE);
}
if(signal(SIGUSR1, &on_SIGUSR1) == SIG_ERR)
{
errorConsolePrint("File: %s; Line: %d; ", "Setting to SIGUSR1 handler", __FILE__, __LINE__);
exit(EXIT_FAILURE);
}
if(signal(SIGPIPE, SIG_IGN) == SIG_ERR)
{
errorConsolePrint("File: %s; Line: %d; ", "Setting to ignore SIGPIPE", __FILE__, __LINE__);
exit(EXIT_FAILURE);
}
这里我为pselect
设置了信号掩码
sigemptyset(&mask);
sigemptyset(&saveMask);
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGUSR1);
sigaddset(&mask, SIGPIPE);
我在这里致电pselect
test = saveSet(masterSet, &backUpSet, &saveMaxFd);
CHECK_MINUS1(test, "Server: creating master set's backup ");
int test = pselect(saveMaxFd+1, &backUpSet, NULL, NULL, &waiting, &mask);
if(test == -1 && errno != EINTR)
{
...error handling...
continue;
}
希望有所帮助! 谢谢大家。
答案 0 :(得分:1)
您可能应该做的是将一个线程专用于信号处理。这是草图:
在main
中,在产生任何线程之前,请阻塞所有信号(使用pthread_sigmask
),除了SIGILL,SIGABRT,SIGFPE,SIGSEGV和SIGBUS之外。
然后,生成信号处理程序线程。该线程循环调用sigwaitinfo
以获取您关心的信号。它会采取适合每个人的任何操作;这可能包括向主线程发送消息以触发干净关闭(SIGINT),将“另一个功能”排队在工作池中(SIGUSR1)等,等等。您要做不安装处理程序这些信号。
然后您产生线程池,根本不必关心信号。
答案 1 :(得分:1)
我建议采取以下策略:
pselect
在主线程中解除阻塞线程。这样做的好处是,系统调用的 all (包括所有工作线程中的所有调用)将从不返回EINTR,除了单个{{1}在主线程中。例如,请参见Am I over-engineering per-thread signal blocking?和pselect does not return on signal when called from a separate thread but works fine in single thread program的答案。
此策略也可以与pselect
一起使用:只需在调用select
之前立即取消阻塞主线程中的信号,然后再重新阻塞它们。如果select
超时时间长或无限长,并且文件描述符大部分处于非活动状态,则只需要pselect
来防止挂起。 (我自己从未使用过select
,因为我主要与不具备此功能的旧版Unix一起使用。)
我认为您的信号处理程序合适:例如,它们只是原子地设置了一个全局变量。
顺便说一句,在示例代码中,您是否需要pselect
,因为sigaddset(&mask, SIGPIPE)
已经被忽略了?
答案 2 :(得分:1)
好,终于我找到了解决方法。
我的问题的核心是关于服务器的多线程性质。 经过长时间的搜索,我发现在从其他进程中发出信号(以异步方式)的情况下,哪个线程捕获信号都没有关系,因为行为保持不变:捕获了该信号,并且先前注册的处理程序为被执行。 也许这对于其他人可能是显而易见的,但这使我发疯,因为我不知道如何解释执行期间出现的错误。
此后,我发现我解决的另一个问题是关于过时的signal()
调用。
在执行期间,我第一次升起SIGUSR1
时,程序将按预期进行捕获和管理,但是第二次以User defined signal 1
退出。
我发现signal()
调用为特定信号设置了“一次”处理程序,在第一次处理该信号后,该信号的行为将返回默认信号。
这就是我所做的:
这里是信号处理程序:
N.B .:我在处理程序本身内部重置了SIGUSR1
的处理程序
static void on_SIGINT(int signum)
{
if(signum == SIGINT || signum == SIGTERM)
serverStop = TRUE;
}
static void on_SIGUSR1(int signum)
{
if(signum == SIGUSR1)
pendingSIGUSR1 = TRUE;
if(signal(SIGUSR1, &on_SIGUSR1) == SIG_ERR)
exit(EXIT_FAILURE);
}
我在服务器初始化期间设置了处理程序:
if(signal(SIGINT, &on_SIGINT) == SIG_ERR)
exit(EXIT_FAILURE);
if(signal(SIGTERM, &on_SIGINT) == SIG_ERR)
exit(EXIT_FAILURE);
if(signal(SIGUSR1, &on_SIGUSR1) == SIG_ERR)
exit(EXIT_FAILURE);
if(signal(SIGPIPE, SIG_IGN) == SIG_ERR)
exit(EXIT_FAILURE);
这里是服务器的监听周期:
while(!serverStop)
{
if (pendingSIGUSR1)
{
... things i have to do on SIGUSR1...
pendingSIGUSR1 = FALSE;
}
test = saveSet(masterSet, &backUpSet, &saveMaxFd);
CHECK_MINUS1(test, "Server: creating master set's backup ");
int test = select(saveMaxFd+1, &backUpSet, NULL, NULL, &waiting);
if((test == -1 && errno == EINTR) || test == 0)
continue;
if (test == -1 && errno != EINTR)
{
perror("Server: Monitoring sockets: ");
exit(EXIT_FAILURE);
}
for(int sock=3; sock <= saveMaxFd; sock++)
{
if (FD_ISSET(sock, &backUpSet))
{
if(sock == ConnectionSocket)
{
ClientSocket = accept(ConnectionSocket, NULL, 0);
CHECK_MINUS1(ClientSocket, "Server: Accepting connection");
test = INset(masterSet, ClientSocket);
CHECK_MINUS1(test, "Server: Inserting new connection in master set: ");
}
else
{
test = OUTset(masterSet, sock);
CHECK_MINUS1(test, "Server: Removing file descriptor from select ");
test = insertRequest(chain, sock);
CHECK_MINUS1(test, "Server: Inserting request in chain");
}
}
}
}
答案 3 :(得分:0)
先阅读signal(7)和signal-safety(7);您可能要使用特定于Linux的signalfd(2),因为它非常适合SIGTERM
和SIGQUIT
和SIGINT
)在poll(2)或旧的{ {3}}(或更新的pselect
或ppoll
)
另请参阅select(2)答案(以及此处提到的this自谋技巧,与POSIX兼容)。
还有pipe(7)个文档:
The effects of signal() in a multithreaded process are unspecified.
因此您确实应该使用signal(2)(即POSIX)。