Is accept() thread-safe?
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. 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.
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
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.
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.
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.
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 accept
ance.
精彩评论