GO语言进阶之并发编程 | 青训营笔记

64 阅读3分钟

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

前言

并发程序是指同时执行多个任务的程序,对并发编程的了解与熟练使用是后端编程中必不可少的一项技能,本文将重点学习go语言中的并发运行机制。

并发编程

并发与并行

  • 并发:两个或多个事件在同一时间间隔发生,是同一实体上的多个事件(在同一个处理器上“同时”处理多个任务),并发是一种由于切换间隔短而产生的“同时发生”的错觉,实际上并不是同时。

  • 并行:两个或多个事件在同一时刻发生,是在不同实体上的多个事件(在不同处理器上同时处理多个任务。

二者有很大差别

Go可以充分发挥多核优势 高效运行

image.png

协程与线程

协程:用户态,轻量级线程 栈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的线程池等等,需要我下来多加学习。