Go并发| 青训营
并发和并行
- 并发是指在同一时间内,多个任务在交替执行,看起来好像是同时执行的。这是因为计算机会在不同的线程之间切换,每个线程都在执行不同的任务;
- 并行是指在同一时间内,多个任务真正的同时执行。这种情况通常发生在多台计算机或多个处理器上,每个处理器都在执行不同的任务。
简单来说呢,并发就是应该处理器处理多个任务,而并行是指多个处理器同时处理多个任务。
下面是并发和并行的对比展示:
并发其实还是相当于串行,CPU在一个时间片里只处理一个任务,只不过这个切换对于用户来说太快以致于没有察觉;而并行才是真正意义上的同时进行多个任务。
进程、线程、协程
进程是程序执行的过程,包括了动态创建、调度和消亡的整个过程,进程是程序资源管理的最小单位。
线程是操作操作系统能够进行运算调度的最小单位。线程被包含在进程之中,是进程中的实际运作单位,一个进程内可以包含多个线程,线程是资源调度的最小单位。
线程在创建时需要消耗一定的系统资源,线程属于内核态,线程的创建、切换、停止都属于比较重量级的系统操作,占用系统栈资源属于MB级别。
协程是属于用户态的,属于轻量级线程,协程的创建、切换、销毁都是由Go语言本身完成,比线程消耗的资源少的多,占用系统栈资源属于KB级别。
一个线程里可以同时执行多个协程,Go可以同时创建上万级别的协程,也是Go支持高并发原因之一。
Go创建协程十分简单,只需要在调用的函数前加一个go关键字即可:
func hello() {
for i := 0; i < 100; i++ {
fmt.Println(i)
}
}
func main() {
go hello()
for i := 0; i < 100; i++ {
fmt.Println(i)
}
}
CSP
CSP(Communicating Sequential Process)模型是上个世纪七十年代提出的,用于描述两个独立的并发实体通过共享的通讯 channel(管道)进行通信的并发模型。 CSP中channel是第一类对象,它不关注发送消息的实体,而关注与发送消息时使用的channel。
Golang 就是借用CSP模型的一些概念为之实现并发进行理论支持,其实从实际上出发,go语言并没有,完全实现了CSP模型的所有理论,仅仅是借用了 process和channel这两个概念。process是在go语言上的表现就是 goroutine 是实际并发执行的实体,每个实体之间是通过channel通讯来实现数据共享。
Golang中推崇不要通过共享内存来通信,而应该通过通信来共享内存:
共享内存会涉及到多个线程同时访问修改数据的情况,那得保证数据的安全性,可见性,那就会加锁,加锁会让并行变为串行,cpu也忙于线程抢锁。 不如换一种方式,把数据复制一份,每个线程有自己的,只要一个线程干完一件事其他线程不用去抢锁了,这就是一种通信方式,把共享的以通知方式交给线程,实现并发。
通道
Golang中的通道叫做Channel,每个通道只允许交换指定类型的数据,使用chan关键字来声明一个通道,使用 close函数来关闭通道。
ch := make(chan 数据类型,[缓冲大小])
close(ch)
在声明的时候是否设置了缓冲大小代表了该通道是否具有缓冲功能。
而通过操作符<- ( -> )来指定通道的方向,实现发送或接收。
ch <- 10 // 发送
x := <- ch // 接收
Lock
Go当然也支持处理共享内存时通过锁来防止数据的竞争问题,提供了两种线程安全的锁机制:
-
sync.Mutex
互斥锁,确保同时只有一个goroutine可以访问共享数据。
var mu sync.Mutex mu.Lock() // 访问共享资源 mu.Unlock() -
sync.RWMutex
读写互斥锁,可以同时允许多个读,但写时独占。读写互斥锁实现了读写分离,相比普通的互斥锁可以提供更高的并发性能。
var mu sync.RWMutex
mu.RLock()
// 读共享资源
mu.RUnlock()
mu.Lock()
// 写共享资源
mu.Unlock()
WaitGroup
sync.WaitGroup是Go语言中的一个常用同步工具,可以用于等待一组goroutine结束。
WaitGroup有3个方法,Add、Done、Wait。
使用方法:当开启协程时调用Add方法增加一个计数,完成时调用Done方法减去一个计数,Wait会一直阻塞直到WaitGroup的值为 0。
func main() {
var wg sync.WaitGroup
// 开启5个计数
wg.Add(5)
for i := 0; i < 5; i++ {
go func(j int) {
// 执行完时调用Donw
defer wg.Done()
hello(j)
}(i)
}
// 等待所有协程执行完
wg.Wait()
}