How do goroutines work? (or: goroutines and OS threads relation)

ConcurrencyGoGoroutine

Concurrency Problem Overview


How can other goroutines keep executing whilst invoking a syscall? (when using GOMAXPROCS=1)
As far as I'm aware of, when invoking a syscall the thread gives up control until the syscall returns. How can Go achieve this concurrency without creating a system thread per blocking-on-syscall goroutine?

From the documentation:

> Goroutines > > They're called goroutines because the existing terms—threads, > coroutines, processes, and so on—convey inaccurate connotations. A > goroutine has a simple model: it is a function executing concurrently > with other goroutines in the same address space. It is lightweight, > costing little more than the allocation of stack space. And the stacks > start small, so they are cheap, and grow by allocating (and freeing) > heap storage as required. > > Goroutines are multiplexed onto multiple OS threads so if one should > block, such as while waiting for I/O, others continue to run. Their > design hides many of the complexities of thread creation and > management.

Concurrency Solutions


Solution 1 - Concurrency

If a goroutine is blocking, the runtime will start a new OS thread to handle the other goroutines until the blocking one stops blocking.

Reference : https://groups.google.com/forum/#!topic/golang-nuts/2IdA34yR8gQ

Solution 2 - Concurrency

Ok, so here's what I've learned: When you're doing raw syscalls, Go indeed creates a thread per blocking goroutine. For example, consider the following code:

package main

import (
        "fmt"
        "syscall"
)

func block(c chan bool) {
        fmt.Println("block() enter")
        buf := make([]byte, 1024)
        _, _ = syscall.Read(0, buf) // block on doing an unbuffered read on STDIN
        fmt.Println("block() exit")
        c <- true // main() we're done
}

func main() {
        c := make(chan bool)
        for i := 0; i < 1000; i++ {
                go block(c)
        }
        for i := 0; i < 1000; i++ {
                _ = <-c
        }
}

When running it, Ubuntu 12.04 reported 1004 threads for that process.

On the other hand, when utilizing Go's HTTP server and opening 1000 sockets to it, only 4 operating system threads were created:

package main

import (
        "fmt"
        "net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hi there, I love %s!", r.URL.Path[1:])
}

func main() {
        http.HandleFunc("/", handler)
        http.ListenAndServe(":8080", nil)
}

So, it's a mix between an IOLoop and a thread per blocking system call.

Solution 3 - Concurrency

It can't. There's only 1 goroutine that can be running at a time when GOMAXPROCS=1, whether that one goroutine is doing a system call or something else.

However, most blocking system calls, such as socket I/O, waiting for a timer are not blocked on a system call when performed from Go. They're multiplexed by the Go runtime onto epoll, kqueue or similar facilities the OS provides for multiplexing I/O.

For other kinds of blocking system calls that cannot be multiplexed with something like epoll, Go does spawn a new OS thread, regardless of the GOMAXPROCS setting (albeit that was the state in Go 1.1, I'm not sure if the situation is changed)

Solution 4 - Concurrency

You have to differentiate processor number and thread number: you can have more threads than physical processors, so a multi-thread process can still execute on a single core processor.

As the documentation you quoted explain, a goroutine isn't a thread: it's merely a function executed in a thread that is dedicated a chunk of stack space. If your process have more than one thread, this function can be executed by either thread. So a goroutine that is blocking for a reason or another (syscall, I/O, synchronization) can be let in its thread while other routines can be executed by another.

Solution 5 - Concurrency

I’ve written an article explaining how they work with examples and diagrams. Please feel free to take a look at it here: https://osmh.dev/posts/goroutines-under-the-hood

Solution 6 - Concurrency

From documentation of runtime:

> The GOMAXPROCS variable limits the number of operating system threads that can execute user-level Go code simultaneously. There is no limit to the number of threads that can be blocked in system calls on behalf of Go code; those do not count against the GOMAXPROCS limit.

so when one OS thread is blocked for syscall, another thread can be started - and GOMAXPROCS = 1 is still satisfied. The two threads are NOT running simultaneously.

Solution 7 - Concurrency

Hope this example of sum numbers helps you.

package main

import "fmt"

func put(number chan<- int, count int) {
	i := 0
	for ; i <= (5 * count); i++ {
		number <- i
	}
	number <- -1
}

func subs(number chan<- int) {
	i := 10
	for ; i <= 19; i++ {
		number <- i
	}
}

func main() {
	channel1 := make(chan int)
	channel2 := make(chan int)
	done := 0
	sum := 0

	//go subs(channel2)
	go put(channel1, 1)
	go put(channel1, 2)
	go put(channel1, 3)
	go put(channel1, 4)
	go put(channel1, 5)

	for done != 5 {
		select {
		case elem := <-channel1:
			if elem < 0 {
				done++
			} else {
				sum += elem
				fmt.Println(sum)
			}
		case sub := <-channel2:
			sum -= sub
			fmt.Printf("atimta : %d\n", sub)
			fmt.Println(sum)
		}
	}
	close(channel1)
	close(channel2)
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionomribahumiView Question on Stackoverflow
Solution 1 - ConcurrencyOneOfOneView Answer on Stackoverflow
Solution 2 - ConcurrencyomribahumiView Answer on Stackoverflow
Solution 3 - ConcurrencynosView Answer on Stackoverflow
Solution 4 - ConcurrencyElwinarView Answer on Stackoverflow
Solution 5 - ConcurrencyMistaOSView Answer on Stackoverflow
Solution 6 - ConcurrencyyyFredView Answer on Stackoverflow
Solution 7 - ConcurrencyDOOMSDAYXX21View Answer on Stackoverflow