【发布时间】:2020-01-07 13:39:17
【问题描述】:
如何缓冲 UNIX 信号,使阻塞函数在被调用时解除阻塞?在我们的软件中,我们使用套接字。我们现在想通过信号取消阻止/取消recv() 调用。问题是如果在输入recv() 之前发送信号,那么它会丢失并且recv() 永远不会解除阻塞。
主函数如下所示:
bool signalHandlerSetup;
bool signalSent;
int main(int argc, char* argv[])
{
pthread_t thread;
signalHandlerSetup = false;
signalSent = false;
// Block the SIGUSR1 signal in the main thread
sigset_t signalSet;
sigemptyset(&signalSet);
sigaddset(&signalSet, SIGUSR1);
pthread_sigmask(SIG_BLOCK, &signalSet, NULL);
// Setup the signal handler for all future threads
struct sigaction signalAction;
signalAction.sa_flags = 0;
signalAction.sa_handler = [](int signalNumber) {};
sigaction(SIGUSR1, &signalAction, NULL);
pthread_create(&thread, NULL, secondaryThreadFunction, NULL);
std::cout << "Started thread" << std::endl;
// Wait until the signal handler is setup
while (!signalHandlerSetup);
pthread_kill(thread, SIGUSR1);
signalSent = true;
pthread_join(thread, NULL);
std::cout << "Joined thread" << std::endl;
}
辅助线程只是创建一个套接字并尝试从中读取:
void* secondaryThreadFunction(void *arg)
{
// Setup socket
int internSocket = setupSocket();
// Setup the signal handling
sigset_t signalSet;
sigemptyset(&signalSet);
sigaddset(&signalSet, SIGUSR1);
pthread_sigmask(SIG_UNBLOCK, &signalSet, NULL);
signalHandlerSetup = true;
while (!signalSent);
char buffer;
std::cout << "recv()..." << std::endl;
ssize_t bytesRead = recv(internSocket, static_cast<void*>(&buffer), sizeof(buffer), 0);
std::cout << "recv() canceled" << std::endl;
close(internSocket);
}
如您所见,在输入 recv() 函数之前明确发送了信号以说明问题。
除了缓冲之外,可能还有其他解决方案,但每个都有缺点:
- 等到辅助线程被阻塞(导致主线程也阻塞或忙循环)
- 不断发送信号(另一个忙循环)
编辑: 不应仅仅为了解除对辅助线程的阻塞而关闭套接字。我们可能想在解除阻塞后再次对同一个套接字进行操作。
编辑 2:
我已经能够使用 Martin James 和 Darren Smith 提供的解决方案来解决问题。我现在将select() 函数与事件文件描述符结合使用。参考这里是工作解决方案:
int eventDescriptor;
int main(int argc, char* argv[])
{
pthread_t thread;
// Create the event file descriptor
eventDescriptor = eventfd(
0, // Initial value
0 // Flags
);
if (eventDescriptor == -1)
{
std::cout << "Failed to create event file descriptor" << std::endl;
return -1;
}
pthread_create(&thread, NULL, secondaryThreadFunction, NULL);
std::cout << "Started thread" << std::endl;
// Notify the event descriptor
uint64_t valueToWrite = 1;
if (write(eventDescriptor, &valueToWrite, sizeof(uint64_t)) == -1)
{
std::cout << "Failed to write to event file descriptor" << std::endl;
return -1;
}
pthread_join(thread, NULL);
std::cout << "Joined thread" << std::endl;
close(eventDescriptor);
return 0;
}
这里是辅助线程:
void* secondaryThreadFunction(void *arg)
{
// Setup socket
int internSocket = setupSocket();
// Set up the file descriptor set
fd_set readFileDescriptorSet;
FD_ZERO(&readFileDescriptorSet);
FD_SET(internSocket, &readFileDescriptorSet);
FD_SET(eventDescriptor, &readFileDescriptorSet);
char buffer;
std::cout << "select()..." << std::endl;
int fileDescriptorsSet = select(std::max(internSocket, eventDescriptor) + 1, &readFileDescriptorSet, NULL, NULL, NULL);
if (FD_ISSET(eventDescriptor, &readFileDescriptorSet))
{
std::cout << "select() canceled via event" << std::endl;
}
else if (FD_ISSET(internSocket, &readFileDescriptorSet))
{
std::cout << "select() canceled through socket" << std::endl;
ssize_t bytesRead = recv(internSocket, static_cast<void*>(&buffer), sizeof(buffer), 0);
}
close(internSocket);
}
【问题讨论】:
-
@AlexF 在给出的示例代码中,可以关闭套接字,但在我们的软件解决方案中,我们只希望
recv()函数解除阻塞。稍后我们可能想再次对同一个套接字进行操作。因此,套接字不应关闭。 -
select() 在 recv() 之前?等待 fd 和信号量。从信号处理程序发出信号量信号 - 如果在达到选择之前发出信号量,则选择仍将“立即”返回,因为 sema 保留了计数值。
-
std::cout表示此代码是 C++,而不是 C。 -
你也可以使用pselect。
标签: c++ sockets pthreads signals