Introduction to Goroutine

Goroutine is the most NB design in GO language, which is also its charm. The essence of Goroutine is coroutine, which is the core of parallel computing. Goroutine is very simple to use. Just use the GO keyword to start a coroutine, and it runs asynchronously. You don’t have to wait for it to finish executing code later.

Go func()// Start a coroutine with the go keyword to run the functionCopy the code

Two, Goroutine internal principle

The concept is introduced

Before getting into the implementation principles, understand the concepts of some key terms.

concurrent

A CPU can perform multiple tasks at the same time, in a short time, the CPU to switch back and forth task execution (in a very short period executable program a, then quickly switch to the program to execute the b), have the time on the overlap (macro is at the same time, the micro is still sequentially), it looks more tasks like performing at the same time, this is complicated.

parallel

When the system has multiple cpus, each CPU runs tasks at the same time without preempting its own CPU resources. This is called parallelism.

process

When the CPU switches programs, if the state of the previous program is not saved (also known as context– context) and the next program is directly switched, a series of states of the previous program will be lost, so the concept of process is introduced to divide the resources needed by the program to run. A process is thus the basic unit of resources (or the entity in which the program is running) that a program needs to run.

thread

CPU switches multiple processes, will spend a lot of time, because of the need to switch to the kernel mode, switching process every time and scheduling user mode to kernel mode needs to read data, process once more, dispatch meetings consume a lot of CPU resources, thus introduced the concept of threads, thread itself is almost no resources possession, they share the resources in the process, Kernel scheduling is less costly than process switching.

coroutines

Coroutines have their own register context and stack. When coroutine schedules a switch, the register context and stack are saved elsewhere, and when cut back, the previously saved register context and stack are restored. Thus, the coroutine can retain the state of the last call (that is, a specific combination of all local states), and each procedure reentry is equivalent to entering the state of the last call, or in other words, the position of the logical flow from which it left the last time. Thread and process operation is triggered by the program system interface, the final executor is the system; The operator of coroutines is the user’s own program, and Goroutine is also a coroutine.

 

Introduction to scheduling Model

The powerful concurrency of Groutine is achieved through the GPM scheduling model, which is explained in goroutine.








Scheduling implement













 

When an OS thread M0 is blocked (as shown below), P instead runs M1, which may be being created or fetched from the thread cache.

 






Use Goroutine

The basic use

Set the number of cpus goroutine runs on, which is already set by default for the latest version of Go.

Num := Runtime. NumCPU() // Obtains the number of logical cpus on the host Runtime. GOMAXPROCS(num) // Sets the maximum number of cpus that can be concurrently executedCopy the code

Use the sample

package main

import (
    "fmt"
    "time"
)

func cal(a int , b int )  {
    c := a+b
    fmt.Printf("%d + %d = %d\n",a,b,c)
}

