day2 go语言并发 | 青训营笔记

65 阅读4分钟

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

1 Go并发介绍

1.1 并发编程遇到的困难

  • 数据竞争
  • 原子性
  • 死锁,活锁问题
  • 并发安全

1.2 Go 如何解决并发的问题

Go语言高性能的原因,其中之一就体现在这个并发性上面

Go的运行时会自动处理并发操作到操作系统线程上,这就是 Goroutine

1.3 并发与并行

对于大部分语言来说,他们使用了一种内核态的,由操作系统提供的解决方案来实现并行 —— 使用线程(Thread)  。线程允许你并行的运行多个程序。

并发与并行不同,其仅是通过对多个不同程序的有效调度,实现了一种在同一时刻完成多个任务的错觉 —— 事实上,同一时刻仍然仅有一个程序在运行。

简而言之,并发是代码的一个属性; 并行是正在运行的程序的一个属性。

1.4 CSP的概念

CSP代表”Communicating Sequential Processes”,简而言之就是一种技术,提倡通信共享内存。相对的,很多其他语言采用内存共享通信。 后者本质不坏,只是在大型复杂的程序中,难以正确使用。

正是因为这个原因,并发才被认为是Go的优势之一:它从一开始就以CSP的原则为基础,因此很容易阅读,编写和推理。

goroutine是Go提供的解决方案的一部分,从CSP概念衍生出的通道——channel和select语句同样是Go中重要的组成部分。

2 Go并发模块

2.1 Goroutines

Go的独特之处在于goutine与Go的运行时深度整合。Goroutine没有定义自己的暂停或再入点; Go的运行时观察着goroutine的行为,并在阻塞时自动挂起它们,然后在它们变畅通时恢复它们。在某种程度上,这使得它们可以抢占,但只是在goroutine被阻止的地方。它是运行时和goroutine逻辑之间的一种优雅合作关系。 因此,goroutine可以被认为是一种特殊的协程。

举个例子,这里用2条路表示线程,车表示协程,假如分别有两辆车进高速收费站,路1上有A,B车,路2上有C,D车,车A因为某种原因阻塞不动,这个时候车B可以观察另外一条路2,在CD结束后,转到路2上去。

goroutines的另一个好处是它们非常轻巧。相比于线程几Mb,协程是Kb级别的

2.2 sync包

WaitGroup 内部维护了一个计数器,并以如下方式工作:

  • 通过调用 Add 方法,向 WaitGroup 的计数器添加指定值;

  • 通过调用 Wait 方法阻塞当前协程,这会使得协程陷入无限的等待;

  • 通过调用 Done 方法使 WaitGroup 内部的计数器 -1,直到计数器值为 0 时,先前被阻塞的协程便会被释放,继续执行接下来的代码或是直接结束运行。

2.3 Channels

Channel,即通道,衍生自Charles Antony Richard Hoare的CSP并发模型,是Go的并发原语,在Go语言中具有极其重要的地位。虽然它可用于同步内存的访问,但更适合用于goroutine之间传递信息。

  1. 可以通过如下方式声明一个 Channel:
ch := make(chan int)

声明了一个名为 ch无缓冲 Channel,无缓存 Channel 意味着,一个数据的发送必须等待另一端代码的接收,如果没有人接收发送的数据,那么发送端便会被永远阻塞。

  1. 可以通过如下方式声明一个带缓冲区的 Channel:
ch := make(chan int, 3)
复制代码

声明一个名为 ch 的有三个缓冲区的 Channel,并指定 Channel 的传输数据类型为 int

这意味着,该 Channel 内可以有最多 3 个数据未被接收方接收,此时,发送方可以直接发送数据而不必收到阻塞,如果超出缓冲区(在本例中为发送第四个数据,且缓冲区被前三个数据占满),则依旧会被阻塞。

2.4 Select

var c1, c2 <-chan interface{}
var c3 chan<- interface{}
select {
case <-c1:
    // Do something
case <-c2:
    // Do something
case c3 <- struct{}{}:
    // Do something
}

所有通道的读取和写入都被同时考虑,以查看它们中的任何一个是否准备好: 如果没有任何通道准备就绪,则整个select语句将会阻塞。当一个通道准备好时,该操作将继续,并执行相应的语句。

3 Go 并发规范

待写...

4 参考