Goroutines are cooperatively scheduled. Does that mean that goroutines that don't yield execution will cause goroutines to run one by one?

Issue

From: http://blog.nindalf.com/how-goroutines-work/

As the goroutines are scheduled cooperatively, a goroutine that loops continuously can starve other goroutines on the same thread.

Goroutines are cheap and do not cause the thread on which they are multiplexed to block if they are blocked on

  • network input
  • sleeping
  • channel operations or
  • blocking on primitives in the sync package.

So given the above, say that you have some code like this that does nothing but loop a random number of times and print the sum:

func sum(x int) {
  sum := 0
  for i := 0; i < x; i++ {
    sum += i
  }
  fmt.Println(sum)
}

if you use goroutines like

go sum(100)
go sum(200)
go sum(300)
go sum(400)

will the goroutines run one by one if you only have one thread?

Solution

A compilation and tidying of all of creker’s comments.

Preemptive means that kernel (runtime) allows threads to run for a specific amount of time and then yields execution to other threads without them doing or knowing anything. In OS kernels that’s usually implemented using hardware interrupts. Process can’t block entire OS. In cooperative multitasking thread have to explicitly yield execution to others. If it doesn’t it could block whole process or even whole machine. That’s how Go does it. It has some very specific points where goroutine can yield execution. But if goroutine just executes for {} then it will lock entire process.

However, the quote doesn’t mention recent changes in the runtime. fmt.Println(sum) could cause other goroutines to be scheduled as newer runtimes will call scheduler on function calls.

If you don’t have any function calls, just some math, then yes, goroutine will lock the thread until it exits or hits something that could yield execution to others. That’s why for {} doesn’t work in Go. Even worse, it will still lead to process hanging even if GOMAXPROCS > 1 because of how GC works, but in any case you shouldn’t depend on that. It’s good to understand that stuff but don’t count on it. There is even a proposal to insert scheduler calls in loops like yours

The main thing that Go’s runtime does is it gives its best to allow everyone to execute and don’t starve anyone. How it does that is not specified in the language specification and might change in the future. If the proposal about loops will be implemented then even without function calls switching could occur. At the moment the only thing you should remember is that in some circumstances function calls could cause goroutine to yield execution.

To explain the switching in Akavall’s answer, when fmt.Printf is called, the first thing it does is checks whether it needs to grow the stack and calls the scheduler. It MIGHT switch to another goroutine. Whether it will switch depends on the state of other goroutines and exact implementation of the scheduler. Like any scheduler, it probably checks whether there’re starving goroutines that should be executed instead. With many iterations function call has greater chance to make a switch because others are starving longer. With few iterations goroutine finishes before starvation happens.

Answered By – m0meni

Answer Checked By – Pedro (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.