Summary of go concurrent programming

Keywords: Go

1. Concurrent using sync:

package main
  
  import (
      "fmt"
      "sync"
  )
  
  func main() {
  
      testSlice := []string{"test1", "test2", "test3"}
      wg := sync.WaitGroup{}
      for _, t := range testSlice {
          wg.Add(1)
          go printSlice(t, &wg)
      }   
      wg.Wait()
  }
  
  func printSlice(s string, wg *sync.WaitGroup) {
      defer wg.Done()
      fmt.Printf("this is %+v\n", s)
  }          

The core of sync package concurrency is waitgroup, which initializes a pointer of WaitGroup type. When concurrency is needed, add counts by using Add method, and pass them in as parameters in functions that need concurrency. When this function operation is completed, call Done method to subtract counts; in the main process, Wait method is used to subtract counts. The number of counters will be monitored all the time. When the counter is zero, all concurrent functions are completed, and then the main coprocess can exit. Personally, I feel that this is the simplest way to achieve concurrency, especially when all data in a set need to be processed concurrently.

2. Using pipelines for concurrency

Pipeline is the data type originally supported by go, and it can also achieve concurrent effect.
The general idea is that when the data in the pipeline is retrieved from the main consortium, the pipeline will be blocked, and the data will be stuffed into the pipeline during the transmission consortium, and the pipeline will be closed after the data is blocked; when the data can not be retrieved from the main consortium, the pipeline will be closed, and the task will be completed.

package main

  import "fmt"

  var channel = make(chan int, 10)

  func main() {
      go func() {
          for i := 0; i < 10; i++ {
              channel <- i
          }
          close(channel) //After putting the data, we must close chan, otherwise it will be deadlocked.
      }()

      for v := range channel {
          fmt.Println(v)
      } //The main routine will not quit because it will be blocked all the time if it cannot be retrieved from chan in the main routine.
      //If another one is blocked in another one, but the main one is not blocked, and the data acquisition process is not yet reached, the main one will quit.
  }

In fact, the above example does not reflect concurrent execution, because ten numbers are inserted into the pipeline in sequence, and the main coprocess takes data out of the pipeline in sequence, or is a serial process.

Advanced Edition

package main

  import (
      "fmt"
  )

  var channel = make(chan int)
  var result = make(chan int)

  func main() {
      go func() {
          for i := 0; i < 100; i++ {
              channel <- i
          }
          close(channel)
      }()

      ct := 0
      for c := range channel {
          go func(i int) {
              result <- i + i
          }(c)

      }

      for v := range result {
          ct++
          fmt.Println(v)
          if ct == 100 {
              break
          }
      }
  }

After putting the data into the pipeline in order, traversing the pipeline, each time a data is extracted, a new co-operation is opened to do the calculation (i+i), then the result is put in the result queue, and finally the value of the result pipeline is taken out in the main co-operation. Because the result pipeline needs to be closed, but the closing position is not determined. The time to jump out of the loop is decided by the amount of data calculated. You can also discuss when to close the result pipeline.

Note that closing the pipeline and monitoring the value of the pipeline need to be two different processes. If both operations are in one process, either a closed protocol or an unclosed protocol will be monitored, an exception will occur.

Here, the capacity of the pipeline is 0, that is, each time the data into the pipeline plug needs to be consumed before it can continue to plug; the capacity of the protocol can be plugged into the number of capacity data, and then the data needs to be blocked until there is space;

3. select keyword

Look at the code first.

    start := time.Now()
    c := make(chan interface{})
    ch1 := make(chan int)
        ch2 := make(chan int)

    go func() {

        time.Sleep(4*time.Second)
        close(c)
    }()

    go func() {

        time.Sleep(3*time.Second)
        ch1 <- 3
    }()

      go func() {

        time.Sleep(3*time.Second)
        ch2 <- 5
    }()

    fmt.Println("Blocking on read...")
    select {
    case <- c:
        fmt.Printf("Unblocked %v later.\n", time.Since(start))
    case <- ch1:
        fmt.Printf("ch1 case...")
      case <- ch2:
        fmt.Printf("ch2 case...")
    default:
        fmt.Printf("default go...")
    }

Run the above code because the current time is not yet 3s. So, at present, the program will go default.
If default, ch1 and CH2 branches are commented out, a random execution will be selected. If the sleep time of ch1 and CH2 is changed to 10 seconds, the c branch will be executed.

Namely

  • If one or more pipeline operations can be completed, the Go runtime system will randomly select an execution. Otherwise, if there is a default branch, the default branch statement will be executed. If there is no default, the select statement will be blocked until at least one pipeline operation can be performed.
  • There needs to be a real goroutine. If all the subgoroutines for pipeline operations have exited, select {} will report to panic.

Some references: https://www.jianshu.com/p/2a1...

Posted by kendhal on Sun, 18 Aug 2019 07:27:04 -0700