Go语言进阶 | 青训营笔记

134 阅读3分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第一篇笔记

并发:多个事情,在同一时间段内同时发生
并行:多个事情,在同一时间点上同时发生

image.png Go语言最大的特点就是从语言层面支持并发,开发者不用担心并发的底层逻辑、内存管理,只需要编写好自己的业务逻辑即可。在Go语言里,想要编写一个并发程序是非常容易的事情,它不需要额外引用其他的第三方库,只需要使用“go”关键字就可以实现。

1. Goroutine

在Go语言中,每一个并发的执行单元叫作一个goroutine。我们只需要在调用的函数前面添加go关键字,就能使这个函数以协程的方式运行。此时,函数的返回值会被忽略,所以要通过channel来与主线程进行数据交换。

go 函数名(函数参数)

注:协程是用户态的轻量级线程,不受内核调度;线程是任务调度和系统执行的最小单位,需要内核调度。

image.png

2. CSP (Communicating Sequential Processes)

Go语言的并发基于CSP (Communication Sequential Process,通信顺序进程) 模型,CSP模型是用于描述两个独立的并发实体通过共享的通信管道(channel)进行通信的并发模型。

image.png

不要通过共享内存来通信,要通过通信开共享内存
作为刚接触并发编程的初学者,对这句话不是很能理解,查找相关资料,对其解释做如下记录:
如果在一个系统中,两个线程或进程,都可以读写同一块内存空间,这就叫做“内存共享”。直觉上会觉得这种方式非常方便。但问题在于数据冲突。多线程或多进程场景下,多个对象同时访问同一块数据,几乎一定会产生数据冲突。人们通过多年试错和迭代,最终“通过通信来实现进程/线程间交互”的方案脱颖而出,成为了大多数人的共识。它们背后的思想高度相似——先提供一个或多个高性能队列,线程/进程/微服务之间需要访问别人时,不能直接读写别人的数据,而要通过队列提出请求,然后在对方处理请求时再做相应处理。

3. Channel

CSP中channel是一类对象,它不关注发送消息的实体,而关注发送消息时使用的通信管道。 简单来说,CSP模型提倡通过通信来共享内存,而非通过共享内存来通信

image.png

goroutine 互相通信

应用实例:两个线程交替打印奇偶数

func main() {
    ch := make(chan struct{})
    go func() {
        for i := 1; i < 11; i++ {
            ch <- struct{}{}
            //奇数
            if i%2 == 1 {
                fmt.Println("奇数:", i)
            }
        }
    }()

    go func() {
        for i := 1; i < 11; i++ {
            <-ch
            if i%2 == 0 {
                fmt.Println("偶数:", i)
            }
        }
    }()

    time.Sleep(10 * time.Second)
}

channel 解耦

ch :=make(chan T, 100)

可以直接将其理解为队列,正是因为具有缓冲能力,所以我们可以将业务之间进行解耦,生产方只管往 channel 中丢数据,消费者只管将数据取出后做自己的业务。

4. 并发安全Lock

互斥锁是一种常用的控制共享资源的办法,它能够同时保证一个gotroutine可以访问共享资源。go语言中使用sync和metux类型来实现互斥锁。

5. WaitGroup

go语言使用sync.WaitGroup来实现并发同步。主要由三个方法:

  • wg.Add() 计数器+1;
  • wg.Done() 计数器-1;
  • wg.Wait() 等到直到计数器变为0; sync.WaitGroup内部维护着一个计数器,计数器的值可以增加和减少。例如当我们启动了N 个并发任务时,就将计数器值增加N。每个任务完成时通过调用Done()方法将计数器减1。通过调用Wait()来等待并发任务执行完,当计数器值为0时,表示所有并发任务已经完成。