func main() {

for i :=0 ; i<10 ; I ++{go CAL (I, I +1) // start 10 goroutines to calculate} time.sleep (time.second * 2) // Sleep is used to wait for all tasks to complete} // result //8 + 9 = 17 //9 + 10 = 19 / / 4 + 5 = 9 11 / / / / 5 + 6 = 0 + 1 = 1 / (1 + 2 = 3/2 + 3 = 5 / / 3 + 4 = 7 7 + 8 = 15 / / / / 6 + 7 = 13Copy the code

Goroutine exception catching

When multiple Goroutines are started, if one of them fails and we do not handle the exception, the whole program will terminate. Therefore, it is best to use RECOVER to handle the exception for every function that goroutine runs

Package main import (" FMT ""time") func addele(a []int, I int) {defer func() {err := recover() if err! = nil { fmt.Println("add ele fail") } }() a[i]=i fmt.Println(a) } func main() { Arry := make([]int,4) for i :=0 ; i<10 ; I ++{go addele(Arry, I)} time.sleep (time.second * 2)} // result addele fail [0 0 0 0] [0 1 0 0] [0 1 2 0] [0 1 2 3] addele  fail add ele fail add ele fail add ele fail add ele failCopy the code

Synchronous goroutine

Since goroutine is executed asynchronously, it is possible that the main program will exit and the Goroutine will exit as well. If you want to wait for all goroutine tasks to complete before exiting, go provides sync packages and channels to solve synchronization problems. Of course, if you can predict the execution time of each Goroutine, You can also exit the program by waiting for all Groutine execution to complete in time.sleep mode (as shown above).

package main import ( "fmt" "sync" ) func cal(a int , B int,n * sync.waitgroup) {c := a+b FMT.Printf("%d + %d = %d\n",a,b,c) defer n.one () // After the goroutinue is finished, 1} func main() {var go_sync sync.waitgroup // declare a WaitGroup variable for I :=0; i<10 ; Go CAL (I, I +1,&go_sync)} go_sync.wait () // Wait for all goroutines to complete} // result 9 + 10 = 19 2 + 3 = 5 3 + 4 = 7 4 + 5 = 9 5 + 6 = 11 1 + 2 = 3 6 + 7 = 13 7 + 8 = 15 0 + 1 = 1 8 + 9 = 17Copy the code

Example 2: Synchronization between Goroutines through channels.

Implementation method: When a Goroutine is finished, it sends an exit signal to the channel. When all goroutines exit, it uses the for loop channel to access the signal in the channel. If no data can be retrieved, the principle will be blocked. After all the Goroutines have been executed, you can use this method only if you know how many goroutines you have started.

package main import ( "fmt" "time" ) func cal(a int , b int ,Exitchan chan bool) { c := a+b fmt.Printf("%d + %d = %d\n",a,b,c) time.Sleep(time.Second*2) Exitchan <- true } Func main() {Exitchan := make(chan bool,10) i<10 ; i++{ go cal(i,i+1,Exitchan) } for j :=0; j<10; J++ {< -exitchan} close(Exitchan)Copy the code

Communication between goroutines

Goroutines are essentially coroutines, and can be understood as threads that are not scheduled by the kernel but managed by the GO scheduler. Goroutines can communicate with each other via channels or share data, but you can also share data using global variables.

Example: Using channels to simulate consumer and producer patterns

package main import ( "fmt" "sync" ) func Productor(mychan chan int,data int,wait *sync.WaitGroup) { mychan <- data FMT. Println (" product data: ",data) wait.done ()} func Consumer(mychan chan int,wait * sync.waitgroup) {a := < -mychan FMT.Println(" Consumer data: ",a) wait.done ()} func main() {datachan := make(chan int, 100) var wg sync.waitgroup for I := 0; i < 10; I ++ {go Productor(datachan, I,&wg) // Add(1)} for j := 0; j < 10; J++ {go Consumer(datachan,&wg) // Add(1)} wg.wait ()} // result Consumer data: 4 product data: 5 product data: 6 Product data: 7 Product data: 8 Product data: 9 Consumer data: 1 Consumer data: 5 Consumer data: 6 Consumer data: 7 Consumer Data: 8 Consumer data: 9 Product data: 2 Consumer data: 2 Product data: 3 Consumer data: 3 4 Consumer data: 0 Product data: 0 Product data: 1Copy the code

 

Fourth, the channel

Introduction to the

A channel, commonly known as a channel, is used for data transmission or data sharing. In essence, it is a first-in, first-out queue. Using Goroutine +channel for data communication is simple and efficient, but also thread safe.

Channels can be divided into three types:

Read-only channel: only data in a channel can be read, but not written

Write channel only: Only data can be written, not read

Common channel: read and write

The channel to use

Definitions and declarations

Var readOnlyChan <-chan int // readOnlyChan var writeOnlyChan chan< -int // writeOnlyChan var mychan chan int // read and write a channel // Make to allocate memory space Otherwise use deadlock mychannel = make(chan int,10)// or read_only := make(<-chan int,10)// define a read-only channel write_only := make (chan< -int,10)// define write only channel read_write := make (chan int,10)//Copy the code

Read and write data

Note that:

  • If the pipe is not closed, a deadlock exception will be raised during a read timeout
  • The pipe will pannic if closed for writing data
  • Read when there is no data in the pipe or read to a default value, such as 0 for int
Ch < - "wd" / / write data a: = < - ch / / read the data. A, ok: = < - ch / / elegant reading dataCopy the code

Circular pipe

Note that:

  • Use a range loop pipe, which causes a deadlock error if the pipe is not closed.
  • If you use a for dead-loop closed pipe, when the pipe has no data, the data read will be the pipe’s default value and the loop will not exit.
package main import ( "fmt" "time" ) func main() { mychannel := make(chan int,10) for i := 0; i < 10; I ++{mychannel < -i} close(mychannel) Println(v)} Printf("data lenght: %d",len(mychannel))} ",len(mychannel)) for v := range mychannel {Copy the code

Buffer channe and no buffer channel

Buffered channel: The size (length) of the buffer is specified in the definition declaration and can hold multiple data.

Channel without buffer: Only one data can be stored, and only one data can be stored when the data is retrieved.

Ch := make(chan int) ch := make(chan int,10Copy the code

Example without buffer:

package main import "fmt" func test(c chan int) { for i := 0; i < 10; i++ { fmt.Println("send ", i) c <- i } } func main() { ch := make(chan int) go test(ch) for j := 0; j < 10; J++ {FMT.Println("get ", <-ch)}}  send 0 send 1 get 0 get 1 send 2 send 3 get 2 get 3 send 4 send 5 get 4 get 5 send 6 send 7 get 6 get 7 send 8 send 9 get 8 get 9Copy the code

Channel implements job pools

We create three channels, one for accepting tasks, one for holding results, and one for deciding when the program exits.

package main import ( "fmt" ) func Task(taskch, resch chan int, Exitch chan bool) {defer func() {err := recover() if err! Println("do task error: ", err) return}}() for t := range taskch {fmt.Println("do task :", T) resch < -t // exitch < -true // Exitch := make(chan int, 20) // make(chan bool, 5) // go func() {for I := 0; i < 10; i++ { taskch <- i } close(taskch) }() for i := 0; i < 5; I ++ {// start 5 goroutines to do tasks go Task(taskch, resch, exitch)} go func() { i < 5; I++ {<-exitch} close(resch) () for res := range resch{FMT.Println("task res: ",res)}}Copy the code

Read-only channel and write channel only

It doesn’t make much sense to define read-only and write-only pipes, but more often we can specify whether the pipe is readable or writable during parameter passing, even if the pipe is currently read-write.

Func send(c chan< -int) {for I := 0; i < 10; Func get(c <-chan int) {for I := range c {fmt.println (I)}} func main() {c := Make (chan int) go send(c) go get(c) time.sleep (time.second *1)Copy the code

 

Select-case implements a non-blocking channel

The principle is added to a set of pipes through select+case. If a case in select is satisfied, then the case is returned. If neither case is satisfied, then the default branch is used.

package main

import (
    "fmt"
)

func send(c chan int)  {
    for i :=1 ; i<10 ;i++  {
     c <-i
     fmt.Println("send data : ",i)
    }
}

func main() {
    resch := make(chan int,20)
    strch := make(chan string,10)
    go send(resch)
    strch <- "wd"
    select {
    case a := <-resch:
        fmt.Println("get data : ", a)
    case b := <-strch:
        fmt.Println("get data : ", b)
    default:
        fmt.Println("no channel actvie")

    }

}

//结果:get data :  wdCopy the code

Channel frequency control

When reading and writing a channel, Go also provides a very humane operation, that is, the frequency control of reading and writing, which is realized through time.ticke

Example:

package main import ( "time" "fmt" ) func main(){ requests:= make(chan int ,5) for i:=1; i<5; i++{ requests<-i } close(requests) limiter := time.Tick(time.Second*1) for req:=range requests{ <-limiter FMT.Println("requets",req, time.now ())) Requets 1 2018-07-06 10:17:35.98056403 +0800 CST M =+1.004248763 RequETS 2 2018-07-06 10:17:36.978123472 +0800 CST M =+2.001798205 RequETS 3 2018-07-06 10:17:37.980869517 +0800 CST M =+3.004544250 2018-07-06 10:17:38.976868836 0800 CST m = + + 4.000533569Copy the code