Wake up the wrist Go language concurrent programming (goroutine, channel) detailed tutorial (updating)

Basic concepts of threads and coroutines

Coroutines are concurrency under a single thread, also known as micro-threads and fibers. It is another way to achieve multitasking, but it is a smaller execution unit than a thread. Because it has its own CPU context, we can switch one coroutine to another coroutine at the right time. The English name is Coroutine.

In one sentence, what is a coroutine?

Lightweight thread-independent stack space, shared program heap space scheduling is logically controlled by the user, and consumes little resources.

The difference between threads and coroutines

Thread switching is a CPU switching back and forth between different threads. From the system level, it is not only as simple as saving and restoring the CPU context, but it will consume a lot of performance. But coroutines just switch back and forth between different functions in the same thread, and simply operate the context of the CPU, so the performance consumed will be greatly reduced.

Golang's coroutine mechanism can easily open tens of thousands of coroutines. Concurrency mechanisms in other languages ​​are generally based on threads, and it is costly to enable too many resources.

func runTimes(nums int) {
    
    
	for i := 0; i < nums; i++ {
    
    
		fmt.Println("runTimes", i, "times")
		time.Sleep(1000 * time.Millisecond)
	}
}

func main() {
    
    
	go runTimes(10)
	for i := 0; i < 10; i++ {
    
    
		fmt.Println("main", i, "times")
		time.Sleep(2000 * time.Millisecond)
	}
}

Note: If the coroutine has not finished executing, but the main thread has ended, the coroutine will end directly. Coroutines end before the main thread. Then the task of the coroutine is completed.

Go language goroutines

When we want to implement concurrent programming in java/c++, we usually need to maintain a thread pool by ourselves, and we need to package one task after another by ourselves. At the same time, we need to schedule threads to execute tasks and maintain context switching by ourselves. It consumes a lot of programmers' minds.

For this reason, the Go language provides a mechanism such as goroutine. The concept of goroutine is similar to that of thread, but goroutine is scheduled and managed by Go's runtime. The Go program will intelligently allocate the tasks in the goroutine to each CPU reasonably. The reason why the Go language is called a modern programming language is because it has built-in scheduling and context switching mechanisms at the language level.

use goroutines

Sometimes testRun() is not executed, because the program will create a default goroutine for the main function. When the statement in main is executed, the goroutine will end, and there is no time for go testRun() to execute. In order to ensure that the goroutine of go hello() can be executed, the end time of the program can be delayed

func main() {
    
    
	go testRun()
	fmt.Println("main hello")
	time.Sleep(time.Second)
}

func testRun() {
    
    
	fmt.Println("hello")
}

A single goroutine can be fully executed by delaying time, but when there are hundreds or thousands or more goroutines, using time.Sleep() obviously has no way to determine how much time is given to allow the goroutine to be fully executed. Too much affects the efficiency of the program, and some goroutines will not execute and affect the program results. At this time, we need to use another thing that is sync.WaitGroup.

There is a timer inside the WaitGroup object, initially starting from 0, he has 3 methods Add(), Done(), Wait() to control the number of counters. Add(n) sets the counter to n, Done() sets the counter to -1 each time, and wait() blocks the execution of the code until the value of the counter is reduced to 0. Combining the remaining number of goroutines with WaitGroup can solve the above problems.

var wg sync.WaitGroup

func main() {
    
    
	for i := 0; i < 10; i++ {
    
    
		wg.Add(1)
		go printNum(i)
	}
	wg.Wait()
	fmt.Println("end")
}

func printNum(i int) {
    
    
	defer wg.Done()
	fmt.Println(i)
}

When we run this code, we will find that the output is different each time, because the execution of these 10 goroutines is concurrent, but the scheduling is random.

goroutine stack memory

OS threads (operating system threads) generally have fixed stack memory (usually 2MB), and a goroutine stack has only a small stack (typically 2KB) at the beginning of its life cycle. The goroutine stack is not fixed and can be Grow and shrink as needed, and the goroutine stack size limit can reach 1GB, although this size is rarely used. So it is also possible to create about 100,000 goroutines at a time in the Go language.

