How Go runtime checks if goroutine is blocked?

Issue

Go docs says that:

When a coroutine blocks, such as by calling a blocking system call, the run-time automatically moves other coroutines on the same operating system thread to a different, runnable thread so they won’t be blocked

But how does runtime detect that goroutine is blocked?

For example if I will run calculation in one of go-routine will it be evaluated as blocking operation?

package main

import (
    "fmt"
    "runtime"
)

func f(from string, score int) {
    for i := 0; i < score; i++ {
            for z := 0; z < score; z++ {
        }
    }

    fmt.Println(from, " Done")
}   

func main() {
runtime.GOMAXPROCS(1)
f("direct", 300000)
go f("foo", 200000)
go f("bar", 20000)
go f("baz", 2000)

 go func(msg string) {
        fmt.Println(msg)
    }("going without loop")

       var input string
    fmt.Scanln(&input)
    fmt.Println("done")
}

I am getting result: baz, boo bar. But why? Does Go understand that foo is blocking?

Solution

This ticket is relevant to the questions:

https://github.com/golang/go/issues/11462

Each blocking call you could do, will be served by the runtime. So the runtime knows if something happens which could block.

For example:

  1. If you call Lock() on a sync.Mutex the runtime will handle that and checks wether that would block or not and act accordingly.

  2. If you call Run() or Output() (or the like) on a exec.Cmd the runtime notices that and assumes, that this call will block. It can’t know wether the program you are running will block, so it has to assume the worst.

To the best of my knowledge, there are two principle mechanisms, how a goroutine could block and the examples above are examples for each one.

  1. Is an example for things the runtime provides to you without “external” help.

  2. Is an example for things were a syscall is involved. Golang on linux for example doesn’t use gnu libc and implements the syscalls it needs directly by calling the os. All those calls are going through package syscall (to the best of my knowledge) and here the runtime has a single hook to get notified of what happens.

Of course the picture is a bit muddy as it might be, that golang needs a mutex from the os for cross os thread synchronization even for 1. and then it is also somehow a bit of example 2.

Regarding the code in the question: No. Go doesn’t understand that f might take a lot of time, if the loops aren’t optimised away by the compiler. And in such a tight loop, the go scheduler can’t “stop” the goroutine and set another as running, as there is no preemption point in the loop. So if you have mulitple of such goroutines doing tight loops
without preemption points, they might eat up your cpus and all other goroutines have to wait, until at least one of the tight loops is done.
But only calling a different function in that loop changes that picture,
as a function call is a preemption point.

user1432751 asked in a comment:

what happens with CPU registers when blocking operation is happen?
Current thread is blocking by goroutines, Go scheduler create new
system thread and migrate all other threads there? –

The go schedule isn’t preemptive (at least that was the state last time I checked) but schedules at certain preemption points. For example syscalls, sends and waits on channels and if I remember correctly function calls. So at those points an internal function is called and the cpu registers relevent to the code executing in the goroutine are already on the stack, when the scheduler decides what to do.

And yes, if a syscall is done and therefore there is the danger of a blocked os thread, the goroutine doing the syscall gets its own os thread, that doesn’t even count to GOMAXPROCS.

Answered By – typetetris

Answer Checked By – David Goodson (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.