Golang总结"/>
Golang总结
并发
协程
package main
import ( "fmt" "time"
)
func say(s string) { for i := 0; i < 5; i++ { time.Sleep(100 * time.Millisecond) fmt.Println(s) }
}
func main() { go say("world") say("hello")
}
输出
world ....... world world hello 他们是两个goroutine在执行
goroutine调度
GPM是Go语言运行时层面的实现,是go语言自己实现的一套调度系统。
1.G很好理解,就是个goroutine的,里面除了存放本goroutine信息外,还有与所在P的绑定等信息。当启动一个 G 时,会优先放入到一个P的本地队列中,P会从线程池里拿一个M(没有则创建)来运行里面的G。
2.P代表M运行G所需要的资源(P不是cpu核心;P数量默认等于cpu核心数,但是可以通过环境变量修改,实际与cpu核心数没有关联;P数量=线程数。一个P维护着一个G队列,这个本地队列用于存储等待执行的G列表,数量不超过256个,如果队列满了,则新建的G将会被放入到全局队列中。如果队列空了,与之绑定的M会从其他队列或全局队列中拿一部分G过来放到自己本地的队列中,并执行。
3.M 对内核线程的封装,用来运行G,数量等于CPU核数,M需要绑定一个P才能执行里面的G(线程是CPU执行的最小单位),一个P也只能同时绑定一个M去执行里面的G。如果G阻塞时,执行的M也会阻塞,那么P就会将G留在M,将队列内其他G绑定到另一个可用M继续执行。如果M的G执行完了,找不到其他G,则将G放入到全局队列,M放入空闲线程池里。
一个G的执行需要M和P支持。一个M和一个P关联形成一个有效的G环境(内核线程、上下文环境。每个P都会包含一个可运行的G队列。
执行流程
1、我们通过 go func () 来创建一个 goroutine;
2、有两个存储 G 的队列,一个是局部调度器 P 的本地队列、一个是全局 G 队列。新创建的 G 会先保存在 P 的本地队列中,如果 P 的本地队列已经满了就会保存在全局的队列中;
3、G 只能运行在 M 中,一个 M 必须持有一个 P,M 与 P 是 1:1 的关系。M 会从 P 的本地队列弹出一个可执行状态的 G 来执行,如果 P 的本地队列为空,就会从其它的P队列中偷取G来执行;
4、一个 M 调度 G 执行的过程是一个循环机制,防止其他G等待;
5、当 M 执行某一个 G 时候如果发生阻塞,如果当前有一些 G 在执行,此时会创建新的M或从休眠M队列取出一个M。
6、当M的G执行完则将G放入到全局队列,M放入空闲线程池里。
总结
GMP调度模型设计的核心思想是用较少的线程完成很多Goroutine 的执行,从语言级别支持并发,轻量级协程 Goroutine 。由于P的存在,使得GM,协程也就是用户态线程与内核态线程实现了解耦,当遇到G的阻塞时,不会影响到其他G的使用,P会选择其他M来实现继续执行,提升并发性能。
Channel
通道是用来传递数据的一个数据结构,可以用于两个goroutine之间传一个置顶类型的值来同步的。操作符 <-
用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。
chan := make(chan int)
//这种情况下通道是没有缓冲区的。发送端发送数据同时接收端必须相应的接收。
chan := make(chan int ,100)
//带缓冲区,大小为第二个参数大小。带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。缓冲区满后和上面一样。
v , ok := <-ch
//如果接受不到那么ok就为false,通道可以使用close()关闭
1、发送 将一个值发送到通道中。 ch <- 10 // 把10发送到ch中
2、接收 从一个通道中接收值。
x := <- ch // 从ch中接收值并赋值给变量x <-ch // 从ch中接收值,忽略结果
3、关闭 我们通过调用内置的close函数来关闭通道 close(ch)
通道是可以被垃圾回收机制回收的,它和关闭文件是不一样的,在结束操作之后关闭文件是必须要做的,但关闭通道不是必须的。
关闭后的通道有以下特点
对一个关闭的通道再发送值就会导致panic。 对一个关闭的通道进行接收会一直获取值直到通道为空。(如果通道中还有数据的话) 对一个关闭的并且没有值的通道执行接收操作会得到对应类型的零值。 关闭一个已经关闭的通道会导致panic。
无缓冲管道
1)无缓冲通道上的发送操作会阻塞,直到另一个goroutine在该通道上执行接收操作,这时值才能发送成功,两个goroutine将继续执行。
2)相反,如果接收操作先执行,接收方的goroutine将阻塞,直到另一个goroutine在该通道上发送一个值。
3)使用无缓冲通道进行通信将导致发送和接收的goroutine同步化。因此,无缓冲通道也被称为同步通道。
有缓冲管道
只要通道的容量大于零,那么该通道就是有缓冲的通道,通道的容量表示通道中能存放元素的数量。
带缓冲通道在很多特性上和无缓冲通道是类似的。无缓冲通道可以看作是长度永远为 0 的带缓冲通道。因此根据这个特性,带缓冲通道在下面列举的情况下依然会发生阻塞:
带缓冲通道被填满时,尝试再次发送数据时发生阻塞。带缓冲通道为空时,尝试接收数据时发生阻塞。
单向管道:
有的时候我们会将通道作为参数在多个任务函数间传递,很多时候我们在不同的任务函数中使用通道都会对其进行限制,比如限制通道在函数中只能发送或只能接收。
异常情况总结
channle | nil | 非空 | 空的 | 满了 | 没满 |
---|---|---|---|---|---|
接收 | 阻塞 | 接收值 | 阻塞 | 接收值 | 接收值 |
发送 | 阻塞 | 发送值 | 发送值 | 阻塞 | 发送值 |
关闭 | panic | 关闭,读取数据返回0值 | 关闭,返回0值 | 关闭,读取数据返回0值 | 关闭,读取数据返回0值 |
死锁问题
未初始化
1.读:未初始化的channel,读取里面的数据时,会造成死锁deadlock
var ch chan int
<-ch // 未初始化channel读数据会死锁
2.写:未初始化的channel,往里面写数据时,会造成死锁deadlock
var ch chan int
ch<- // 未初始化channel写数据会死锁
3.关闭:未初始化的channel,关闭该channel时,会panic
var ch chan int
close(ch) // 关
更多推荐
Golang总结
发布评论