If goroutines involve userspace threads, can a blocking operation leads to context switch of the entire thread?


Apologies if this question is too stupid. I was reading through the details of goroutines Here. According to that page, it says Goroutines are multiplexed onto a small number of OS threads, rather than a 1:1 mapping by which, all I could think of with my limited knowledge was, there are a limited number of OS threads spawned, inside which, it may be using userspace threads or coroutines. Is this correct? And if so, if I may take an example, if a program clones 4 OS threads inside which there are multiple userspace threads, and there happen to be a single blocking operation inside all these 4 threads along with non-blocking operations, will the OS scheduler context-switch all these threads, as the userspace threads are not transparent to the OS threads?

Out of curiosity, is there a possible C implementation of goroutines, which could help understand the internals?


Below is what I understood after reading Go in Action

Goroutines run within what are called “logical processors”(NOT physical processors). Each of these logical processors are bound to a single OS thread.

After Go 1.5, the number of logical processors equals the number of available physical processors.

The Go scheduler intelligently schedules the running of multiple goroutines on each of these logical processors

Crude diagram follows :-

OS Thread —— Logical Processor —— Goroutine 1, Goroutine 2….. Goroutine n

Now, it is very likely that one of the Goroutines makes a blocking system call. When this happens,

  1. The OS thread and the Goroutine that made the blocking call are
    detached from the logical processor

    This logical processor now has no OS thread.

  2. The Go scheduler creates a new OS thread, and attaches it to the logical processor. The remaining goroutines that were attached to the logical processor, now continue to run.

  3. The detached goroutine and the OS thread it is associated with continue to block, waiting for the syscall to return.

  4. When the system call returns, the goroutine is re-attached to one of the logical processors, and is placed in its run queue.

  5. The OS thread is “put aside for future use”. I am guessing it is added to some sort of thread pool.

If the goroutine makes a Network I/O call, it is handled in slightly different way.

The goroutine is detached from the logical processor, and is moved to the integrated network poller. Once the poller says the I/O operation is ready, the goroutine is re-attached to the logical processor to handle it.

Now, to answer your question 🙂

I’m not an expert, but this is what I think will happen, based on what was stated above.

Since one goroutine on each of the 4 OS threads have made a blocking syscall, all 4 threads will be detached from their logical processors, and will continue to block until the syscall(s) return. The 4 OS threads will be associated with the respective goroutines that made the blocking syscall.

Now, this results in 4 logical processors(and the non-blocking goroutines attached to them) without any OS threads.

So, the GO scheduler creates 4 new OS threads, and assigns the logical processors to these threads.

From the OS’s point of view, the 4 OS threads that made blocking calls obviously cannot be allowed to take CPU time, since they aren’t doing anything.

So it will switch their contexts with some other non-blocking thread of its choosing.

Answered By – kkaosninja

Answer Checked By – Mary Flores (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.