这是我参加「第三届青训营 -后端场」笔记创作活动的的第2篇笔记。

协程与管道

协程(Goroutine)

  • go不直接支撑创立体系线程,协程是Go程序内部仅有的并发完成方式。
  • 起协程的句子:go func(){……}()
  • 注意主协程(main)完毕后,此程序也就退出了,即便还有一些其他协程在运转。
  • 从古至今,多线程的优化有好多种方式,比较常见的一种是reactor模型:线程池中存储许多线程,需求多创立一个线程来履行任务时就从线程池中选取一个线程来用。
  • 而Go言语采用的是另一个思路:有几个核就跑几个线程,只是某个线程上面有许多协程;协程的切换是不会像线程切换那样有操作体系层面上的开支的,例如线程切换需求切换虚拟地址空间、切换内核栈、切换硬件上下文、CPUcache需求失效,而切换协程彻底没有这些开支。
  • 协程底层的完成原理:根据GMP模型:
  • G:goroutines 表示一个协程
  • M:machine 表示一个线程
  • P:processor 管理器,经过行列管理协程

Go语言快速上手(二) | 青训营笔记
Go语言快速上手(二) | 青训营笔记

  • 根据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的数量

    • 抢占:限制协程履行时长,不会出现饿死现象

    • 大局协程行列:多个线程全满时能够塞入大局协程行列,它是链表完成的,能够塞许多协程,不必怕没地方放协程。

Go语言快速上手(二) | 青训营笔记
Go语言快速上手(二) | 青训营笔记

  • 常用的同步操控机制: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()

Go语言快速上手(二) | 青训营笔记

管道(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"
		}
	}
}

Go语言快速上手(二) | 青训营笔记