这是我参加「第三届青训营 -后端场」笔记创作活动的的第2篇笔记。
协程与管道
协程(Goroutine)
- go不直接支撑创立体系线程,协程是Go程序内部仅有的并发完成方式。
- 起协程的句子:go func(){……}()
- 注意主协程(main)完毕后,此程序也就退出了,即便还有一些其他协程在运转。
- 从古至今,多线程的优化有好多种方式,比较常见的一种是reactor模型:线程池中存储许多线程,需求多创立一个线程来履行任务时就从线程池中选取一个线程来用。
- 而Go言语采用的是另一个思路:有几个核就跑几个线程,只是某个线程上面有许多协程;协程的切换是不会像线程切换那样有操作体系层面上的开支的,例如线程切换需求切换虚拟地址空间、切换内核栈、切换硬件上下文、CPUcache需求失效,而切换协程彻底没有这些开支。
- 协程底层的完成原理:根据GMP模型:
- G:goroutines 表示一个协程
- M:machine 表示一个线程
- P:processor 管理器,经过行列管理协程
- 根据GMP模型,协程运转在线程上
- 一个协程中的信息:运转栈+寄存器数值(PC,BP,SP)
- 协程的切换,只是需求改变寄存器的数值,cpu便会从需求切换的协程指定位置持续运转
- 协程与线程的比例关系:N:M
协程:线程 | 含义 | 优点 | 缺陷 |
---|---|---|---|
1:1 | 一个协程在一个线程上运转(其实就是传统的多线程) | 利用多核 | 上下文切换比较慢(reactor模型,代价较大) |
N:1 | 多个协程在一个线程上运转 | 上下文切换较快 | 1.无法充分利用多核2.饥饿,如果一个协程不完毕,其余协程堵塞 |
N:M | 多个协程在多个线程上运转 | 充分利用多核,上下文切换快 | 对完成要求更高 |
-
协程调度器的设计策略(削减开支、兼顾公平):
-
复用线程(防止频频的创立、销毁线程,而是对线程的复用)
- work stealing机制:当本线程无可运转的协程时,尝试从其他线程绑定的P盗取G,而不是销毁线程。
- hand off机制:当本线程由于G进行体系调用堵塞时,线程开释绑定的P,把P转移给其他空闲的线程履行。
-
利用并行:GOMAXPROCS设置P的数量
-
抢占:限制协程履行时长,不会出现饿死现象
-
大局协程行列:多个线程全满时能够塞入大局协程行列,它是链表完成的,能够塞许多协程,不必怕没地方放协程。
-
-
常用的同步操控机制:WaitGroup
- 开发过程中,常常遇到多task之间的同步问题。例如,多个子task并发完成一部分任务,主task等待他们最后完毕。
var wg sync.WaitGroup ;
for i := 0;i<3;i++ {
wg.Add(1)
go func(i int){
wg.Done()
}(i)
}
wg.Wait()
管道(Channel)
-
并发模型CSP,全称Communicationg Sequential Processes。它的中心观念是将两个并发履行的实体经过管道连接起来,一切的消息都经过管道传输。
-
管道(通道),也是一种Go的数据同步技能。它能够被看作是在一个程序内部的一个先进先出(FIFO:first in first out) 数据行列。
-
管道的操作有读、写和关闭。
- 界说:ch := make(chan string)
- 读:a = <- ch
- 写: ch <- “hello”
- 写一个现已关闭的channel会引发panic
-
管道分类:无缓冲管道&缓冲管道
-
无缓冲管道:长度为0的channel,为不带buffer的channel
- ch := make(chan int)
- 不会产生额定的仿制
- 读在写前
-
有缓冲管道:长度大于0的channel,为带buffer的channel
- ch := make(chan int,10)
- 会产生额定的仿制
- 写在读前 ch<- 1
- 缓冲区最大为65535
-
管道元素的传递,是仿制,非缓冲区管道仿制了1次,缓冲区管道仿制了2次
-
例如面试常考的:请用管道完成替换打印AB:
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
ch3 := make(chan string)
go printA(ch1, ch2)
go printB(ch1, ch2, ch3)
<-ch3
}
func printA(ch1, ch2 chan string) {
for i := 0; i < 100; i++ {
<-ch2
fmt.Println(i, "A")
ch1 <- "print A"
}
}
func printB(ch1, ch2, ch3 chan string) {
ch2 <- "begin"
for i := 0; i < 100; i++ {
<-ch1
fmt.Println(i, "B")
if i != 99 {
ch2 <- "print B"
} else {
ch3 <- "end"
}
}
}