Accept() 是线程安全的吗?
我目前正在用 C 语言为我正在做的课程编写一个简单的网络服务器。我们的一个要求是实现一个线程池来使用pthreads
处理连接。
我知道我将如何粗略地执行此操作(在主线程中调用 accept
并将文件描述符传递给 freee 线程),但是我的朋友建议了一种替代方法,而不是我想到的方法:预先创建所有线程,并让它们在调用 accept
时永远循环。这个想法是,accept
将阻塞所有空闲线程,并且当连接进入时,仅将文件描述符提供给其中一个线程。然后,当给定线程完成连接后,它会循环返回并再次阻塞对 accept
的调用。本质上使用对accept()的调用作为信号量。他认为这将大大简化实现,因为您不需要跟踪哪些线程正忙,哪些线程已准备好连接。理论上它的延迟也会更低,因为线程可以立即开始执行(无需创建它)。
我的问题是,这安全吗?我打算实施它并尝试一下,但我还没有准备好,我很好奇想知道答案。我在 google 和 stackoverflow 上搜索过,但找不到任何人这样做。 accept
线程安全吗?我认为这种方法会产生更多的开销,因为您一直在运行所有线程,这两种方法只是简单的内存/延迟权衡吗?
编辑:我不确定这是否应该是社区维基,如果应该是,抱歉,我找不到按钮:P
I'm currently writing a simple webserver in C for a course I'm doing. One requirement is for us to implement a thread pool to handle connections using pthreads
.
I know how I would go about doing this roughly(calling accept
in a main thread and passing the file descriptor to a freee thread), however my friend suggested an alternate method than the one I had in mind: creating all my threads up front, and getting them all to loop forever on a call to accept
. The idea being that accept
will block all the idle threads and when a connection comes in, only giving the file descriptor to one of them. Then when a given thread is done with a connection it loops back around and blocks on a call to accept
again. Using the call to accept()
as a semaphore essentially. This would simplify the implementation quite a bit he figures, as you wouldn't need to keep track of which threads are busy and which are ready for a connection. It would also be lower latency in theory, as the thread can immediately start executing (without the need to create it).
My question is, is this safe? I'm planning to implement it and try it out, but I'm not ready yet and I'm quite curious to know the answer. I've searched on google and here on stackoverflow, but couldn't find anyone doing it this way. Is accept
thread safe? I assume there will be more overhead with this approach as you are running all your threads all the time, are the two approaches simply a simple memory/latency tradeoff?
Edit: I'm unsure if this should be community wiki, apologies if it should be, I can't find the button :P
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
是的。这是设计多线程服务器的常用方法和公认的设计实践。
您还可以多次
fork
并让子进程调用accept
,这将允许您在不需要线程库的情况下进行多线程处理。较旧的服务器会这样做。Yes. This is a common way to design multithreaded servers and accepted design practice.
You can also
fork
several times and have the child processes callaccept
, this will allow you to do multithreading without needing a threads library. Older servers do this.由于此内容已获得奖励,请求参考:
是的,accept() 是线程安全的,正如 POSIX 定义该术语一样。
相关参考文献为 POSIX.1 的第 2.9.1 节,当前版本表示:
为了完整起见,POSIX 确实定义了
accept()
: https://pubs.opengroup.org/onlinepubs/9699919799/functions/accept.html,这样没有出现在异常列表中的 POSIX 函数,POSIX 指定它是线程安全的。Since this has been bountied, asking for references:
Yes,
accept()
is thread-safe, as POSIX defines that term.The relevant reference would be section 2.9.1 of POSIX.1, the current version of which says:
For completeness, POSIX does define
accept()
: https://pubs.opengroup.org/onlinepubs/9699919799/functions/accept.html, so as a POSIX function that does not appear on the list of exceptions, POSIX specifies that it is thread-safe.在评论中,@Rick(赏金提供者)说:
关于线程安全的 POSIX 定义确实指的是同一进程中的线程(请参阅:§2.9)。
如果您询问
fork()
之后会发生什么,以及父级和子级同时调用accept()
是否安全,我们首先注意到 POSIX 定义称为连接指示队列的系统资源。然后我们注意到fork()
< /a> 获取父级描述符的副本,因此子级和父级将访问相同的连接指示队列(就像文本文件的重复文件描述符将访问同一文件一样)。此时,
accept()
对每个进程(子进程和父进程)执行的操作的定义是相同的。In comments, @Rick (the bounty offerer) says:
The POSIX definitions regarding thread-safety do indeed refer to threads in the same process (see: §2.9).
If you are asking about what happens after
fork()
, and if it is safe for the parent and child to concurrently call intoaccept()
, we note first that POSIX defines a system resource known as a connection indication queue. Then we note that the child offork()
gets a copy of the parent's descriptors, and therefore the child and parent will access the same connection indication queue (just as a duplicate file descriptor to a text file would be accessing the same file).The definition of what
accept()
does for each process (child and parent) is the same at that point.应用程序链接到系统上的
libc
实现,以便调用accept()
和其他与套接字相关的函数(#include) )。您想阅读其文档。
Linux 上最常见的
libc
实现来自 GNU(或者 Android 上的 Googlebionic
),它被称为glibc
,它非常有用。可能您正在(将)使用什么。如接受
文档 对于 glibc:如中所述POSIX 安全概念,初步部分列举了以下属性:
下面是对这些概念的解释(另外,请检查维基百科上的“线程安全”以了解不同的方法实现线程安全)。根据文档,
accept
被声明为 MT-Safe:事实上,
glibc
的accept
实现只是重定向到内核系统调用,这使得此描述对于 Linux 上的其他libc
实现也很有用。系统(也可能只是执行到系统调用的重定向)。另一方面,更通用的方法是检查
man-pages 项目<如果可用的话,您系统上的 /code>
(在大多数系统上最接近官方文档),其中:
通过输入
man 2 Accept
在命令行上:我们看到
POSIX.1-2008
是一个可行的参考(检查此相关描述Linux 系统的标准)。正如其他答案中已经说过的,POSIX.1
标准将accept
函数指定为 (POSIX-
)线程安全 (如基本定义,第 3.399 节线程安全中所定义),但未将其列在系统接口,第 2.9.1 节线程安全上。最后,由于
glibc
只是委托内核的accept()
,因此最可靠的源代码是内核源代码(当然)。 这个答案在accept()
ing时通过内核代码路径:看看并说服自己共享资源受自旋锁保护,特别是 套接字状态 和 等待应用程序接受的连接队列。Applications link against the
libc
implementation on a system in order to callaccept()
and other socket-related functions (#include <sys/socket.h>
). You want to read its documentation.The most common implementation of
libc
on Linux comes from GNU (or maybebionic
from Google on Android), it is calledglibc
and it is very likely what you are (will be) using. As stated inaccept
documentation forglibc
:As explained in POSIX Safety concepts, the Preliminary section enumerates properties which:
And an explanation of such concepts follow (also, check "Thread Safety" on wikipedia for different approaches to achieve thread safety).
accept
is declared as MT-Safe, according to doc:The fact that
glibc
'saccept
implementation just redirect to the kernel system call, makes this description a useful one also for otherlibc
implementations on Linux systems (which likely just perform a redirection to the system call too).On the other hand, a more general approach is to check the
man-pages project
on your system if available (the closest thing to an official documentation on most systems), which:By typing
man 2 accept
on the command line:We see that
POSIX.1-2008
is a viable reference (check this for a description of relevant standards for Linux systems). As already said in other answers,POSIX.1
standard specifiesaccept
function as (POSIX-
)thread safe (as defined in Base Definitions, section 3.399 Thread Safe) by not listing it on System Interfaces, section 2.9.1 Thread Safety.Finally, as
glibc
just delegates on kernel'saccept()
, the most reputable source is the kernel source code (of course). This answer goes through kernel code path whenaccept()
ing: take a look and convince yourself that shared resources are protected by spin-locks, in particular the socket state and the queue of connections waiting for applicationaccept
ance.