这是我参与「第五届青训营 」伴学笔记创作活动的第 6 天
前言
大家好呀,这是我参加青训营伴学笔记创作活动的第 6 天,如存在问题,烦请各位斧正!
其中有一些关键图片超过了最大字符限制,不能上传了,我都使用特殊标记给它标记出来了,如有需要,请联系我。
goroutine
1)在Go语言里,想要编写一个并发程序是非常容易的事情,它不需要额外引用其他的第三方库,只需要使用"go"关键字就可以实现。
2)优势:goroutine是Go并发设计的核心,也叫协程,它比线程更加轻量,因此可以同时运行成千上万个并发任务。
不仅如此,Go语言内部已经实现了goroutine之间的内存共享,它比线程更加易用、高效和轻便。
3)与线程相比:
(1)协程处于用户态、线程处于内核态,不需要有内核态的切换。 协程相当于轻量级线程。
(2)协程的切换时间点是由调度器决定,而不是由系统内核决定的。
(3)对垃圾回收有好处:如果垃圾回收时使用的是线程,那么系统势必会暂停所有线程。
但如果使用协程,调度器知道什么时候内存位于一致状态,所以也就没有必要暂停所有运行的线程。
(4)线程一般是固定的栈容量2MB,用于保存局部变量,在函数切换时使用。
但是对于goroutine来说,大小固定的栈可能会导致资源浪费,所以Go采用了动态扩张收缩的策略,初始化为2KB,最大可扩张到1GB。
4)Go语言的并发基于CSP(通信顺序进程)模型,CSP模型是用于描述两个独立的并发实体通过共享的通信管道(channel)进行通信的并发模型。
<图片:超过最大字节限制>
5)使用:在调用的函数前面添加go关键字,就能使这个函数以协程的方式运行。(调度器会自动将其安排到合适的系统线程上去执行。)
channel
概述
1)channel是golang在goroutine之间的通讯方式
2)channel是引用类型,使用的时候必须通过make进行初始化,make的channel打印结果是地址。
3)背景:全局变量加锁的方式来解决goroutine通讯的方式不完美,
主线程在等待所有goroutine全部完成的时间很难确定,所以这里需要一个管道channel来完成这种通讯连接。
4)channel的本质就是一个先进先出的数据结果队列,
它本身是线程安全的,因为本身就是阻塞模式,所以多个goroutine访问时,不需要加锁。
5)channel接收参数结束后,必须关闭,避免channel不断增加带来的内存泄漏。
(关闭channel的时候不要在接收端关闭channel)
使用
1)创建channel:
make(chan 元素类型, 容量)
// 不指定容量或容量为0 则为非缓冲区的channel,这必须在main以外的goroutine里写入,否则会报错。比如
ch := make(chan int, 3)
2)发送:
ch <- 10
// 把10传递给ch3)接收:
x := <-ch
// 从ch中接收值并赋值给变量x4)关闭管道:
close(ch)
// 一定要关闭管道,建议创建后使用defer关闭
select
概述
1)Go的select语句是一种仅能用于channl发送和接收消息的专用语句,此语句运行期间是阻塞的;当select中没有case语句的时候,会阻塞当前groutine。
2)select是Golang在语言层面提供的I/O多路复用的机制,其专门用来检测多个channel是否准备完毕:可读或可写。
3)select语句中除default外,每个case操作一个channel,要么读要么写。
4)select语句中除default外,各case执行顺序是随机的。
5)select语句中如果没有default语句,则会阻塞等待任一case。
6)select语句中读操作要判断是否成功读取,因为关闭的channel也可以读取。
基本使用
1)声明两个channel:chan1 := make(chan int) 和 chan2 := make(chan int)
2)
go func() { // 同样再有一个func来向chan2写入
chan1 <- 1
time.Sleep(5 * time.Second)
}()
3)
select {
// select语句两个case分别检测chan1和chan2是否可读,如果都不可读则执行default语句。
case <-chan1:
fmt.Println("chan1 ready.")
case <-chan2:
fmt.Println("chan2 ready.")
default:
fmt.Println("default")
}
4)select中各个case执行顺序是随机的:
(1)如果某个case中的channel已经ready,则执行相应的语句并退出select流程。
(2)如果所有case中的channel都未ready,则执行default中的语句然后退出select流程。
(3)并且由于协程和select语句并不能保证执行顺序,所以也有可能select执行时协程还未向channel中写入数据,所以select直接执行default语句并退出。
(4)假如select语句中如果没有default语句,则会阻塞等待任一case。
runtime包
概述
Go语言中就是runtime包实现了协程的小型任务调度器。
以下主要介绍三个函数:
Gosched()
、Goexit()
、GOMAXPROCS()
。1)
runtime.Gosched()
函数:使当前Go协程放弃处理器,以让其他Go协程运行。它不会挂起当前Go协程,因此当前Go协程未来会恢复执行。2)
Goexit()
终止调用它的Go协程,但其他Go协程不会受影响。Goexit()会在终止该Go协程前执行所有defer的函数。3)
GOMAXPROCS(n int)
函数设置可同时执行的最大CPU数,并返回先前的设置。若n < 1,它就不会更改当前设置。(Go语言程序默认会使用最大CPU数进行计算。)
拓展
Go语言的协程是抢占式调度的,当遇到长时间执行或者进行系统调用时,会主动把当前goroutine的CPU转让出去,让其他goroutine能被调度并执行。
一般出现如下几种情况,goroutine就会发生调度:
(1)syscall
(2)C函数调用(本质上和syscall一样)
(3)主动调用runtime.Gosched
(4)某个goroutine的调用时间超过100ms,并且这个goroutine调用了非内联的函数。