这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天
前言
并发程序是指同时执行多个任务的程序,对并发编程的了解与熟练使用是后端编程中必不可少的一项技能,本文将重点学习go语言中的并发运行机制。
并发编程
并发与并行
-
并发:两个或多个事件在同一时间间隔发生,是同一实体上的多个事件(在同一个处理器上“同时”处理多个任务),并发是一种由于切换间隔短而产生的“同时发生”的错觉,实际上并不是同时。
-
并行:两个或多个事件在同一时刻发生,是在不同实体上的多个事件(在不同处理器上同时处理多个任务。
二者有很大差别
Go可以充分发挥多核优势 高效运行
协程与线程
协程:用户态,轻量级线程 栈MB级别
线程:内核态,线程跑多个协程,栈KB级别
协程Goroutine
Go语言的并发通过goroutine实现。goroutine类似于线程,属于用户态的线程,我们可以根据需要创建成千上万个goroutine并发工作。
Go语言还提供channel在多个goroutine间进行通信。goroutine和channel是 Go 语言并发模式的重要实现基础。
由于其是用户态线程,没有从用户态到核心态的切换的开销,因此goroutine是非常轻量级的线程。其实goroutine和channel之间的关系,相当于进程与队列之间的关系。
Goroutine使用
Go语言中使用goroutine非常简单,只需要在调用函数的时候在前面加上go关键字,就可以为一个函数创建一个goroutine。
一个goroutine必定对应一个函数,可以创建多个goroutine去执行相同的函数。
func hello() {
fmt.Println("Hello Goroutine!")
}
func main() {
hello() //这是我们一般执行代码的逻辑
fmt.Println("main goroutine done!")
}
Channel
单纯地将函数并发执行是没有意义的。函数与函数间需要交换数据才能体现并发执行函数的意义。
Go语言的并发模型是CSP(Communicating Sequential Processes),提倡通过通信共享内存而不是通过共享内存而实现通信
创建channel
ch1 := make(chan int)
ch2 := make(chan bool)
ch3 := make(chan []int) //无缓冲通道
ch4 :=make(chan int,3) //有缓冲通道
通道有发送、接收和关闭三种操作。
ch := make(chan int)
ch <- 10 // 把10发送到ch中
x := <- ch // 从ch中接收值并赋值给变量x
<-ch // 从ch中接收值,忽略结果
close(ch) //关闭ch
线程安全与锁
若采用共享内存实现通信,则会出现多个Goroutine同时操作一块内存资源的情况,这种情况会发生竞态问题
互斥锁是一种常用的解决数据竞态的方法,它能够保证同时只有一个goroutine可以访问共享资源。Go语言中使用sync包的Mutex类型来实现互斥锁。
var x int64
var wg sync.WaitGroup
var lock sync.Mutex
func add() {
for i := 0; i < 5000; i++ {
lock.Lock() // 加锁
x = x + 1
lock.Unlock() // 解锁
}
wg.Done()
}
func main() {
wg.Add(2)
go add()
go add()
wg.Wait()
fmt.Println(x)
}
这样能保证只有一个goroutine能获取资源,而其他goroutine进入等待,有多个goroutine等待时,唤醒策略时随机的。
总结
并发编程这一块涉及的知识点很多,可以深入的东也很多,比如还有很多种类的锁以及go的线程池等等,需要我下来多加学习。