It is alway a good surprise when a task you thought being rather hard turns out to be quite easy when you actually do it. Using select for I/O multiplexing and stuff like this is one of those pleasant surprise. The man page could be quite intimidating, therefore I start with an example. Suppose you are dealing with network communication (or any other form of interaction where an I/O operation could take too long to be correct). You are likely to read (or write) into a file descriptor (previously opened via socket and then bound in some way) AND to check for a timeout. If the operation is taking too long, you want to bail out of the read operation and perform the needed action.If you are stuck with standard read and timer operations you may need to set up some signaling check for the right thread to catch them and so on. But there is a better way.
Select accepts several arguments: a limit, three sets of file descriptors and a timeout, and returns as soon as one of the conditions (defined by arguments) is met. The file descriptor sets are defined via fd_set type (handled with fellow macros FD_SET, FD_ISSET, FD_ZERO and FD_CLR). All these arguments can either be NULL or point to a fd_set. The first one is the set of file descriptors checked for non blocking read. That is that if one of the file descriptors contained in this sets become ready to be read without blocking the caller, then select returns. The next argument is for writable file descriptors and the third one is filedescriptors that have to be checked for errors (exceptions).
The first argument is the maximum filedescriptor contained in the union of the three sets plus one. This serves as a limit to avoid checking the whole range of file descriptor.
The last argument is a timeout. It is a struct timeval (the same filled in by gettimeofday) that can define timeouts with a microsecond resolution. In practice the resolution is much less fine grained than that and depends on the kernel and the architecture. For example on Linux kernel 2.4 on ARM the resolution is 10ms. Better check the smallest handled timeout before blindly relying on it.
Select returns -1 in case of error, 0 in case of timeout or the number of the filedescriptors that changed status in the three sets.
For the example the return code is easily processed, while for more convoluted cases could be more complex.
Let’s take another example, suppose you are reading audio packets from a stream and you want to decode and playback them. The first approach to this problem could be using two threads with a coupling buffer. One thread reads packets and pushes them into the buffer and the other thread pops the packets out of the buffer and sends them to the audio driver. This is conceptually simple, but not straightforward to do in the right way. When dealing with threading you always have to synchronize them. It is likely that you need a third thread to control the streamer and the player threads.
If you employ select the solution is very simple and natural. Just check the wall clock and compute a timeout for the next play, then wait with select either for a new incoming packet or the time to play.
In this case there is just one thread and the warrant that if you are reading the buffer no one is writing in it. This allows you to simplify the buffer management.
If you are not so lucky to work with Linux, but your daunting task is to earn a living with Windows the good news is that a similar function is available for Microsoft platforms.