I mostly agree with Gian's answer, but I have different interpretations of a few concurrency primitives. Note that these terms are often used inconsistently by different authors. These are my favorite definitions (hopefully not too far from the modern consensus).
- Process:
- OS-managed
- Each has its own virtual address space
- Can be interrupted (preempted) by the system to allow another process to run
- Can run in parallel with other processes on different processors
- The memory overhead of processes is high (includes virtual memory tables, open file handles, etc)
- The time overhead for creating and context switching between processes is relatively high
- Threads:
- OS-managed
- Each is "contained" within some particular process
- All threads in the same process share the same virtual address space
- Can be interrupted by the system to allow another thread to run
- Can run in parallel with other threads on different processors
- The memory and time overheads associated with threads are smaller than processes, but still non-trivial
- (For example, typically context switching involves entering the kernel and invoking the system scheduler.)
- Cooperative Threads:
- May or may not be OS-managed
- Each is "contained" within some particular process
- In some implementations, each is "contained" within some particular OS thread
- Cannot be interrupted by the system to allow a cooperative peer to run
- (The containing process/thread can still be interrupted, of course)
- Must invoke a special yield primitive to allow peer cooperative threads to run
- Generally cannot be run in parallel with cooperative peers
- (Though some people think it's possible: http://ocm.dreamhosters.com/.)
- There are lots of variations on the cooperative thread theme that go by different names:
- Fibers
- Green threads
- Protothreads
- User-level threads (user-level threads can be interruptable/preemptive, but that's a relatively unusual combination)
- Some implementations of cooperative threads use techniques like split/segmented stacks or even individually heap-allocating every call frame to reduce the memory overhead associated with pre-allocating a large chunk of memory for the stack
- Depending on the implementation, calling a blocking syscall (like reading from the network or sleeping) will either cause a whole group of cooperative threads to block or implicitly cause the calling thread to yield
- Coroutines:
- Some people use "coroutine" and "cooperative thread" more or less synonymously
- I do not prefer this usage
- Some coroutine implementations are actually "shallow" cooperative threads; yield can only be invoked by the "coroutine entry procedure"
- The shallow (or semi-coroutine) version is easier to implement than threads, because each coroutine does not need a complete stack (just one frame for the entry procedure)
- Often coroutine frameworks have yield primitives that require the invoker to explicitly state which coroutine control should transfer to
- Generators:
- Restricted (shallow) coroutines
- yield can only return control back to whichever code invoked the generator
- Goroutines:
- An odd hybrid of cooperative and OS threads
- Cannot be interrupted (like cooperative threads)
- Can run in parallel on a language runtime-managed pool of OS threads
- Event handlers:
- Procedures/methods that are invoked by an event dispatcher in response to some action happening
- Very popular for user interface programming
- Require little to no language/system support; can be implemented in a library
- At most one event handler can be running at a time; the dispatcher must wait for a handler to finish (return) before starting the next
- Makes synchronization relatively simple; different handler executions never overlap in time
- Implementing complex tasks with event handlers tends to lead to "inverted control flow"/"stack ripping"
- Tasks:
- Units of work that are doled out by a manager to a pool of workers
- The workers can be threads, processes or machines
- Of course the kind of worker a task library uses has a significant impact on how one implements the tasks
- In this list of inconsistently and confusingly used terminology, "task" takes the crown. Particularly in the embedded systems community, "task" is sometimes used to mean "process", "thread" or "event handler" (usually called an "interrupt service routine"). It is also sometimes used generically/informally to refer to any kind of unit of computation.
One pet peeve that I can't stop myself from airing: I dislike the use of the phrase "true concurrency" for "processor parallelism". It's quite common, but I think it leads to much confusion.
For most applications, I think task-based frameworks are best for parallelization. Most of the popular ones (Intel's TBB, Apple's GCD, Microsoft's TPL & PPL) use threads as workers. I wish there were some good alternatives that used processes, but I'm not aware of any.
If you're interested in concurrency (as opposed to processor parallelism), event handlers are the safest way to go. Cooperative threads are an interesting alternative, but a bit of a wild west. Please do not use threads for concurrency if you care about the reliability and robustness of your software.