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

93 阅读4分钟

这是我参与「第五届青训营 」笔记创作活动的第4天,这篇文章总结Go语言并发编程的内容。

并发编程


赵老师为我们介绍了Go语言在并发编程应用中速度快的原因。
首先高并发作为一个广义上的概念,我们要明白并发与并行的概念区别:
1.并发是指多线程程序在一个核的cpu上运行;
2.并行是指多线程程序在多个核的cpu上运行。
在实际应用中,Go可以充分发挥多核计算机的优势,提高运行效率,说Go语言是为高并发而生的也不为过。

Goroutine(协程,也称轻量级线程)

我们再来讨论一下线程与协程,线程通常会耗费较多的计算机资源,资源消耗通常以“兆”(MB)为单位,协程是相比线程更轻量化的操作,由Go语言进行创建和调度,资源消耗通常以“千”(KB)为单位。线程可以并发的运行多个协程。这也是Go语言适应高并发的原因之一。

image.png

CSP

这里我们顺应引出协程之间的通信,Go语言提倡通过通信共享内存而不是通过共享内存而实现通信,这里我们要知道通道这一概念,CSP(通信顺序进程)通过通道向另一个CSP发送数据,这就是典型的通过通信共享内存。
而对于通过共享内存实现内存实现数据交换,在交换时必须引用互斥量对数据进行加锁,来获取对于临界区的权限,这也容易导致数据竞赛的问题,所以Go语言倾向于通过通信共享内存。

image.png

Channel

对于引用类型通道的创建,我们需要使用到make关键字,其中包括chan元素类型和缓冲大小的设置,由此可分为有无缓冲区的通道的两种类型。

image.png

缓冲区我们可以理解为类似生成消费模型,当两个信息阻塞在缓冲区,缓冲区向Gorountine2发送数据。我们可以举例如下:

image.png

我们注意到B函数使用了一个有缓冲区的队列,这里和我们实际应用场景中生产者和消费者的环境很像,A作为生产者不间断生成提供给B,B分发给消费者需要缓冲时间,所以设置缓冲区进行阻塞。

并发安全Lock

由于Go语言保存了共享内存实现通信的机制,这样就可能存在多个Gorountine同时操作一块内存的情况,即数据竞赛,这里我们以一个实际例子来分析:

image.png

两个函数第一个实现了临界区功能,具体实现方式是我们使用一个sync.Mutex关键字定义临界区加锁lock,这样我们就可以使用lock()函数来获得临界区资源,在临界区内进行计算完成后再使用Unlock()函数释放掉临界区权限。而第二个函数没有使用临界区保护,就结果来看我们发现没有加锁的函数输出的不是我们的预期值,这就是典型的并发安全问题,想要解决就需要我们使用对临界区的控制来实现并发安全。在实际开发过程中,并发安全是有一定可能出现的问题,而且比较难以定位。所以需要开发者在开发过程中避免对共享内存进行一些非并发安全的读写操作。

WaitGroup

这里我们尝试使用WaitGroup来对协程进行优雅的阻塞,常用的有Add、Done、Wait方法,这里也使用一个简单的例子来展示:

image.png

image.png

这里我们使用Add方法对计数器进行设置(初始为5),然后每当函数执行一次就同时执行一次Done方法,最后当函数执行五次后协程发生阻塞。

总结

1.Go语言使用协程实现高并发操作;
2.Go语言提倡使用通信来共享内存;
3.Sync包下的一些关键字的使用与理解。