goroutine scheduling

GPM is the implementation of the Go language runtime (runtime) level, and it is a set of scheduling system implemented by the Go language itself. It is different from the operating system scheduling OS thread.

  1. G is a goroutine, which not only stores the information of the goroutine, but also the binding information with the P where it is located.
  2. P manages a group of goroutine queues, and P will store the current goroutine running context (function pointer, stack address and address boundary), and P will do some scheduling for the goroutine queues it manages (for example, goroutines that take up a long CPU time Pause, run subsequent goroutines, etc.) When your own queue is consumed, it will go to the global queue to fetch tasks. If the global queue is also consumed, it will go to other P queues to grab tasks.
  3. M (machine) is the Go runtime's virtualization of operating system kernel threads. M and kernel threads generally have a one-to-one mapping relationship. A groutine is ultimately executed on M.
在这里插入代码片

Go language runtime library

Set the maximum number of CPU cores

runtime.GOMAXPROCS(8)

View the current number of cores of the current CPU

fmt.Println(runtime.NumCPU())

runtime.Gosched()

Exit the current goroutine, make room for other goroutines, and finally execute the exited goroutine

It probably means that I planned to go out for a barbecue on the weekend, but your mother asked you to go on a blind date. There are two situations. Slow down, the meeting is yours and mine, and the barbecue is delayed, but if you are still greedy, you have to go to the barbecue if you delay the barbecue.

func main() {
    
    
	// 让所有协程在一个核上执行
	runtime.GOMAXPROCS(1)
	go func(s string) {
    
    
		for i := 0; i < 2; i++ {
    
    
			fmt.Println(s,i)
		}
	}("协程运行中")
	// 主协程
	for i := 0; i < 2; i++ {
    
    
		fmt.Println("hello")
		// 停一下,再次分配任务
		runtime.Gosched()
		fmt.Println("world",i)
	}
}

runtime.Goexit()

Exits the current goroutine and will not execute in the future.

Blind date while grilling, suddenly found that the blind date is too ugly to affect the barbecue, decisively let her go, and then there is no more

runtime.GOMAXPROCS

The Go runtime scheduler uses the GOMAXPROCS parameter to determine how many OS threads it needs to use to execute Go code concurrently. The default is the number of CPU cores on the machine. For example, on an 8-core machine, the scheduler will schedule Go code to 8 OS threads at the same time (GOMAXPROCS is n in m:n scheduling).

In the Go language, you can runtime.GOMAXPROCS()函set the number of CPU logical cores occupied by the current program concurrently through the number.

OS thread vs goroutine relationship

1. 一个操作系统线程对应用户态多个goroutine。
2. go程序可以同时使用多个操作系统线程。
3. goroutine和OS线程是多对多的关系,即m:n。

Go language channel pipeline

The meaning of our setting function is to obtain a specific output under a specific input. If the function is only blindly concurrent without value transfer, then this concurrency is meaningless.

If goroutine is the concurrent execution of Go program, channel is the connection between them. A channel is a communication mechanism that allows one goroutine to send a specific value to another goroutine.

Channel is a special type. Channels are similar to conveyor belts or queues, following the principle of First In First Out.

Channel declaration and initialization

Each channel is of a specific type, and the type of elements transferred in the channel needs to be specified when declaring it.

var 变量 chan 元素类型
var ch chan int
println(ch)	

The declared channel needs to be initialized with the make function before it can be used. The null value of the initialized channel is a hexadecimal address.

`在不进行初始化的情况下使用通道会报 deadlock`
`其中缓存大小是可选项`
make(chan 元素类型, [缓冲大小])

var ch1,ch2 chan int
ch1 = make(chan int)	`无缓存的通道ch1`
ch2 = make(chan int,20)	`缓存大小为20的通道ch2`

Define channels directly

ch3 := make(chan int)

Guess you like

Origin blog.csdn.net/qq_47452807/article/details/128673618