Go语言并发基础 | 青训营

68 阅读1分钟

1.并发 vs 并行 image.png

Go语言能够原生实现多线程程序在多个核的CPU上运行(并行),从而实现高并发的操作。

2.协程 vs 线程 image.png Go语言通过GOROUTINE也就是(Go协程) 实现高并发,协程是一种轻量级的线程,相比开启/关闭线程来说,是非常轻的系统操作,栈只有KB级别。一个线程可以开启多个携程,最后开启上万个携程也不是问题。

通过在函数前面加 go 关键字实现开启协程。例:

go func (<name><type>){
 ……
}

3.Go协程通信过程** Comunicating Sequential Process (CSP)**

image.png

Go语言提倡使用通信共享内存,而不是共享内存实现通信//这里理解可以参考操作系统的相关知识

4.Channel GoRoutine使用channel通信协调,共享内存 image.png 使用make(chan <type>,<缓冲大小>)生成channel,当<缓冲大小>0时,则为无缓冲channel,例:

make(chan int) //无缓冲通道
make(chan int,2) //有缓冲通道

例程:

func Calsqure() {
    src := make(chan int)
    dest :=make(chan int,3)
    go func() {//子协程A(生产):发送数字0~9
        defer close(src)
        for i := 0; I < 10; i++ {
            Src <- i //每次循环将i发送到channel src
            }
    }()
    go func() {//子协程B(生产):计算输入数字的平方,带缓冲
        defer close(dest)
        for i := range src {
            dest <- i * i //每次循环将i * i的值发送到channel dest
        }
    }()
    for i := range dest {//主协程M(消费):输出最后的平方数
        //复杂操作
        println(i)
    }
}

Tips:

  • 此时,使用chanel保证了并发的安全性

  • 子协程A中使用channel src实现了与子协程B的通信,子协程B中使用channel dest实现了与主协程M的通信。

  • 子协程B采用带缓冲的channel的原因是,主协程M的逻辑可能非常复杂,此时生产的速度大于消费的速度,如果不带缓冲,则消费者消费之后,生产者才能继续生产,带缓冲的channel就避免了浪费生产者的生产效率的问题

  1. 并发安全:LOCK //通过共享内存进行通信的方式
  • 使用 lock 的数据类型为sync.Mutex,即生成一个锁的方式为: var Lock sync.Mutex

  • 使用<name>.lock()加锁

  • 使用<name>.unlock()解锁

6.WaitGroup:并发下的同步
为了防止主协程在子协程未运行完其中时关闭,Go语言中的sync包中实现了WaitGroup,主要是实现了一个计数器,计数器在开启协程时加+<delta>,协程关闭时减1,而主协程将阻塞到计数器为0的时候。//delta为int类型的整数
例程:

func ManyGoWait() {
    var wg sync.WaitGroup //新建了一个名为wg的WaitGroup
    wg.Add(5)//计数器的初值为5,即将开启5个协程
    for i :=0; i < 5; i++ {
        go func(j int) {
        defer wg.Done()//方法结束后WaitGroup-1
        hello(j)
        }(i)
    }
    wg.Wait()//阻塞主协程
}