select() is a great system call. You can pack any number of file descriptors, socket descriptors, pipes, etc. and get notified in a synchronous fashion when input becomes availa
Use the timeout parameter - keep your timer events in a priority queue, check the top item and set the timeout accordingly - if the timeout is reached, then you can check that the event is ready to run, run the event and continue.
At least that's what I do.
Note that poll has a nicer interface (in some ways) and may be more efficient with lots of file descriptors.