Using threads is one way, providing each thread only accesses a single port, and has only thread-local data.
But if there is any shared data, say reading from one port and writing to another, then threads will
be harmful to your code. Debugging massively threaded code which doesn't have a solid design and poor use of say semaphores to protect access to shared data will be a nightmare.
http://www.linuxmanpages.com/man2/select_tut.2.php
http://www.linuxmanpages.com/man2/select.2.php
The select() system call offers a way to monitor many file descriptors at the same time.
Essentially, when something interesting happens, select() returns, and then you can deal with it.