【问题标题】:How to cleanly interrupt a thread blocking on a recv call?如何干净地中断recv调用上的线程阻塞?
【发布时间】:2013-07-23 05:18:55
【问题描述】:

我有一个用 C 编写的多线程服务器,每个客户端线程看起来像这样:

ssize_t n;
struct request request;

// Main loop: receive requests from the client and send responses.
while(running && (n = recv(sockfd, &request, sizeof(request), 0)) == sizeof(request)) {
    // Process request and send response.
}
if(n == -1)
    perror("Error receiving request from client");
else if(n != sizeof(act))
    fprintf(stderr, "Error receiving request from client: Incomplete data\n");

// Clean-up code.

在某些时候,客户端满足必须断开连接的特定条件。如果客户端定期发送请求,这很好,因为它可以在响应中被告知断开连接;然而,有时客户端需要很长时间才能发送请求,因此客户端线程最终会在 recv 调用中阻塞,并且客户端在下一个请求/响应之前不会断开连接。

当客户端线程在recv 调用中阻塞时,是否有一种干净的方法可以断开客户端与另一个线程的连接?我尝试了close(sockfd),但这会导致错误Error receiving request from client: Bad file descriptor 发生,这确实不准确。

或者,有没有更好的方法让我在这里处理错误?

【问题讨论】:

  • @DrewMcGowen 谢谢,我曾寻找类似的线程,但我设法错过了那个。仍然不确定这是完全相同的问题,因为完全杀死线程并不是我真正认为干净的。
  • @DanielGibbs:“kill”是一个用于发送信号的函数的不幸名称。也许在另一个现实中,pthread_kill() 被称为pthread_send_signal()。但是,是的,您可以使用信号彻底中断recv()
  • @MartinJames:这个解决方案是绝对错误的。它有极其危险的比赛条件。假设recv 被信号中断,close 在信号处理程序运行时发生。信号处理程序返回后,recv 在相同的 fd 编号上重新启动。如果幸运的话,fd 是无效的,它会返回 EBADF。但是如果你很不走运,另一个线程打开了一个新的 fd,得到了相同的 fd 编号,而你只是窃取了另一个线程的输入。
  • 然而,close 方法的安全替代方法是使用shutdown。基本上,它会半关闭或完全关闭 TCP 连接,但会保留文件描述符。

标签: c multithreading sockets pthreads posix


【解决方案1】:

要中断线程,请使套接字非阻塞(使用fcntl 设置O_NONBLOCK),然后使用pthread_kill 向线程发出信号。这样,recv 将失败,EINTR 如果它正在睡觉,或者EAGAINEWOULDBLOCK 如果它不是(也可能如果SA_RESTART 有效,没有检查)。请注意,在此之前,套接字不需要,实际上也不应该是非阻塞的。 (当然需要处理信号;空处理程序就足够了)。

为了确保捕捉到停止信号而不是其他任何东西,请使用标志;有些事情可能会出错。例如,recv 可能会在某些虚假信号上出现 EINTR 失败。或者如果有一些可用的数据,它可能会成功,实际上忽略了停止请求。

以及该做什么:

  1. 不要单独使用pthread_kill 或与任何普通支票一起使用。它可能会在发出 recv 系统调用之前到达,但要在所有检查之后中断它还为时过早。

  2. 不要close 套接字。这甚至可能行不通,因为@R.. 指针指向,是很危险的,因为套接字文件描述符可能在closerecv 之间重复使用(除非您确定没有打开文件描述符)。

【讨论】:

  • 被屏蔽了怎么可能一直在睡觉呢?如果没有被阻止,就没有问题要回答。仅当读取超时有效且已过期时,您才会获得 EAGAIN/EWOULDBLOCK。
  • @user207421 无法确保它已经进入阻塞系统调用。它可能会在发出调用之前被抢占任意长的时间。
【解决方案2】:

关闭套接字以获取来自另一个线程的输入。这将导致读取线程接收到一个 EOS,这将导致它关闭套接字并在正确写入时终止。

【讨论】:

  • 注意:我认为当@EJP 说“关闭”时,他的意思是对shutdown 的字面调用,即对close 的调用不会中断线程。
  • @checham 'shutdown the socket for input' 只有一个含义。我没有在任何地方使用“关闭”这个词。你并没有真正帮助。
  • 很自然的假设认为关闭会导致关闭。因此,我写了我的评论,以澄清其他可能像我一样误解的人。无需敌对。
  • @chacham15 close() 确实会导致关机,但这并不重要。你的观点让我无法理解。
  • shutdown() 不能保证解除阻塞已在进行的阻塞套接字调用。有时会,有时不会。这取决于平台。
【解决方案3】:

所以你至少有这些可能性:

(1) pthread_kill 将使用 errno == EINTR 将线程从recv 中吹出,您可以自行清理并退出线程。有些人认为这很恶心。取决于,真的。

(2) 使您的客户端套接字非阻塞并使用select 等待输入一段时间,然后检查线程之间使用的开关是否已设置为指示它们应该关闭。

(3) 结合 (2) 让每个线程与主线程共享一个管道。将其添加到select。如果它变得可读并包含关闭请求,则线程将自行关闭。

(4) 如果以上(或其变体)都不能满足您的需求,请查看pthread_cancel 机制。

【讨论】:

  • 对,所以如果我打电话给pthread_killSIGUSR1,然后检查errno 是否是EINTR,那应该可以吗?我需要为SIGUSR 的线程添加信号处理程序吗?如果是这样,它应该怎么做?
  • 信号处理程序设置一个开关。当你得到 EINTR 时,你检查开关,如果它打开,那么你清理并离开。前几天我回答了一个与此有些相似的问题,它或多或少涵盖了基础知识stackoverflow.com/a/17607149/63743
  • pthread_kill 只会在您安装没有SA_RESTART 选项的信号处理程序时导致EINTR。这种方法也有导致信号丢失的竞争条件,但您可能并不关心这一点,并且它不是“库安全”的,因为您必须修改程序的全局状态(信号处置)才能使用它。 pthread_cancel 确实是正确的解决方案。
  • 啊,是的,我明白了。我将如何在每个线程的基础上设置一个开关?
  • 你可以将 switch 设置为线程本地的,但实际上,这已经进入了糟糕的黑客领域。 pthread_cancel 完全符合您的要求;只需设置正确的清理处理程序。唯一一次 pthread_cancel 不是正确的解决方案是,如果您需要突破 recv 但保持线程运行。
猜你喜欢
  • 1970-01-01
  • 2013-08-18
  • 2014-03-29
  • 1970-01-01
  • 2015-07-20
  • 2013-11-16
  • 1970-01-01
  • 2014-10-26
  • 2011-12-24
相关资源
最近更新 更多