Go concurrency model

Traditional programming languages such as C++, Java and Python, their concurrent logic is based on operating system threads. Communication between concurrent execution units (threads) takes advantage of the operating system-provided primitives for thread or interprocess communication. Such as: shared memory, signals, pipes, message queues, sockets, etc. The most widely used of these communication primitives is shared memory.

If you’ve ever used this shared memory concurrency model, it can be difficult to use and error-prone, especially in large or complex business scenarios.

Go language aims to solve the problems of the above traditional concurrency model from the beginning of programming, and uses the noted CSP(Communicationing Sequential Processes) concurrency model for reference in the design of the new concurrency model.

The PURPOSE of the CSP model is to simplify the writing of concurrent programs and make the writing sequence of concurrent programs as simple as that of sequential programs.

Producer – Output data – Input/Output primitive – Output data

To implement the CSP model, the GO language introduced channels. Goroutine can read and write data in a Channel, through which Goroutine groups are linked together.

Although CSP is the mainstream concurrency model in Go language, it still supports the shared memory concurrency model. These include mutex, read/write locks, conditional variables, atomic operations, and so on in the SYNC package. So how do we choose?

The first: create mode

The following is usually used:

type Worker struct { } func Do(f func()) chan Worker { w:= make(chan Worker) go func() { f() w<-Worker{} }() return w } Func main () {c: = Do (func () {FMT. Print (" to go off work time..." ) }) <-c }Copy the code

The Do function creates a gorutine internally and returns a variable of type channel. The new goroutine created by the Do function is connected to the goroutine called by the Do function through a channel, through which the two goroutines can communicate. Because a channel is a first-class citizen in the Go language, it can be initialized, passed, and assigned like a variable. The above example, Do, returns a variable, the channel, that implements communication between the primary goroutine and its children.

Second: exit mode

A) Separation mode

The most widely used split mode is the Goroutine exit mode. The separation mode is that the goroutine that created it does not need to care about its exit. This type of Goroutine is completely separated from its creator once it starts, and its life cycle is related to the main function it executes. The return of the function is the exit of the Goroutine. Scenario 1: One-off task

// $GOROOT/src/net/dial.go func (d *Dialer) DialContext(ctx context.Context, network, address string) (Conn, error) { ... . if oldCancel := d.Cancel; oldCancel ! = nil { subCtx, cancel := context.WithCancel(ctx) defer cancel() go func() { select { case <-oldCancel: cancel() case <-subCtx.Done(): } }() ctx = subCtx } ... . }Copy the code

A goroutine is created in the DialContext method, which is used to listen for data in a channel and exit after processing data.

Scenario 2 Resides in the background to perform specific tasks, such as the common for{… For} or {select {… }} form, can also be used by timer or event-driven execution. Here is the GC Goroutine that Go gives each P built-in for this scenario.

// $GOROOT/src/runtime/mgc.go func gcBgMarkStartWorkers() { // Background marking is performed by per-P G's. Ensure that  // each P has a background GC G. for _, P := range allp {if p.gcbgmarkWorker == 0 {go gcBgMarkWorker(p) // Create a goroutine for each p, To run gcBgMarkWorker notetSleepg (&work.bgmarkReady, -1) noteclear(&work.bgMarkReady) } } } func gcBgMarkWorker(_p_ *p) { gp := getg() ... . For {// handle GC issues... . }}Copy the code
B) join mode

In the threading model, the parent thread can wait for the child thread to terminate and obtain the termination status of the child thread through pThread Join. In Go, we sometimes have this requirement: the creator of a Goroutine needs to wait for the results of a new Goroutine.

type Worker struct { } func Do(f func()) chan Worker { w:= make(chan Worker) go func() { f() w<-Worker{} }() return w } Func main () {c: = Do (func () {FMT. Print (" to go off work time..." ) }) <-c }Copy the code

The Do function creates a groutine using the typical goroutine creation pattern. Main’s Goroutine establishes a relationship with the new Goroutine by creating a channel returned by the Do function. The purpose of this channel was to establish an exit time “signal” communication mechanism between cultural Revolution Goroutines. Main Goroutine blocks on the channel after creating a new goroutine, sending a “signal” to the channel before the new goroutine exits.

Run the code and the result is as follows:

It’s time to go home… Process finished with exit code 0

Gets the exit status of goroutine

If the creator of a new Goroutine not only needs to wait for the exit of the Goroutine, but also needs to know the end state, we can implement this requirement with a channel of a custom type.

func add(a,b int) int{ return a+b } func Do(f func(a,b int) int,a,b int) chan int{ c:=make(chan int) go func() { R: = f (a, b) < - c () return c r}} func main () {c: = Do (add, 1, 5) FMT. Println (< - c)} running result is 6Copy the code

Wait for multiple Goroutines to exit

func add(a,b int) int{ return a+b } func Do(f func(a,b int) int,a,b,n int) chan int{ c:=make(chan int) var wg sync.WaitGroup for i:=0; i<n; i++{ wg.Add(1) go func() { r:=f(a,b) fmt.Println(r) wg.Done() }() } go func() { wg.Wait() c<-100 }() go func() { }() Return c} func main() {c:=Do(add,1,5,5) fmt.Println(<-c)} 6 6 6 6 6 6 100Copy the code
C) notify – wait mode

In the previous scenario, the creator of a Goroutine was passively waiting for the new Goroutine to exit. In some cases, the creator of a Goroutine will need to actively notify the new Goroutine to quit.

Notify and wait for a Goroutine to exit

func add(a, b int) int { return a + b } func Do(f func(a, b int) int, a, b int) chan int { quit := make(chan int) go func() { var job chan string for { select { case x := <-job: f(a, b) fmt.Println(x) case y := <-quit: quit <- y } } }() return quit } func main() { c := Do(add, 1, Sleep(1 * time.second) c < -0 timer := time.newtimer (time.second * 10) defer timer.stop () Select {case status := <-c: fmt.Println(status) case < -timer.c: fmt.Println(" wait...") )}}Copy the code

The result of executing the code is 0

Notify and wait for multiple Goroutines to exit

Here is a scenario where you notify and wait for multiple Goroutines to exit. A feature of the Go language’s channel is that when a channel is closed using the close function, all goroutines blocked on the channel are notified.

func worker(x int) { time.Sleep(time.Second * time.Duration(x)) } func Do(f func(a int), n int) chan int { quit := make(chan int) job:=make(chan int) var wg sync.WaitGroup for i:=0; i<n; i++ { wg.Add(1) go func(i int) { defer wg.Done() name := fmt.Sprintf("worker-%d",i) for { j,ok:=<-job if ! ok{ fmt.Println(name,"done") return } worker(j) } }(i) } go func() { <-quit close(job) wg.Wait() quit<-200 }() return quit } func main() { quit:=Do(worker,5) fmt.Println("func Work..." ) quit<-1 timer := time.NewTimer(time.Second * 10) defer timer.Stop() select { case status := <-quit: FMT.Println(status) case < -timer.c: FMT.Println(" waiting for...") }} func Work... worker-1 done worker-2 done worker-3 done worker-4 done worker-0 done 200Copy the code