closesocket是否线程安全?

6

如果我想从一个与运行服务器使用相同服务器套接字的另一个线程中调用closesocket()关闭服务器套接字,这样做是否安全?


2
你可能需要重新考虑为什么有多个线程访问同一个套接字。也许最好有一些状态标志,可以通知其他线程连接正在关闭,但只有一个线程实际上与套接字交互。 - Kerrek SB
1
@KerrekSB - 通常情况下,有多个线程访问套接字,因为一个线程执行阻塞读取,另一个线程写入。这是安全/正常的。如果线程在阻塞调用上被卡住,状态标志是无用的。 - Martin James
@MartinJames:虽然从套接字的角度来看,这种做法是“安全”的,但要正确实现这种场景非常困难(至少比起一开始看起来要难得多),因为在阻塞调用中协调线程的困难很大。无论如何,应用程序设计应该明确谁拥有套接字,“谁最后完成”确实是一个不错的选择。 - André Caron
什么大困难?通常是读取线程被卡住了。无论套接字因为其对等方关闭连接还是因为其他本地线程关闭套接字而引发异常/错误,问题都是相同的,如果有套接字对象列表等(例如聊天服务器),那么情况可能会很棘手。 - Martin James
2个回答

5

这个调用本身是线程安全的,但实践起来并非如此。每当您要释放一个资源且该资源的标识符在被释放后可能会被重复使用时,您必须与所有可能使用它的线程同步。否则,在资源被释放后,可能会分配一个具有相同标识符(套接字号码)的新资源(在您的情况下为套接字),并且意图访问(现在关闭的)服务器套接字的代码可能会在不同的套接字上运行。

这种情况的危险程度(以及是否可能发生)取决于您的代码。如果在关闭服务器套接字后从未创建任何其他套接字,则可能不会发生这种情况。但从概念上讲,这仍然是非常错误的,任何熟练的代码审查人员都会认为这是非常糟糕的。

编辑: 解决这类问题的方法是使用读写锁(rwlock)保护资源描述符(而不是资源本身)。访问资源描述符(在您的情况下是持有套接字号码的整数变量)需要持有"读"锁定,无论您将执行输入、输出还是使用它引用的资源进行其他操作。释放资源并在保存描述符的变量中存储类似于-1的哨兵值需要写锁定。


这就是'TIME_WAIT'的作用。如果存在一些更高级别的映射(例如,套接字库分配套接字上下文对象),并且未能确保这些上下文不能被重复使用,则不在OP问题的范围内。如果认为这种方法是错误的,那么也许有人可以提出另一种选择(不涉及将同步服务器重新设计为异步,并且不需要在套接字上进行CPU浪费的短暂超时来检查标志)。 - Martin James
1
不,这与“TIME_WAIT”完全无关。我说的不是端口被重用,这没有问题。我说的是套接字号码(在POSIX上,这将是文件描述符,但在Windows上它们是分开的),它是一个标识打开套接字的进程本地整数,被重用。这与“TIME_WAIT”绝对没有任何关系。 - R.. GitHub STOP HELPING ICE
虽然不是直接相关的,但它与之有关。这是一种资源,可能存在一些疑问,即何时可以重新使用它。解决这个问题的常见方法是将资源放在计时器队列中,直到重新使用可能会导致问题的合理概率已经过去(TCP TIME_WAIT)。对于套接字句柄,通常甚至可以轻松控制该问题而无需超时,例如通过从任何线程安全列表中删除句柄。仅仅因为某些东西可能被误用并不意味着永远不应该这样做,特别是如果没有合理的替代方案。 - Martin James
TIME_WAIT并不是套接字句柄上的计时器队列,而是地址/端口上的计时器队列,并且与手头的问题完全无关。手头的问题是一个完全通用的资源释放并发问题的例子,与套接字毫无关系,我已经解释了规范/通用的轻量级解决方案(rwlock)。 - R.. GitHub STOP HELPING ICE
你能再澄清一下吗?如果另一个线程在套接字上执行 recv 调用,会发生什么? - chacham15

1

没问题。当然,在其他线程中有对套接字的调用未完成时,可能会生成异常/错误,但网络堆栈本身(由于通常使用它的所有不同进程/线程都必须是线程安全的)不会受到损坏。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接