Accept() 是线程安全的吗?

发布于 2024-10-19 05:14:53 字数 641 浏览 7 评论 0原文

我目前正在用 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

黄昏下泛黄的笔记 2024-10-26 05:14:53

是的。这是设计多线程服务器的常用方法和公认的设计实践。

您还可以多次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 call accept, this will allow you to do multithreading without needing a threads library. Older servers do this.

御弟哥哥 2024-10-26 05:14:53

由于此内容已获得奖励,请求参考:

是的,accept() 是线程安全的,正如 POSIX 定义该术语一样。

相关参考文献为 POSIX.1 的第 2.9.1 节,当前版本表示:

本卷 POSIX.1-2017 定义的所有函数均应
线程安全,但以下函数不需要
线程安全。

[不包含 accept() 的列表]

为了完整起见,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:

All functions defined by this volume of POSIX.1-2017 shall be
thread-safe, except that the following functions need not be
thread-safe.

[a list that does not include accept()]

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.

单挑你×的.吻 2024-10-26 05:14:53

在评论中,@Rick(赏金提供者)说:

在某些方面我现在明白了。线程安全将是满足该行为的属性。我认为线程安全必须是同一进程中的线程。但现在我认为同一进程内的线程之间或不同进程之间的线程之间没有太大差异。在某些方面它们是相同的。所以线程安全的概念可以应用于这两种场景。

关于线程安全的 POSIX 定义确实指的是同一进程中的线程(请参阅:§2.9)。

如果您询问 fork() 之后会发生什么,以及父级和子级同时调用 accept() 是否安全,我们首先注意到 POSIX 定义称为连接指示队列的系统资源。然后我们注意到 fork()< /a> 获取父级描述符的副本,因此子级和父级将访问相同的连接指示队列(就像文本文件的重复文件描述符将访问同一文件一样)。

此时,accept() 对每个进程(子进程和父进程)执行的操作的定义是相同的。

In comments, @Rick (the bounty offerer) says:

In some ways I get the point now. thread-safe would be the property that meets the behaviour. I was thinking thread-safe has to be threads within same process. But now I think there aren't much differences between threads within same process or threads between different processes. In some ways they are the same. So the concept of thread-safety can be applied to both scenarios.

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 into accept(), we note first that POSIX defines a system resource known as a connection indication queue. Then we note that the child of fork() 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.

快乐很简单 2024-10-26 05:14:53

应用程序链接到系统上的 libc 实现,以便调用 accept() 和其他与套接字相关的函数(#include) )。您想阅读其文档。

Linux 上最常见的 libc 实现来自 GNU(或者 Android 上的 Google bionic),它被称为 glibc,它非常有用。可能您正在(将)使用什么。如接受文档 对于 glibc:

函数:int Accept(int socket, struct sockaddr *addr, socklen_t *length_ptr)

初步:| MT-安全 | AS-安全 |交流安全 fd | 请参阅 POSIX 安全概念。

如中所述POSIX 安全概念初步部分列举了以下属性:

根据 POSIX 标准中针对线程安全、异步信号安全和异步取消安全等安全上下文的标准进行评估。

下面是对这些概念的解释(另外,请检查维基百科上的“线程安全”以了解不同的方法实现线程安全)。根据文档,accept 被声明为 MT-Safe

MT 安全或线程安全函数在存在其他线程的情况下可以安全地调用。 MT,在MT-Safe中,代表多线程。

MT 安全并不意味着函数是原子的,也不意味着它使用 POSIX 向用户公开的任何内存同步机制。甚至有可能按顺序调用 MT-Safe 函数不会产生 MT-Safe 组合。例如,让一个线程依次调用两个 MT 安全函数并不能保证相当于这两个函数组合的原子执行的行为,因为其他线程中的并发调用可能会以破坏性方式进行干扰。

跨库接口内联函数的整个程序优化可能会暴露不安全的重新排序,因此不建议跨 GNU C 库接口执行内联。在整个程序优化下,无法保证记录的 MT 安全状态。但是,用户可见标头中定义的函数被设计为可以安全内联。

事实上,glibcaccept 实现只是重定向到内核系统调用,这使得此描述对于 Linux 上的其他 libc 实现也很有用。系统(也可能只是执行到系统调用的重定向)。


另一方面,更通用的方法是检查 man-pages 项目<如果可用的话,您系统上的 /code>(在大多数系统上最接近官方文档),其中:

[...] 记录了用户空间程序使用的 Linux 内核和 C 库接口。对于 C 库,主要焦点是 GNU C 库 (glibc),尽管在已知的情况下,还包括可用于 Linux 的其他 C 库的变体文档。

通过输入 man 2 Accept在命令行上:

[...]
符合

accept(): POSIX.1-2001, POSIX.1-2008, SVr4, 4.4BSD (首先接受()
出现在 4.2BSD 中)。

我们看到 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 call accept() 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 maybe bionic from Google on Android), it is called glibc and it is very likely what you are (will be) using. As stated in accept documentation for glibc:

Function: int accept (int socket, struct sockaddr *addr, socklen_t *length_ptr)

Preliminary: | MT-Safe | AS-Safe | AC-Safe fd | See POSIX Safety Concepts.

As explained in POSIX Safety concepts, the Preliminary section enumerates properties which:

are assessed according to the criteria set forth in the POSIX standard for such safety contexts as Thread-, Async-Signal- and Async-Cancel- -Safety.

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:

MT-Safe or Thread-Safe functions are safe to call in the presence of other threads. MT, in MT-Safe, stands for Multi Thread.

Being MT-Safe does not imply a function is atomic, nor that it uses any of the memory synchronization mechanisms POSIX exposes to users. It is even possible that calling MT-Safe functions in sequence does not yield an MT-Safe combination. For example, having a thread call two MT-Safe functions one right after the other does not guarantee behavior equivalent to atomic execution of a combination of both functions, since concurrent calls in other threads may interfere in a destructive way.

Whole-program optimizations that could inline functions across library interfaces may expose unsafe reordering, and so performing inlining across the GNU C Library interface is not recommended. The documented MT-Safety status is not guaranteed under whole-program optimization. However, functions defined in user-visible headers are designed to be safe for inlining.

The fact that glibc's accept implementation just redirect to the kernel system call, makes this description a useful one also for other libc 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:

[...] documents the Linux kernel and C library interfaces that are employed by user-space programs. With respect to the C library, the primary focus is the GNU C library (glibc), although, where known, documentation of variations on other C libraries available for Linux is also included.

By typing man 2 accept on the command line:

[...]
CONFORMING TO

accept(): POSIX.1-2001, POSIX.1-2008, SVr4, 4.4BSD (accept() first
appeared in 4.2BSD).

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 specifies accept 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's accept(), the most reputable source is the kernel source code (of course). This answer goes through kernel code path when accept()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 application acceptance.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文