There are many ways to schedule work in the linux kernel: timers, tasklets, work queues, and kernel threads. What are the guidelines for when to use one vs another?
softirqs : deferred work runs in interrupt context tasklets : deferred work runs in interrupt context work queues : deferred work runs in process context softirqs : cannot run simultaneously on different CPU's tasklets : cannot run simultaneously on different CPU's work queues : can run simultaneously on different CPU's softirqs : cannot go to sleep tasklets : cannot go to sleep work queues : can go to sleep softirqs : cannot be preempted/schedule tasklets : cannot be preempted/schedule work queues : maybe be preempted/schedule softirqs : not easy to use tasklets : easy to use work queues : easy to use
Kernel threads form the basis for Work Queues. They are the only types of kernel helper routines that run within the process context.
As you said, it depends on the task at hand:
Work queues defer work into a kernel thread - your work will always run in process context. They are schedulable and can therefore sleep.
Normally, there is no debate between work queues or sotftirqs/tasklets; if the deferred work needs to sleep, work queues are used, otherwise softirqs or tasklets are used. Tasklets are also more suitable for interrupt handling (they are given certain assurances such as: a tasklet is never ran later than on the next tick, it's always serialized with regard to itself, etc.).
Kernel timers are good when you know exactly when you want something to happen, and do not want to interrupt/block a process in the meantime. They run outside process context, and they are also asynchronous with regard to other code, so they're the source of race conditions if you're not careful.
Hope this helps.