If you really wanted to create a new thread for each request, you could just do that. Just put the accept
call into the thread function, rather in your event loop. If the accept
succeeds, the thread function should then spawn a new thread (which will accept the next request) before processing the request. There may be no need for join
in this scenario; in a simple implementation, the threads would be detach
ed.
But you're likely to find that the result is suboptimal, for several reasons:
There's no way to control the number of threads created.
Creating a thread and a request processor for each request is a lot of overhead.
A more common implementation strategy, which is still quite simple, is to spawn a predetermined number of threads, each of which executes a loop which accept
s and processes a request. (This is safe because accept()
is thread-safe; each thread will be handed a different connection. But you can't assume that every accept
will succeed without blocking, or succeed at all.)
That's usually called a thread pool and you'll find a variety of library implementations for production purposes. But rolling your own is not too complicated, and it's a good learning experience.
You don't necessarily need to spawn all of the threads at once. If you used some kind of shared data (like a semaphore) to count the number of active threads, you could let each thread decide whether to spawn a new thread after a successful accept
. This strategy will be useful if threads might terminate prematurely (and see below).
A naive thread pool might also be suboptimal, though. Things you might want to watch out for, for a more sophisticated strategy, are:
Bugs in request processors which fail to release resources. ("Memory leaks" are a prime example, but there are other resources as well.) A server must be able to run forever and resource leaks will slowly bring it to its knees. One strategy to avoid this problem is to let each thread die after it has processed some number of requests.
This solution depends on the operating system (or thread library) to fairly distribute load between the competing threads. That might not be a realistic expectation, and you might find that under high load, some cores are oversubscribed and others are remaining idle. Some servers implement their own schedulers to solve this problem.
For some possibly interesting reading, you might want to take a look at:
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…