You may consider a simple queuing system to allow for the input/output operations to working in a disconnected way. Then your render thread would be able to update and paint while still waiting on user/system input. This would also allow you to push data processing to yet another queue to reduce contention more.
The number of optimal threads depends on the application and load. Too many threads and you could run into an issue with deadlocking and switching contention. Too few and you become I/O bound.
Deadlock - Resource contention causes the application to run slower due to different threads fighting over limited resources (I/O or Data)
Switching Contention - The amount of time the applications (or operating system) swaps/preempts threads is greater than the time spent processing I/O or Data
I/O Bound - Threads ending up in a blocked/wait state while resources are in use.
On modern hardware switching contention probably isn't an issue unless you prioritize your threads in a bad way. (Give long blocking threads higher priority.)
I/O bound is typically less of an issue than deadlocks and may typically be reduced by increasing the number of available threads.
Deadlocks are by far the hardest to solve. The simplest way would probably be to use immutable data structures and preempt a call to shared resources that would cause a deadlock. The reason they are so hard to fix is that different parts of the resources are blocked by different threads and could require each of the threads to toss out their current data and restart. (These restarts also cause the application to waste time reprocessing data.)