Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why are OS threads considered expensive?

There are many solutions geared toward implementing "user-space" threads. Be it golang.org goroutines, python's green threads, C#'s async, erlang's processes etc. The idea is to allow concurrent programming even with a single or limited number of threads.

What I don't understand is, why are the OS threads so expensive? As I see it, either way you have to save the stack of the task (OS thread, or userland thread), which is a few tens of kilobytes, and you need a scheduler to move between two tasks.

The OS provides both of this functions for free. Why should OS threads be more expensive than "green" threads? What's the reason for the assumed performance degradation caused by having a dedicated OS thread for each "task"?

like image 915
Chi-Lan Avatar asked Apr 01 '12 13:04

Chi-Lan


People also ask

Is thread creation expensive?

Java thread creation is expensive because there is a fair bit of work involved: A large block of memory has to be allocated and initialized for the thread stack. System calls need to be made to create / register the native thread with the host OS.

Are processes more expensive than threads?

Inter-process communication is slow as processes have different memory addresses. Inter-thread communication can be faster than inter-process communication because threads of the same process share memory with the process they belong to. Context switching between processes is more expensive.

How expensive is a thread?

However, prices of a ThreadLift treatment usually range from $1,500 to $4,500, with the average cost of a ThreadLift coming in at $3,000. The final cost of your ThreadLift procedure may depend on the following factors: Your provider.

Why are threads cheaper than processes?

Thread switching is a type of context switching from one thread to another thread in the same process. Thread switching is very efficient and much cheaper because it involves switching out only identities and resources such as the program counter, registers and stack pointers.


2 Answers

I want to amend Tudors answer which is a good starting point. There are two main overheads of threads:

  1. Starting and stopping them. Involves creating a stack and kernel objects. Involves kernel transitions and global kernel locks.
  2. Keeping their stack around.

(1) is only a problem if you are creating and stopping them all the time. This is solved commonly using thread pools. I consider this problem to be practically solved. Scheduling a task on a thread pool usually does not involve a trip to the kernel which makes it very fast. The overhead is on the order of a few interlocked memory operations and a few allocations.

(2) This becomes important only if you have many threads (> 100 or so). In this case async IO is a means to get rid of the threads. I found that if you don't have insane amounts of threads synchronous IO including blocking is slightly faster than async IO (you read that right: sync IO is faster).

like image 187
usr Avatar answered Oct 02 '22 18:10

usr


Saving the stack is trivial, no matter what its size - the stack pointer needs to be saved in the Thread Info Block in the kernel, (so usualy saving most of the registers as well since they will have been pushed by whatever soft/hard interrupt caused the OS to be entered).

One issue is that a protection level ring-cycle is required to enter the kernel from user. This is an essential, but annoying, overhead. Then the driver or system call has to do whatever was requested by the interrupt and then the scheduling/dispatching of threads onto processors. If this results in the preemption of a thread from one process by a thread from another, a load of extra process context has to be swapped as well. Even more overhead is added if the OS decides that a thread that is running on another processor core than the one handling the interrupt mut be preempted - the other core must be hardware-interrupted, (this is on top of the hard/soft interrupt that entred the OS in the first place.

So, a scheduling run may be quite a complex operation.

'Green threads' or 'fibers' are, (usually), scheduled from user code. A context-change is much easier and cheaper than an OS interrupt etc. because no Wagnerian ring-cycle is required on every context-change, process-context does not change and the OS thread running the green thread group does not change.

Since something-for-nothing does not exist, there are problems with green threads. They ar run by 'real' OS threads. This means that if one 'green' thread in a group run by one OS thread makes an OS call that blocks, all green threads in the group are blocked. This means that simple calls like sleep() have to be 'emulated' by a state-machine that yields to other green threads, (yes, just like re-implementing the OS). Similarly, any inter-thread signalling.

Also, of course, green threads cannot directly respond to IO signaling, so somewhat defeating the point of having any threads in the first place.

like image 43
Martin James Avatar answered Oct 02 '22 18:10

Martin James