Golang – Channel & Routine – 通道/协程使用以及学习总结

Golang - Channel & Routine - 通道/协程使用以及学习总结

Go 中,可以利用通道以及协程来实现高并发。

转载请注明来源:https://janrs.com/zide

常用高并发协程库工具:panjf2000/ants


协程

有关协程查看博文:Golang 协程/线程/进程 区别以及 GMP 详解

Channel 通道

概念

  • 作用:用于在协程之间进行通信。
  • 通道分为有缓冲、无缓冲通道。有数据长度(len)以及容量(cap)。设置了缓冲区后,发送端和接收端处于异步的状态;反之是同步状态。
  • 单双向通道:单向通道分为只读(<-chan)只写(chan<-)通道;既可读又可写为双向通道。
  • 遍历通道。遍历通道需要关闭(close)通道,再使用(for-rang)进行遍历。否则阻塞。

阻塞场景

  1. 有无缓冲通道中无数据,但执行读通道。
  2. 无缓冲通道中无数据,向通道写数据,但无协程读取。
  3. 有缓冲通道的缓存已经占满,向通道写数据,但无协程读。

sync.WaitGroup


概念

一个主任务里面,有多个并行的子任务。sync.WaitGroup 就是子任务的集合,用来控制主任务需要等待多个并行的子任务执行后才能继续往下执行。也就是起到阻塞的作用。

主要有三个方法:Add ,Done,Wait

  • Add 方法用来设置并行子任务的数量
  • Done 方法用来当一个子任务执行完毕后,扣除一个子任务数量
  • Wait 方法用来阻塞。只有当所有的子任务都完成后,主任务程序才会继续往下执行

Demo 代码

package main

import (
    "flag"
    "fmt"
    "sync"
)

//channel练习
// 1. 启动一个goroutine,生成100个随机数发送到ch1
// 2. 启动一个goroutine,从ch1通道中读取值,然后计算其平方,放到ch2中
// 3. 在main中,从ch2取值打印出来

/*
知识点:
1、开启多个goroutine,执行同一个函数的时候,需要开启 sync.Once ,保证某个操作只执行一次
        var once sync.Once
        once.Do(func() { close(ch2) })

2、开启多个goroutine的时候,需要开启线程等待。
    var wg sync.WaitGroup
    wg.Done()
    wg.Add(3)
    wg.Wait()

3、只读、只写chan
*/

var wg sync.WaitGroup
var once sync.Once

// 只写通道
func wChan(ch chan<- int) {
    defer wg.Done()
    for i := 0; i < 100; i++ {
        ch <- i
    }
    close(ch)
}

// 只读通道和只写通道
func rChan(ch1 <-chan int, ch2 chan<- int) {
    defer wg.Done()
    for {
        x, ok := <-ch1
        if !ok {
            break
        }
        ch2 <- x * x
    }
    once.Do(func() {
        close(ch2)
    })
}

// main main
func main() {
    //flag.Parse()
    //server.Run(*cfg)

    a := make(chan int, 100)
    b := make(chan int, 100)

    // 开启三个协程数量
    wg.Add(3)

    go wChan(a)
    go rChan(a, b)
    go rChan(a, b)

    // Wait 进行阻塞
    wg.Wait()

    // 读取数据
    for result := range b {
        fmt.Println(result)
    }
}

生产者消费者模型

在工作中会遇到一些需要并发的,且没有逻辑上的先后顺序,就可以使用生产者消费者模型来提供并发能力。

生产者消费者模型:某个模块(函数等〉负责产生数据,这些数据由另一个模块来负责处理(此处的模块是广义的,可以是类、函数、协程、线程、进程等)。产生数据的模块,就形象地称为生产者;而处理数据的模块,就称为消费者。
单单抽象出生产者和消费者,还够不上是生产者消费者模型。该模式还需要有一个缓冲区处于生产者和消费者之间,作为一个中介。生产者把数据放入缓冲区,而消费者从缓冲区取出数据。

要点:

  • producer 是生产者,里面批量生产业务数据,放到一个有缓冲管道里。
  • consumer 是消费者,从通道里面获取数据进行消费。

Demo 代码如下:

package main

import (
    "flag"
    "fmt"
)

type OrderInfo struct {
    Id int
}

// main main
func main() {
    ch := make(chan OrderInfo, 5)
    go producerOrder(ch)
    consumerOrder(ch)
}

// 生产订单 - 只写通道
func producerOrder(ch chan<- OrderInfo) {
    // 创建订单后关闭通道
    // 如果不关闭,消费者就会一直阻塞,等待读
    defer close(ch)
    // 创建订单的逻辑代码
    for i := 0; i < 10; i++ {
        order := OrderInfo{Id: i + 1}
        fmt.Println("创建了一个新订单,订单 ID 为:", order.Id)
        // 将订单数据写入通道
        ch <- order
    }
}

// 消费订单 - 只读通道
func consumerOrder(ch <-chan OrderInfo) {
    for order := range ch {
        handleOrder(order)
    }
}

func handleOrder(order OrderInfo) {
    // 处理订单业务逻辑...
    fmt.Println("订单:", order.Id, " 处理完毕")
}

转载请注明来源:https://janrs.com/zide