Go语言并发编程 | 青训营笔记

102 阅读2分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天

GO为并发而生

高效的并发模型

image.png

image.png

并发是多个线程轮询使用一个CPU,并行是多个线程同时使用多个CPU。

并行的效率高于并发。

Go语言实现的调度模型,可以充分发挥多核性能,高效运行。

协程

简介

作用

原理

线程和线程区别

  • 线程
    • 昂贵的系统资源
      • 栈MB级别
    • 内核态
    • 创建、切换、停止都是很重的操作。
  • 协程
    • 轻量级线程
      • 栈KB级别
      • 一次可以创建上万的协程
    • 用户态
    • 创建、切换、停止都是由GO语言完成

使用

创建协程

调用函数时,前面加 go 关键字 例如:

// 快速打印 hello goroutine : 0~hello goroutine : 4
func hello(i int) {
    println("hello goroutine : " +fmt.Sprint(i))
}

func HelloGoRoutine() {
    for i := 0; i < 5; i++ {
        go func(j int) {
            hello(j)
        }(i)
    }
    time.Sleep(time.Second)
}

通过 channel 通信

简介

通过通信来共享内存,而不是通过共享内存来通信。

这是 Go 线程通信的设计思想。

具体实现则是 Go 中的 channel。

channel 就像所有协程共享的队列,里面存放的消息先进先出,协程能往 channel 里存数据,也能从 channel 中取数据,可以让一个 gorountine 发送特定的值到另一个 goroutine。

创建 channel

API

make(chan 元素类型,[缓冲大小])

根据是否有缓冲区,可以分为无缓冲通道、有缓冲通道。

示例

// 创建一个无缓冲区的 channel
src := make(chan int)
// 创建一个缓冲为3个字节的 channel
dest := make(chan int, 3)

应用实例

func CalSquare() {
    src := make(chan int)
    dest := make(chan int, 3)
    // 生产者
    go func() {
        defer close(src)
        for i := 0; i < 10; i++ {
            src <- i
        }
    }()
    // 消费者
    go func() {
        defer close(dest)
        for i := range src {
            dest <- i * i
        }
    }()
    // 生产者往 src put 的逻辑简单,而 println 打印消费流逻辑复杂。
    // 所以消费者消费速度慢,给 dest 设置缓冲区,避免生产者等着消费者消费而阻塞。
    for i := range dest {
        // 复杂操作
        println(i)
    }
}

通过临界区通信

简介

通过互斥量对内存进行加锁操作,同一时刻,只允许一个线程访问该内存区域。

缺点是多个 goroutine 访问临界区,会互相争抢。

创建临界区

API

使用 sync.Mutex 实例

// 创建 sync.Mutex 实例
lock sync.Mutex

// 加锁
lock.Lock()

// 临界区代码

// 解锁
lock.UnLock()
示例
var (
    x int64
    lock sync.Mutex
)

func addWithLock() {
    for i := 0; i < 20000; i++ {
        lock.lock()
        x += 1
        lock.UnLock()
    }
}

func addWithoutLock() {
    for i := 0; i < 2000; i++ {
        x += 1
    }
}

通过 WaitGroup 阻塞当前线程

简介

WaitGroup 计数器归零前,阻塞当前线程。

API

WaitGroup 实际上维护了一个计数器。

WaitGroup
    Add(delta int) // 计数器 + delta
    Done()         // 计数器 - 1
    Wait()         // 阻塞直到计数器为0

实例

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()
}