Thread complexity issues
Although threads are very appropriate for some system designs, it's important to respect the Pandora's box of complexities their use unleashes.
In some ways, it's ironic that while MMU-protected multitasking has become common, computing fashion has made popular the use of multiple threads in an unprotected address space. This not only makes debugging difficult, but also hampers the generation of reliable code.
Threads were initially introduced to UNIX systems as a
lightweight
concurrency mechanism to address
the problem of slow context switches between
heavyweight
processes. Although this is a worthwhile goal,
an obvious question arises: Why are process-to-process
context switches slow in the first place?
Architecturally, the OS addresses the context-switch performance issue first. In fact, threads and processes provide nearly identical context-switch performance numbers. The QNX OS's process-switch times are faster than UNIX thread-switch times. As a result, QNX OS threads don't need to be used to solve the IPC performance problem; instead, they're a tool for achieving greater concurrency within application and server processes.
Without resorting to threads, fast process-to-process context switching makes it reasonable to structure an application as a team of cooperating processes sharing an explicitly allocated shared-memory region. An application thus exposes itself to bugs in the cooperating processes only so far as the effects of those bugs on the contents of the shared-memory region. The private memory of the process is still protected from the other processes. In the purely threaded model, the private data of all threads (including their stacks) is openly accessible, vulnerable to stray pointer errors in any thread in the process.
Nevertheless, threads can also provide concurrency
advantages that a pure process model cannot address. For
example, a filesystem server process that executes requests
on behalf of many clients (where each request takes
significant time to complete), definitely benefits from
having multiple threads of execution. If one client process
requests a block from disk, while another client requests a
block already in cache, the filesystem process can utilize a
pool of threads to concurrently service these requests,
rather than remain busy
until the disk block
is read for the first request.
As requests arrive, each thread can respond directly
from the buffer cache or it can block and wait for disk I/O without
increasing the response latency seen by other client
processes. The filesystem server can
precreate
a team of threads, ready to respond
in turn to client requests as they arrive. Although this
complicates the architecture of the filesystem manager, the
gains in concurrency are significant.