Concurrent programming in go language

The go language natively supports coroutines, and the bottom layer implements coroutine scheduling. High concurrency is one of the characteristics of the go language.

goroutine principle

Threads are scheduled by the operating system, and coroutines are user-mode and user-scheduled. Many coroutines can be run on one thread.

The relationship between coroutines and threads

coroutine:thread=1:1

        A coroutine is bound to a thread, and the scheduling of the coroutine is completed by the CPU, which is equivalent to thread switching. Context switching is very slow.

Coroutine:Thread=N:1

        N coroutines are bound to 1 thread. The coroutine can complete switching in user mode and will not fall into kernel mode, but it cannot utilize multi-core resources.

coroutine:thread=N:M

        Multiple threads execute multiple coroutines at the same time, which can utilize multi-core resources and overcome the shortcomings of the above two models.

Goroutine is based on operating system threads. It implements a many-to-many (N:M) two-level thread model with operating system threads.

GM model

The coroutine scheduling before go1.0 version is the GM model. If a thread wants to execute or put back into the coroutine, it must access the global G queue, and there are multiple threads. Because multiple threads accessing the same resource need to be locked to ensure mutual exclusion and synchronization, the global queue is protected by a mutex lock. This will cause lock contention problems, and the cache of each coroutine will be stored in the memory cache, which will occupy a lot of memory space. Therefore, after version 1.12, the GPM model was adopted.

GPM model

G represents goroutine coroutine; P represents processor logical processor; M represents machine thread.

Compared with the GM model, the P logical processor is introduced to improve the coroutine scheduling model.

Normally, M for each running processing task must correspond to a P.

This model has two queues for storing G: the global G queue and the local queue corresponding to P. When the local queue is full (the upper limit is 256), it will automatically be put into the global queue, and the cache of G is stored in P, that is, P stores the cache of all G in the local queue, reducing memory consumption.

1. How to allocate G as reasonably as possible on a multi-core system to improve concurrency?

The go runtime system will always ensure that there is at least one active M and P to bind various G queues to process corresponding tasks. Generally, this active M is called a spin thread - it is in a running state but has no executable G. .

M will first search for the G task in its own P queue. If there is no G task in its own queue, it will go to the global queue to find the G task for processing. If there is no G task in the global queue, it will steal tasks from other P queues (steal half of the tasks) - This mechanism is called work stealing mechanism.

Note: When this thread is executed without G, it will steal G instead of directly destroying the thread.

2. What should I do if a certain M is blocked by a system call during the execution of G?

When M is scheduled out of the CPU by the kernel scheduler and is in a blocked state, then the G associated with M has no way to run. When go is running, there is a system monitoring process - the sysmon thread, which can monitor this situation. If it encounters a blocking situation, it will separate P and M, find other idle M or create a new M to take over P, and then continue Run G in the original queue.

After the original M recovers from the blocking state, there will be two situations:

        ①. If there is an idle P, obtain a P and continue executing the originally blocked G.

        ② If there is no idle P, the originally blocked G will be added to the global queue, waiting to be scheduled by other P, and M will enter the buffer pool sleep

This mechanism is called the hand off mechanism.

3. What if a G takes too long to run on M?

The sysmon process will monitor. If G runs for too long, in order to prevent other Gs from being starved to death, a timeout will be set: a goroutine can occupy up to 10ms of the CPU. If the time exceeds, the running G will actively give up the execution of M. right.

Coroutine writing

The underlying core package of coroutine: runtime package, goroutine is the runtime.g structure.

The runtime.GOMAXPROCS() function can set the number of P. By default, the number of P is equal to the number of CPU cores. When the parameter passed in the function is 0, it means the current default number of P. When the parameter passed in is greater than 0, it means the number of P is set.

When go runs the main function, it creates a coroutine - the main coroutine. By default, the main coroutine ends and the sub-coroutine will also exit.

Example: Create 5 coroutines to request Baidu pages, each request sleeps for 1 second

package main

func mian(){
    fmt.Println(time.Now().Unix())    //显示当前时间戳
    //请求5次百度页面
	for i := 0; i < 5; i++{
		go request("https://baidu.com")    //开启协程同步执行
	}
    //睡5s 保证子协程全部执行完,再退出
	time.Sleep(5 * time.Second)
}

func request(url string){
	client := http.Client{}
	r, _ := http.NewRequest("GET", url, nil)
	response , e := client.Do(r)
	if e != nil{
		fmt.Println("发送请求失败!")
		return
	}
	fmt.Println("请求状态码:", response.StatusCode)
	defer response.Body.Close()    //释放资源
	time.Sleep(1 * time.Second)
	fmt.Println(time.Now().Unix())
}

operation result:

You can also use anonymous functions to create sub-coroutines:

package main

func main()  {
	//匿名函数的方式创建子协程
	go func(){
		i := 0
		for{
			i++
			fmt.Println("子协程i =", i)
			time.Sleep(1 * time.Second)    //睡1s
		}
	}()
	//主协程退出 子协程也退出
	for i := 0; i < 3; i++{
		fmt.Println("主协程i =", i)
		time.Sleep(1 * time.Second)
	}
}

channel

The CSP model is a concurrent programming model that does not communicate through shared memory, but shares memory through communication. Channel is the first class object of CSP.

