Getting Started Goroutine concurrent design mode and Goroutine Visualizer

Source: Internet
Author: User
This is a creation in Article, where the information may have evolved or changed.

Daisy-chain

First of all, in order to prevent too boring, I first listed my favorite mode: Daisy-chain. This mode is more complex, the concurrent programming of GO is not familiar with the classmate, you can first look at the following mode. And look back at this.

Daisy chain will create a lot of channel, then cascade the channel to the end, forming a one-way chain, each channel is processing different sub-tasks, the final result is output at the ends of the chain. This was presented by Rob Pike in Golang talks in 2012:

func f(left, right chan int) {    // 这个函数就把right的输出和left的输入联系起来了。    left <- 1 + <-right}func main() {    const n = 10000    leftmost := make(chan int)    right := leftmost    left := leftmost    // 创建长度为n的daisy链    for i := 0; i < n; i++ {        right = make(chan int)        go f(left, right)        left = right    }    // 在链的最右端输入1,那么最左端就会得到10001    go func(c chan int) { c <- 1 }(right)    fmt.Println(<-leftmost)}

The whole process is similar:

So what's the use of this model? It can be used to deal with iterative algorithms, allowing some iterative operations to execute concurrently. As long as each phase of the iteration is independent of each other. For example, calculate prime numbers:

  Import ("FMT" "OS" "Runtime/trace" "Time") func Generate (ch chan<-int) {for I: = 2;; i++ {ch <-I//This is for convenient gotrace drawing time. Sleep (Ten * time.           Millisecond)}}func Filter (ch <-chan int, out chan<-int., Prime int) {for {i: = <-ch If I%prime! = 0 {out <-i} else {fmt. Printf ("[%d] filter out%d\n", Prime, I)}}}func main () {//These are also gotrace required to insert code. The same trace. Start (OS. STDERR) ch: = make (chan int) go Generate (CH) for I: = 0; I < 10; i++ {prime: = <-ch//Step1 FMT. PRINTLN (prime) out: = Do (chan int)//Step2 go Filter (ch, out, Prime)//step3 ch = ou T//Step4} trace. Stop ()}  

Careful analysis of the above code, its function is to output the first 10 positive integer numbers. As for the details, let's take a look at the steps:
First, generate a positive integer from 2, and was put into goroutine in the first place. The result will be put in ch ;
Then, starting a for loop in main, each step1 in the loop will ch read a prime number from the. 2 is prime, of course, but is it a prime number to read from CH in each subsequent step? And look at the code below.
Step2 will then create a new channel (like the one in the out example above), CH and it creates a filter goroutine as input and output, specifically filtering the number of primes that can be step1. So the out number that is in the output is not divisible by the prime.
Finally in the key Step4, out becomes the next ch . Equivalent to an increase in the length of a section of chain. The first number that CH outputs in each loop is the number that is not divisible by all previous prime numbers , that is, the next prime.
The output logs are as follows:

23[2] filter out 45[2] filter out 67[2] filter out 8[3] filter out 9[2] filter out 1011[2] filter out 1213[2] filter out 14[3] filter out 15[2] filter out 1617[2] filter out 1819[2] filter out 20[3] filter out 21[2] filter out 2223[2] filter out 24[5] filter out 25[2] filter out 26[3] filter out 27[2] filter out 2829

In order to display the whole process more intuitively, I drew the Goroutine 3d interaction diagram with the Gotrace tool of the Divan great God:

Each of these red vertical bars represents a goroutine, the timeline is top to bottom, so the longer the red line means that the longer the goroutine lasts, the earlier it is generated.
As you can see, the first goroutine to get the number is 3, 4, ... 29, because 2 has been output, so is 3 to 29, and then the next goroutine obtained is 5,7,9, ... 29, because 3 is output, and even numbers are filtered. And so on, the final output is the first 10 prime numbers.
It is important to note that this algorithm is not the most efficient, but it is very elegant.

For installation and use of Gotrace, please visit here. I was able to use it after I patched it to go1.6.3 in his own way.

OK, let's take a look at some basic patterns :

Ping-Pong

As the name implies, is a pattern composed of 2 Goroutine mutual buck. Was raised in the Golang talks in 2013. Although it is very simple, it is convenient for us to understand the concurrency programming concept of go.

The code is as follows:
The ball is represented by an int, the pipe represents table (table), and two goroutine are 2 athletes, numbered 1 and 2 respectively.

func main() {    var Ball int    table := make(chan int)    go player("2", table)    go player("1", table)    // 首先把球放到“桌上”    table <- Ball    time.Sleep(1 * time.Second)    // 1s后比赛结束……    <-table}func player(id string, table chan int) {    for {        ball := <-table        log.Printf("%s got ball[%d]\n", id, ball)        time.Sleep(50 * time.Millisecond)        log.Printf("%s bounceback ball[%d]\n", id, ball)        ball++        table <- ball    }}

The output is as follows:

1 got ball[0]1 bounceback ball[0]2 got ball[1]2 bounceback ball[1]1 got ball[2]1 bounceback ball[2]2 got ball[3]2 bounceback ball[3]1 got ball[4]1 bounceback ball[4]2 got ball[5]2 bounceback ball[5]

The code is simple and easy to understand, very good understanding (don't understand the classmate please do not shoot me).
Now, let's add one player to play with 3 players.

    go player("2", table)    go player("3", table)    go player("1", table)

This is hilarious, the output is as follows:

1 got ball[0]1 bounceback ball[0]2 got ball[1]2 bounceback ball[1]3 got ball[2]3 bounceback ball[2]1 got ball[3]1 bounceback ball[3]2 got ball[4]2 bounceback ball[4]3 got ball[5]3 bounceback ball[5]1 got ball[6]1 bounceback ball[6]2 got ball[7]2 bounceback ball[7]3 got ball[8]3 bounceback ball[8]

See 3 of people methodically hitting each other. Virgo must be very happy at this point, but for programmers accustomed to concurrent randomness, this is a bit too good: why are their order so coordinated, why 1 always give 3,3 to 2,2 to 1, rather than the other order?

The point is:

The answer is because Go runtime holds waiting FIFO queue for receivers, that's goroutines ready to receive on the Partic Ular Channel

That is, for the goroutines that receive the channel content, the go runtime assigns them to a FIFO queue, so the goroutines can only receive the channel content in a given order, without confusing it. So even if hundreds of palyers are created, the order is still fixed. Go is too sweet, there is no!

Fan-in

Also called "Fan in", it should be the concurrent programming inside a more common mode. Fan-in will read the input from multiple pipelines and summarize it into a channel output, an image metaphor such as:

The sample code is as follows

  Import ("FMT" "Math/rand" "OS" "Runtime/trace" "Time") Func main () {trace. Start (OS. Stderr) C: = Fanin (Boring (1), boring (2)) for I: = 0; I < 10; i++ {fmt. Println (<-C)} fmt. Println ("You ' re both boring;       I ' m leaving. ") Trace. Stop ()}func fanin (INPUT1, input2 <-chan int) <-chan int {c: = make (chan int) go func () {for {C <-&       LT;-INPUT1}} () go func () {for {C <-<-input2}} () return c}func boring (msg int) <-chan int {           c: = make (chan int) go func () {//We launch the Goroutine from inside the function. For I: = 0;; i++ {C <-msg*1000 + I time. Sleep (time. Duration (Rand. INTN (1E3)) * Time.millisecond)}} () return c//return the channel to the caller.}  

The output is:
2000
2001
1001
2002
1002
2003
1003
2004
1004
The Gotrace output is (note that this is two independent run results):

As you can see, two of times results are remitted to the main thread, and the output is sequential, with no data loss or deadlock.

Of course, the simple case, with select also can.

The purpose of select design is to communicate in the middle of the channel, whose data arrives first and which case branch executes first.

c1 := boring(1)c2 := boring(2)for i := 0; i < 10; i++ {    select {    case v := <-c1:        fmt.Println(v)    case v := <-c2:        fmt.Println(v)    }}

Workers

Also called fanout (fan out), and fan-in mode in contrast, the working mode is a pipeline distribution task, multiple Goroutines to execute.
The sample code is as follows:

  Import ("FMT" "OS" "Runtime/trace" "Sync" "Time") Func worker (ch <-chan int, wg *sync. Waitgroup) {defer WG. Done () for {task, OK: = <-ch if!ok {return} time. Sleep (Time.millisecond) fmt. PRINTLN ("Processing Task", Task)}}func pool (WG *sync. Waitgroup, workers, tasks int) {ch: = make (chan int) for i: = 0; i < workers; i++ {time. Sleep (1 * time.millisecond)//Spawn out many worker threads go Worker (CH, WG)} for I: = 0; I < tasks; i++ {time. Sleep (Time.millisecond)//Start distribution task, the activated workers begins to work with ch <-i} close (CH)}func main () {trace. Start (OS. STDERR) var wg sync. Waitgroup WG. ADD (+) go Pool (&WG, $) WG. Wait () trace. Stop ()}  

The code is slightly longer, but the logic is actually very clear. I also gave a brief explanation in the comments.
Note ( focused ), close(ch) here is the key, which determines the node that each worker exits. When the content in the channel is empty, and it has been close, task, ok := <- ch the Ok==false is returned, at which point the worker exits, the WG tag is complete, and when all the workers are complete, the WG. Wait () completes and goes to the next line to execute.
In Golang, main does not automatically wait for all child processes to complete , and if there is no Exit check, the main process will flash and all child processes will be forced to exit, so there must be an exit detection mechanism in main, and the first few examples we are using time. Sleep and for loops, here we use the Waitgroup.

Gotrace results are as follows:

The center of the cylinder is the pool process generated in the main process, which is surrounded by 36 worker processes. The blue arrows indicate that the pool distributes tasks every 10ms, and they are processed by the worker.

Servers

The server mode is similar to Fan_out, except that its worker threads are generated on demand and released when the work is finished. So this pattern is often applied to the Web server. In the main process, there is a for loop, the Accept function blocks the loop, and once a new request comes in, the accept generates a connection, and the main process creates a child process to handle the connection and other logic.

The sample code is as follows:

import (       "fmt"       "net"       "os"       "runtime/trace"       "time")func handler(c net.Conn, ch chan int) {   ch <- len(c.RemoteAddr().String())   time.Sleep(10 * time.Microsecond)   c.Write([]byte("ok"))   c.Close()}func logger(ch chan int) {       for {           time.Sleep(1500 * time.Millisecond)           fmt.Println(<-ch)       }}func server(l net.Listener, ch chan int) {       for {           c, err := l.Accept()           if err != nil {               continue           }           go handler(c, ch)       }}func main() {       trace.Start(os.Stderr)       l, err := net.Listen("tcp", ":5000")       if err != nil {           panic(err)       }       ch := make(chan int)       go logger(ch)       go server(l, ch)       time.Sleep(10 * time.Second)       trace.Stop()}

As you can see, the main process generates a TCP connection that starts the server and logger two child processes. The server is used to listen for an extranet request, and once the request comes in, a handler process is generated to handle the connection. At the same time, handler also communicates through pipelines and logger, logger is responsible for recording the corresponding logs asynchronously.

The input of this program runtime needs to simulate an external request to generate, so I wrote a script:

#!/bin/shi=0while [[ $i -lt 20 ]];do    # 通过nc发起tcp请求。每秒请求一次    echo "hello "$i | nc localhost 5000    sleep 1    ((++i))done

At run time, start the script and start the server or Gotrace.

The results of the gotrace operation are as follows:

As you can see, even though the program ran 10s, only 6 requests were processed. This is because logger takes up the pipe for a long time, which makes the handler run longer than 1.5s.

To solve this problem, we just use the worker pattern described above to improve the concurrency of logger.

Server + Worker

Import ("FMT" "NET" "OS" "Runtime/trace" "Time") func handler (c net. Conn, ch chan int) {ch <-0 time. Sleep (*. microsecond) C.write ([]byte ("OK")) C.close ()}func logger (wch Chan int) {for {fmt. PRINTLN (&LT;-WCH)//This is mainly time-consuming. Sleep (* time.           Millisecond)}}func pool (ch chan int, n int) {WCH: = make (chan int) for i: = 0; i < n; i++ { Go Logger (WCH)} for {WCH <-<-ch}}func Server (l net.           Listener, ch Chan int) {for {c, err: = L.accept () if err! = Nil {Continue } Go Handler (c, CH)}}func main () {trace. Start (OS. STDERR) L, err: = Net.        Listen ("TCP", ":") if err! = Nil {panic (ERR)} ch: = make (chan int) go Pool (CH, 36) Go server (L, CH) time. Sleep (Ten * time. Second) trace.Stop ()} 

The pool function is similar to the previous example, which is to generate a lot of workers, and then the data generated in the handle is first entered into the pool and assigned to these workers by the pool.

The 3D figure is as follows:

You can see that at this point the server is processing just 10 requests. No longer delayed by logger.

Concurrency & Parallelism

Note My topic is concurrent (concurrent) design patterns. So what's the difference between concurrency and parallelism?

    • Concurrency:a condition that exists when at least, the threads is making progress. A more generalized form of parallelism that can include time-slicing as a form of virtual parallelism.
      Concurrency (concurrency): is a generalized parallelism. In the context of concurrence, two threads/tasks can look "like" parallel, but in fact the machine has only one core, and they just share the time blocks. Of course, in the case of multicore, it can be parallel.

    • Parallelism (parallelism): A condition that arises if at least, the threads is executing simultaneously.
      This is the narrow parallel, that is, the thread, the task must be at the same time, otherwise it is not parallelism.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.