Go语言工程实践01—— 并发编程 | 青训营笔记

76 阅读3分钟

1. Go 并发编程(Goroutine、Channel、Sync)

1.1 并发 VS 并行

​ 并发:多线程程序在一个核的cpu上运行(是指多个任务在一段时间内同时发生 )(并发不一定是同时发生,可能是同一时间段内交替发生 )

​ 并行:多线程程序在多个核的cpu上运行(多个任务同时发生 )

Go可以充分发挥多核优势,高效运行

1.2 Goroutine

协程:用户态,轻量级线程,栈KB级别

线程:内核态,线程跑多个协程,栈MB级别

快速打印hello goroutine:0~hello goroutine:4

快速打印:使用协程,同一线程上并行

time.Sleep(time.Second) // 防止主协程直接结束了,打印协程还没来得及执行

  package main
  ​
  import (
      "fmt"
      "time"
  )
  ​
  func hello(i int){
      println("hello goroutine:"+fmt.Sprint(i))
  }
  func main(){
      for i:=0;i<5;i++{
          go func(j int){
              hello(j)
          }(i)
      }
      time.Sleep(time.Second) // 防止主协程直接结束了,打印协程还没来得及执行
  }

// before go coroutine

// after go coroutine

// hello goroutine:4

// hello goroutine:1

// hello goroutine:3

// hello goroutine:0

// hello goroutine:2

1.3 CSP (Communicating Sequential Processes)

​ CSP 是 Communicating Sequential Process 的简称,中文可以叫做通信顺序进程,是一种并发编程模型,是一个很强大的并发数据模型

​ 用于描述两个独立的并发实体通过共享的通讯 channel(管道)进行通信的并发模型

​ GO语言并没有完全实现了CSP模型的所有理论,仅仅是借用了 process和channel这两个概念。

​ process是在go语言上的表现是:goroutine 是实际并发执行的实体,每个实体之间是通过channel通讯来实现数据共享

​ 提倡通过通信共享内存而不是通过共享内存而实现通信

1.4 Channel

Goroutine和Channel是Go语言并发编程的 两大基石。Goroutine用于执行并发任务,Channel用于Goroutine 之间的同步、通信

Channel 在 gouroutine 间架起了一条管道,在管道里传输数据,实现 gouroutine 间的通信;由于它是线程安全的,所以用起来非常方便;channel 还提供 “先进先出” 的特性;它还能影响 goroutine 的阻塞和唤醒

  • Channel 是 Go 语言中一个非常重要的类型,是 Go 里的第一对象。通过 channel,Go 实现了通过通信来实现内存共享。Channel 是在多个 goroutine 之间传递数据和同步的重要手段
  • Channel 分为两种:带缓冲、不带缓冲。对不带缓冲的 channel 进行的操作实际上可以看作 “同步模式”,带缓冲的则称为 “异步模式
  
  make(chan 元素类型,[缓冲大小])
  无缓冲通道:make(chan int)
  有缓冲通道:make(chan int,2)
  
  package main
  ​
  func main(){
      src := make(chan int) // 无缓冲
      dest := make(chan int,3) // 有缓冲
      go func(){
          /* A协程:生产数字,将数字发送到src channel */
          defer close(src)
          for i:=0;i<10;i++{
              src <- i
          }
      }()
  ​
      go func(){
          /* B协程:遍历src数据,通过src channel实现A协程与B协程通信,计算平方,将数字发送到dest channel */
          defer close(dest)
          for i := range src{
              dest <- i * i
          }
      }()
      
      /* 主协程:遍历dest数据,进行复杂操作(此处为打印操作) */
      for i := range dest{
          // 复杂操作
          println(i)
      }
  }

// 0

// 1

// 4

// 9

// 16

// 25

// 36

// 49

// 64

// 81

1.5 并发安全Lock

​ 并发安全Lock 对变量执行2000次操作,5个协程并发执行

  
  package main
  ​
  import (
      "sync"
      "time"
  )
  ​
  var (
      x       int64
      lock    sync.Mutex
  )
  ​
  func addWithLock(){
      for i:=0;i<2000;i++{
          lock.Lock()
          x += 1
          lock.Unlock()
      }
  }
  func addWithoutLock(){
      for i:=0;i<2000;i++{
          x +=1
      }
  }
  func main(){
      x = 0
      for i:=0;i<5;i++{
          go addWithoutLock()
      }
      time.Sleep(time.Second)
      println("WithoutLock:",x)
  ​
      x = 0
      for i:=0;i<5;i++{
          go addWithLock()
      }
      time.Sleep(time.Second)
      println("WithLock:",x)
  }
  ​
  // WithoutLock: 7465
  // WithLock: 10000

1.6 WaitGroup

sync.WaitGroup 是 Go 语言标准库中的一个结构体,用于等待一组 goroutine 完成执行。它的主要作用是等待所有的 goroutine 完成后再继续执行下一步操作,以避免主程序过早退出

sync.WaitGroup 结构体中的 state1 字段包含了一个 counter 计数器,用于记录等待的 goroutine 数量

  
  func ManyGoWait(){
      var wg sync.WaitGroup
      wg.Add(5)
      for i:=0;i<5;i++{
          go func(j int){
              defer wg.Done()
              hello(j)
          }(i)
      }
      wg.Wait()
  }

// hello goroutine:4 hello goroutine:2 hello goroutine:3 hello goroutine:0 hello goroutine:1

  • sync.WaitGroup 的方法

    • Add 方法

      Add 方法用于向 WaitGroup 中添加指定数量的等待的 goroutine

      func (wg *WaitGroup) Add(delta int)

      ​ delta 表示要添加的等待的 goroutine 的数量

      ​ Add 方法会将 delta 值加到 counter 上

    • Done 方法

      Done 方法用于标记一个等待的 goroutine 已经完成

      func (wg *WaitGroup) Done()

      ​ Done 方法会将 counter 减 1

    • Wait 方法

      Wait 方法用于阻塞当前的 goroutine,直到所有的等待的 goroutine 完成

      func (wg *WaitGroup) Wait()

      ​ Wait 方法会检查 counter 的值,如果不为 0,则当前的 goroutine 会被阻塞

      ​ 当 counter 的值为 0 时,阻塞解除,当前的 goroutine 可以继续执行