2
头图

For concurrent operations, we have already learned about the channel channel, synchronization primitive sync package to lock shared resources, Context tracking coroutine/parameter transfer, etc. These are the relatively basic elements of concurrent programming. I believe you already have a good one. master. Today we introduce how to use these basic elements to form a concurrent mode to better write concurrent programs.

for select infinite loop mode

This mode is relatively common, and the examples in the previous article have also been used. It is generally combined with the channel to complete the task. The format is:

for { //for 无限循环,或者使用 for range 循环
  select {
    //通过 channel 控制
    case <-done:
      return
    default:
      //执行具体的任务
  }
}
  • This is a for + select multiplexing concurrency mode. Which case meets the condition will execute the corresponding branch, and will not exit the loop until the exit condition is met.
  • When no exit conditions are met, the default branch will always be executed

for range select limited loop mode

for _,s:=range []int{}{
   select {
   case <-done:
      return
   case resultCh <- s:
   }
  • Generally send the content of the iteration to the channel
  • done channel is used to exit the for loop
  • resultCh channel is used to receive the value of the loop, these values can be passed to other callers through resultCh

select timeout mode

If a request needs to access the server to obtain data, but may not get a response late due to network problems, then you need to set a timeout period:

package main

import (
    "fmt"
    "time"
)

func main() {
    result := make(chan string)
    timeout := time.After(3 * time.Second) //
    go func() {
        //模拟网络访问
        time.Sleep(5 * time.Second)
        result <- "服务端结果"
    }()
    for {
        select {
        case v := <-result:
            fmt.Println(v)
        case <-timeout:
            fmt.Println("网络访问超时了")
            return
        default:
            fmt.Println("等待...")
            time.Sleep(1 * time.Second)
        }
    }
}

operation result:

等待...
等待...
等待...
网络访问超时了
  • The core of select timeout mode is the timeout time set by the time.After function to prevent the select statement from waiting indefinitely due to exceptions

Notice:
Don't write it like this

for {
       select {
       case v := <-result:
           fmt.Println(v)
       case <-time.After(3 * time.Second): //不要写在 select 里面
           fmt.Println("网络访问超时了")
           return
       default:
           fmt.Println("等待...")
           time.Sleep(1 * time.Second)
       }
   }

case <- time.After(time.Second) is the timeout time of this monitoring action, which means that it will be valid only in this select operation, and the time will restart again if you select again, but there is default, then the case timeout operation , Certainly can't be implemented.

Context's WithTimeout function timeout cancellation

package main

import (
    "context"
    "fmt"
    "time"
)
func main() {
    // 创建一个子节点的context,3秒后自动超时
    //ctx, stop := context.WithCancel(context.Background())
    ctx, stop := context.WithTimeout(context.Background(), 3*time.Second)

    go func() {
        worker(ctx, "打工人1")
    }()
    go func() {
        worker(ctx, "打工人2")
    }()
    time.Sleep(5*time.Second) //工作5秒后休息
    stop() //5秒后发出停止指令
    fmt.Println("???")
}

func worker(ctx context.Context, name string){
    for {
        select {
        case <- ctx.Done():
            fmt.Println("下班咯~~~")
            return
        default:
            fmt.Println(name, "认真摸鱼中,请勿打扰...")
        }
        time.Sleep(1 * time.Second)
    }
}

operation result:

打工人2 认真摸鱼中,请勿打扰...
打工人1 认真摸鱼中,请勿打扰...
打工人1 认真摸鱼中,请勿打扰...
打工人2 认真摸鱼中,请勿打扰...
打工人2 认真摸鱼中,请勿打扰...
打工人1 认真摸鱼中,请勿打扰...
下班咯~~~
下班咯~~~
//两秒后
???
  • In the above example, we used the WithTimeout function to timeout cancellation, which is a recommended way to use

Pipeline mode

The Pipeline mode has also become the pipeline mode, which simulates the pipeline generation in reality. Let's take assembling a mobile phone as an example, assuming there are only three processes: parts procurement, assembly, and packaging of finished products:

parts procurement (process 1)-"assembly (process 2)-"packing (process 3)

package main

import (
    "fmt"
)

func main() {
    coms := buy(10)    //采购10套零件
    phones := build(coms) //组装10部手机
    packs := pack(phones) //打包它们以便售卖
    //输出测试,看看效果
    for p := range packs {
        fmt.Println(p)
    }
}

//工序1采购
func buy(n int) <-chan string {
    out := make(chan string)
    go func() {
        defer close(out)
        for i := 1; i <= n; i++ {
            out <- fmt.Sprint("零件", i)
        }
    }()
    return out
}

//工序2组装
func build(in <-chan string) <-chan string {
    out := make(chan string)
    go func() {
        defer close(out)
        for c := range in {
            out <- "组装(" + c + ")"
        }
    }()
    return out
}

//工序3打包
func pack(in <-chan string) <-chan string {
    out := make(chan string)
    go func() {
        defer close(out)
        for c := range in {
            out <- "打包(" + c + ")"
        }
    }()
    return out
}

operation result:

打包(组装(零件1))
打包(组装(零件2))
打包(组装(零件3))
打包(组装(零件4))
打包(组装(零件5))
打包(组装(零件6))
打包(组装(零件7))
打包(组装(零件8))
打包(组装(零件9))
打包(组装(零件10))

Fan-in and fan-out mode

After the mobile phone assembly line was running, it was found that the accessory assembly process was relatively time-consuming, which resulted in the corresponding slowdown of process 1 and process 3. In order to improve performance, process 2 increased two shifts of manpower:

image.png

  • According to the schematic diagram, the red part is fan-out , and blue part is fan-in

Improved pipeline:

package main

import (
    "fmt"
    "sync"
)

func main() {
    coms := buy(10)    //采购10套配件
    //三班人同时组装100部手机
    phones1 := build(coms)
    phones2 := build(coms)
    phones3 := build(coms)
    //汇聚三个channel成一个
    phones := merge(phones1,phones2,phones3)
    packs := pack(phones) //打包它们以便售卖
    //输出测试,看看效果
    for p := range packs {
        fmt.Println(p)
    }
}

//工序1采购
func buy(n int) <-chan string {
    out := make(chan string)
    go func() {
        defer close(out)
        for i := 1; i <= n; i++ {
            out <- fmt.Sprint("零件", i)
        }
    }()
    return out
}

//工序2组装
func build(in <-chan string) <-chan string {
    out := make(chan string)
    go func() {
        defer close(out)
        for c := range in {
            out <- "组装(" + c + ")"
        }
    }()
    return out
}

//工序3打包
func pack(in <-chan string) <-chan string {
    out := make(chan string)
    go func() {
        defer close(out)
        for c := range in {
            out <- "打包(" + c + ")"
        }
    }()
    return out
}

//扇入函数(组件),把多个chanel中的数据发送到一个channel中
func merge(ins ...<-chan string) <-chan string {
    var wg sync.WaitGroup
    out := make(chan string)
    //把一个channel中的数据发送到out中
    p:=func(in <-chan string) {
        defer wg.Done()
        for c := range in {
            out <- c
        }
    }
    wg.Add(len(ins))
    //扇入,需要启动多个goroutine用于处于多个channel中的数据
    for _,cs:=range ins{
        go p(cs)
    }
    //等待所有输入的数据ins处理完,再关闭输出out
    go func() {
        wg.Wait()
        close(out)
    }()
    return out
}

operation result:

打包(组装(零件2))
打包(组装(零件3))
打包(组装(零件1))
打包(组装(零件5))
打包(组装(零件7))
打包(组装(零件4))
打包(组装(零件6))
打包(组装(零件8))
打包(组装(零件9))
打包(组装(零件10))
  1. has nothing to do with business and cannot be regarded as a process, we should call it 160fe1e31730ef component
  2. Components can be reused, similar to this fan-in process, you can use merge components

Futures mode

The processes in the pipeline mode are interdependent, and only the previous process is completed before the next process can start. But some tasks do not need to depend on each other, so in order to improve performance, these independent tasks can be executed concurrently.

The Futures mode can be understood as the future mode. The main coroutine does not need to wait for the result of the sub-coroutine to return, but can do other things first, and wait for the result of the sub-coroutine in the future. wait.

Let’s take hot pot as an example. There is no dependency between the two steps of washing vegetables and boiling water. They can be done at the same time. Finally

Example:

package main

import (
    "fmt"
    "time"
)

func main() {
    vegetablesCh := washVegetables() //洗菜
    waterCh := boilWater()           //烧水
    fmt.Println("已经安排好洗菜和烧水了,我先开一局")
    time.Sleep(2 * time.Second)

    fmt.Println("要做火锅了,看看菜和水好了吗")
    vegetables := <-vegetablesCh
    water := <-waterCh
    fmt.Println("准备好了,可以做火锅了:",vegetables,water)

}
//洗菜
func washVegetables() <-chan string {
    vegetables := make(chan string)
    go func() {
        time.Sleep(5 * time.Second)
        vegetables <- "洗好的菜"
    }()
    return vegetables
}
//烧水
func boilWater() <-chan string {
    water := make(chan string)
    go func() {
        time.Sleep(5 * time.Second)
        water <- "烧开的水"
    }()
    return water
}

operation result:

已经安排好洗菜和烧水了,我先开一局
要做火锅了,看看菜和水好了吗
准备好了,可以做火锅了: 洗好的菜 烧开的水
  1. The biggest difference between a coroutine in Futures mode and an ordinary coroutine is that it can return a result, and this result will be used at some point in the future. Therefore, the operation to obtain this result in the future must be a blocking operation, and it has to wait until the result is obtained.
  2. If your big task can be disassembled into small tasks that are executed independently and concurrently, and the results of these small tasks can be used to get the results of the final big task, you can use the Futures mode.

微客鸟窝
37 声望3 粉丝