Characteristics of channel: The channel channel itself is a queue, first in, first out. Channel is a reference type and can be initialized with make. Each channel can only store the same type of data. Sending and receiving operations using channels are performed between different goroutines. You cannot write when the channel is full, and you cannot access it when it is empty. It is easy to cause deadlock and blockage.

1. Unbuffered channel--usually used for blocking

package main

func main(){
    fmt.Println("start main......")
    //生成一个无缓冲通道 里面只能存放1个int类型的数据
    ch1 := make(chan int)
    go child(ch1)
	fmt.Println("start child")
    //从ch1通道里取数据
    //要等子协程发送数据后主协程才能获取数据
    //主协程会等子协程 起到阻塞当前主协程环境上下文的作用
    <- ch1
    fmt.Println("get data finish!")
}

func child(ch chan int){
	fmt.Println("this is child")
	time.Sleep(5 * time.Second)
    //存入数据
	ch <- -1
}

operation result:

2. There is a buffer channel

package main

func main(){
    //设置通道容量为6
    ch1 := make(chan int, 6)
	go senddata(ch1)
    //睡5s保证通道里存满数据
	time.Sleep(5 * time.Second)
    //循环取出数据
	for data := range ch1 {
		fmt.Println("读取数据", data)
	}
}

func senddata(ch chan int){
	for i := 0; i < 10; i++{
		ch <- i
		fmt.Println("往通道里面放数据:", i)
	}
    defer close(ch)
}

3. Several ways to determine channel closure

The following three methods all use the same senddata() function:

func senddata(ch chan string){
	for i := 0; i < 3; i++{
        //格式化存入字符串
		ch <- fmt.Sprintf("send data %d", i)
	}
	//defer 是go中的一种延迟调用机制
	defer fmt.Println("数据发送完毕!")
	//数据发送完毕主动关闭通道 防止产生死锁 引起程序退出
	defer close(ch)
}

method one:

func main(){
    ch1 := make(chan string)
	go senddata(ch1)

    for{
        data := <- ch1
        //如果通道关闭,读取到的是数据类型的默认值 string类型的默认值是空字符串
        if data == ""{
			break
		}
        fmt.Println("从通道里读取到的数据:", data)
    }
}

Method two:

func main(){
    ch1 := make(chan string)
	go senddata(ch1)

    for{
        data, ok := <- ch1
        //ok为false表示通道关闭
        if !ok{
			break
		}
		fmt.Println("获取到的数据:", data)
    }
}

Method three:

func main(){
    ch1 := make(chan string)
	go senddata(ch1)
    
    //range会自动判断通道是否关闭,关闭了就会break
    for value := range ch1{
        fmt.Println("获取到的数据:", value)
    }
}

select statement

The select statement will monitor the data of the channel and select the communicable channel to execute the code. If there are multiple communicable channels at the same time, it will randomly select one; if there is no communicable channel, the code after default will be executed. Similar to switch case statement.

func main() {
	ch1 := make(chan int)
	ch2 := make(chan int)

	go senddata1(ch1)
	go senddata1(ch2)
	//睡一秒让数据存入ch1 ch2中
	time.Sleep(1 * time.Second)
	flag := 0
	for {
		select {
		case data1, ok1 := <-ch1:
			if !ok1 {
				fmt.Println("数据读取完毕!")
				flag++
				break 	//跳出select语句
			}
			fmt.Println("获取ch1的数据为:", data1)
		case data2, ok2 := <-ch2:
			if !ok2 {
				fmt.Println("数据读取完毕!")
				flag++
				break
			}
			fmt.Println("获取ch2的数据为:", data2)
		}
		if flag == 2 {
			return		//两个通道都消费完了退出for循环
		}
	}
}

func senddata1(ch chan int) {
	for i:=0; i<10; i++ {
		ch <- i
	}
	defer close(ch)
}

sync package

sync provides a series of synchronization mutex locks, such as:

sync.Mutex mutex lock

sync.RWMutex read-write lock

sync.WaitGroup A goroutine waits for a group of goroutines to complete execution

sync.Map concurrent version of map

sync.Pool The concurrent pool is responsible for safely saving a group of objects.

Demonstrate the application of mutex locks and wait groups:

//声明全局变量
var num = 20
var wg sync.WaitGroup	//等待组对象
var mutex sync.Mutex	//互斥锁对象

func main(){
    for i := 0; i < 10; i++{
        //设置wg内部的计数器 表示等待的子协程数量 可以累加
        wg.Add(1)
        name := fmt.Sprintf("协程%d",i)
        go modifynum(name, &wg)    //创建3个子协程
    }
    wg.Wait()    //阻塞 等待wg内部计数器为0,才会继续进行后续的操作
    fmt.Println("执行结束!")
}

//wg为引用类型 执行同一个对象
func modifynum(name string, wg *sync.WaitGroup){
    defer wg.Done()    //使用wg.Done 减少wg内部的计数器
    mutex.Lock()    //加互斥锁
    fmt.Println(name, "running...")
	num ++
    fmt.Println("num is", num)
    mutex.Unlock()    //释放锁
}

Guess you like

Origin blog.csdn.net/m0_69298614/article/details/131632534