这是我参与「第五届青训营 」伴学笔记创作活动的第2天
2. GO并发基础
2.1 并发概念
广义上的并发是指多个程序同时进行。
- 并发:单个CPU分时执行多个程序,营造了一种同时进行的错觉。
- 并行:多个CPU同时执行。
2.2 GO中的并发
GO支持两种实现并发的方法(侧重讲解CSP模型中的常用方法)
- 顺序通信进程(CSP):采用了使用通道Channel作为多个并发程序的通讯机制来实现程序间共享信息。
- 多线程共享内存:在多个程序间开辟一块所有程序都能使用的内存来实现程序间的通讯。
2.3 Goroutines
2.3.1 Goroutines是什么
Goroutines是协程的一种实现方式,下面是进程,线程,协程的差异:
- 进程:是正在运行的程序,是由操作系统将程序加载到内存中后具备了状态的一种实体,其中操作系统维护了进程id,状态,寄存器,内存,栈,执行文件信息...等相当多的关于整个程序运行的信息。
- 线程:是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位.其中操作系统维护了状态,栈,寄存器等相对进程少的多的信息,这些包含了线程能够跑起来的必须信息。
- 协程:是一种新的概念,它不由操作系统调度,转而仅仅在进程中保存必须的栈,寄存器信息来实现在用户层面的并发。
问:为什么GO要使用协程来支持并发? 答:由于多进程,线程是由操作系统来负责统一调度的,这就带来了操作系统级别的上下文切换(把将结束的线程信息保存起来,把即将执行的线程现场信息恢复),这种系统开销对于今天相当多很简单的并发任务来说是相当重的。而协程具有最小的上下文切换代价,对于日益增加的高并发需求有着天生的优势。
2.3.2 Goroutines的使用
采用go关键字来使用
// 串行执行func(),并等待返回结果
func()
// 创建一个新的协程,它将会和main一起并发的执行
go func()
2.4 Channels
channels是Goroutines之间的通信机制。 它可以让一个Goroutine通过它给另一个Goroutine发送信息。
2.4.1 声明
使用make函数可以创建channel channel根据是否有缓存分为无缓存的和有缓存的channel:
- 无缓存:一个基于无缓存Channel的发送操作将导致发送者Goroutine阻塞,直到另一个Goroutine在相同的Channels上执行接收操作。反之,如果接收操作先发生,那么接收者Goroutine也将阻塞,直到有另一个Goroutine在相同的Channel上执行发送操作。常常用来实现同步。
- 有缓存:带缓存的Channel内部持有一个元素队列。向缓存Channel的发送操作就是向内部缓存队列的尾部插入元素,接收操作则是从队列的头部删除元素。如果内部缓存队列是满的,那么发送操作将阻塞直到因另一个goroutine执行接收操作而释放了新的队列空间。相反,如果channel是空的,接收操作将阻塞直到有另一个goroutine执行发送操作而向队列插入元素。
//ch1是一个能够传递int变量的channel,并且它是无缓存的
ch1 := make(chan int)
//ch2拥有三个元素的缓存队列
ch2 := make(chan int,3)
1.4.2 常见用法
//向chan写入一个数据10,该操作将导致该Goroutine阻塞至有下一个Goroutine接收该数据
ch1 <- 10
//另一个Goroutine,num的值为10,上一个Goroutine将不在阻塞继续执行
num := <-chan
//关闭该chan
close(ch1)
1.5 例子:用Goroutine和Channel实现1-10的平方计算
func main() {
first := make(chan int, 3)
second := make(chan int, 3)
//定义并创建一个生产者协程
go func() {
defer close(first)
for i := 1; i <= 10; i++ {
first <- i
}
}()
//定义并创建一个消费者协程
go func() {
defer close(second)
for i := range first {
second <- i * i
}
}()
//输出操作
for i := range second {
fmt.Println(i)
}
}