This is the 29th day of my participation in the August More Text Challenge

Goroutine with thread

Growable stack

Each OS thread has a fixed amount of stack memory (typically 2MB), an area of stack memory used to hold local variables in functions that are executing or temporarily suspended during other function calls. This fixed stack size is both too large and too small. The 2MB stack is a huge waste for a small Goroutine, such as a Goroutine that just waits for a WaitGroup to close a channel. It is not uncommon for Go programs to create a hundred thousand or so goroutines at a time, for which the stack is too large. In addition, for the most complex and deeply recursive functions, a fixed-size stack is never large enough. Changing this fixed size can improve space efficiency and allow more threads to be created, or it can allow deeper recursive functions, but not both

By contrast, a Goroutine begins its life with a very small stack, typically 2KB. Like OS threads, Goroutine’s stack is used to hold local variables in functions that are executing or temporarily suspended. Unlike OS threads, however, Goroutine’s stack is not fixed size and can be enlarged and shrunk as needed. Goroutine’s stack size limit can be up to 1GB, orders of magnitude higher than the typical fixed-size stack for threads. Of course, very few Goroutines use such a large stack

Goroutine scheduling

OS threads are scheduled by the OS kernel. Every few milliseconds, a hardware clock interrupts to the CPU, which calls a kernel function called the scheduler. This function suspends the currently running thread, saves its register information to memory, looks at the thread list and decides which thread to run next, retrieves the thread registry information from memory, and resumes execution of the selected thread. Because OS threads are scheduled by the kernel, controlling permissions from one thread to another requires a full context switch: saving the state of one thread to memory, restoring the state of another thread, and finally updating the data structure of the scheduler. Considering the memory locality involved and the number of memory accesses involved, as well as the increase in the number of CPU cycles required to access the memory, this operation is actually slow

The Go runtime includes its own scheduler, which uses a technique called M: N scheduling (because it can reuse/schedule m Goroutines to N OS threads). The Go scheduler works similarly to the kernel scheduler, but the Go scheduler only cares about goroutine scheduling for a single Go program

Unlike the operating system’s threaded scheduler, the Go scheduler is not triggered periodically by a hardware clock, but by specific Go language constructs. For example, when a goroutine calls time.sleep or is blocked by a channel or operates on a mutex, the scheduler puts the goroutine into sleep mode and runs other goroutines until the previous one can be reawakened. Because it does not need to switch to a kernel context, calling a Goroutine is much cheaper than scheduling a thread

GOMAXPROCS

The Go scheduler uses the GOMAXPROCS parameter to determine how many OS threads are needed to execute the Go code simultaneously. The default is the number of cpus on the machine, so on a machine with eight cpus, the scheduler will schedule the Go code to eight OS threads simultaneously. (GOMAXPROCS is n in m:n scheduling.) Goroutines that are sleeping or are blocked by channel traffic do not need to occupy threads. Goroutine that blocks in IO and other system calls or calls functions not written in Go requires a separate OS thread, but this thread is not counted in GOMAXPROCS

This parameter can be explicitly controlled using the GOMAXPROCS environment variable or the Runtime. GOMAXPROCS function. You can see the effect of GOMAXPROCS with a small program that outputs zeros and ones endlessly

for {
    go fmt.Print(0)
    fmt.Print(1)
}

$ GOMAXPROCS=1 go run hacker-cliche.go
111111111111111111110000000000000000000011111

$ GOMAXPROCS=2 go run hacker-cliche.go
01010101010101010101100110010101110010100110..
Copy the code

On the first run, at most one Goroutine can run at a time. It starts with the main goroutine, which outputs after some time. The Go scheduler puts the main Goroutine to sleep and wakes up another Goroutine with an output of 0 to give it a chance to execute. On the second run, there are two OS threads available, so both Goroutines can run at the same time, printing two numbers at the same rate. It is important to note that many factors affect Goroutine scheduling and the runtime is constantly evolving, so your results may differ from those shown above

You can also check the number of cpus by using runtime.numcpu ()

Goroutine has no logo

In most operating systems and programming languages that support multithreading, the current thread has a unique identifier, usually an integer or pointer. This feature makes it easy to build a thread’s local store, which is essentially a global map with the thread’s identity as the key so that each thread can store and retrieve values independently of other threads

Goroutine has no identity that programmers can access. This is by design, because thread-local storage tends to be abused. For example, when a Web server is implemented in a language that supports thread-local storage, many functions access the store to find information about HTTP requests. But like programs that rely too much on global variables, this can lead to an unhealthy form of “action at a distance,” where a function behaves not only by its arguments but also by the identity of the thread running it. As a result, these functions can behave strangely in situations where the identity of a thread needs to be changed, such as when a worker thread is needed

The Go language encourages a simpler programming style, in which parameters that affect the behavior of a function should be explicitly specified. This not only makes the program easier to read, but also gives us the freedom to distribute subtasks of a function to multiple different Goroutines without worrying about the identity of those Goroutines

reference

The Go Programming Language — Alan A. A. Donovan

Go Language Learning Notes — Rain Marks