DiplomateProgrammer
DiplomateProgrammer

Reputation: 169

Why do coroutines need less memory than threads?

It is often said that coroutines could be thought of as "light threads" because they consume less memory. However, I wasn't able to find any explanation on why exactly that is the case. What do threads store that coroutines do not? Especially if we're talking about stackful coroutines which still need their own stack just like threads do.

Upvotes: 2

Views: 82

Answers (1)

Solomon Slow
Solomon Slow

Reputation: 27190

What kind of data structures are needed to support threads?

Here's an off-hand, incomplete list, pulled out of my... Uh,... memories of various different operating systems.

Per-thread data:

All of the following may be bundled into a single "Thread" object that lives in kernel space, and which could be a member of, or could be referenced by a slot in a global "thread table."

  1. The thread's context. Whenever a thread is suspended, its context contains all of the values that must be loaded into hardware registers in order to resume running it.

  2. The thread's state. Typically, the state either is running if it actually is running, or runnable if the only thing that prevents it from running is the lack of an available CPU, or it's blocked by something. If it's blocked, then the state probably contains a pointer to the descriptor for whatever queue (see below) eventually will unblock it. Depending on the OS, there may be other possible states such as, states that support debugging.

  3. A reference to a "parent thread" or, to a "process," or both. In most desktop/server/mobile operating systems, every thread belongs to a process, and there will be places in the OS code where, having found a reference to a thread, it is necessary to find the process to which the thread belongs.

  4. Other attributes: Threads may have "priorities," "base priorities," "nice values," "names," "processor affinities," ... whatever else meets the needs of the particular operating system.

Queues:

Whenever a thread is not running, then it must be waiting for something, and in the general case, there may be other threads awaiting the same thing. References to the waiting threads often are kept in queues, which also are kernel-space objects.

  1. A global run queue contains references to all of the runnable threads in the system

  2. Queues for blocked threads: A mutex will contain a queue of threads that are waiting to lock the mutex, Synchronization objects with other names, "mail slots," "semaphores," "events," "condition variables," etc. They all have queues of threads that are waiting for one reason or another.

The "queues" in most cases are more than just strict FIFO queues because (a) it often is necessary for high priority threads to be bumped to the head of a queue, and (b) it sometimes is necessary to remove a thread from the middle of a queue (e.g., if the process that owns it is peremptorily killed.)

Call Stack:

In virtually every operating system, a thread is expected to use the CPU's "call" and "return" instructions, if only when calling in to the operating system, but in most cases, also to support the programming language of the thread's code. Those call and return instructions implicitly use a dedicated call stack.

Unlike everything else mentioned above, the call stack is allocated in the owning process's virtual address space (a.k.a., in "user space.") Depending on the operating system, allocating the call stack for a new thread, and disposing of the call stack for a dead thread could either be the responsibility of the user-mode code that requested the new thread, or it could be done by kernel code.

Either way, the call stack probably is the single largest chunk of memory associated with the thread.

Upvotes: 1

Related Questions