go语言的GMP模型(协程并发模型),P是go语言本身内部实现的调度器,它是基于协程队列的,协程在调度器面前就类似一个个独立的任务;P一般数量上是处理器内核数。Process本身有调度和创建M的能力,它会调用系统内核方法创建M(M就是内核中轻量级进程),当其中一个M堵塞甚至发生异常了,那么P会调用系统方法创建新的子进程处理,在子进程中是循环获取goroution,也就是对于每一个M进程而言,其执行的伪代码指令:
while(routinelist.size() > 0){
if(next.task){
run routinelist[i].task
}
}
Context通常被称为上下文,在go中,理解为goroutine的运行状态、现场;存在上下层goroutine context的传递,上层goroutine会把context传递给下层goroutine。在网络编程中,当接收到一个网络请求的request,处理request时,可能会在多个goroutine中处理。而这些goroutine可能需要共享Request的一些信息;当request被取消或者超时时,所有从这个request创建的goroutine也要被结束。
1. Context是我们自己来创建和维护的
2. Context创建一般都需要指向传递父节点的上下文对象,所以Context是一个树状结构
go context包不仅实现了在程序单元之间共享状态变量的方法,同时能通过简单的方法,在被调用程序单元外部通过设置ctx变量的值,将过期或撤销等信号传递给被调用的程序单元。在网络编程中,如果存在A调用B的API,B调用C的 API,如果A调用B取消,那么B调用C也应该被取消,通过在A、B、C调用之间传递context,以及判断其状态就能解决此问题。context结构:
// A Context carries a deadline, a cancellation signal, and other values across
// API boundaries.
// Context's methods may be called by multiple goroutines simultaneously.
type Context interface {
// Deadline returns the time when work done on behalf of this context
// should be canceled. Deadline returns ok==false when no deadline is
// set. Successive calls to Deadline return the same results.<br>
// 返回一个超时时间,到期则取消context。在代码中,可以通过 deadline 为io操作设置超时时间
Deadline() (deadline time.Time, ok bool)
// a Done channel for cancellation. 返回一个channel, 用于接收context的取消或者deadline
//信号。当channel关闭,监听done信号的函数会立即放弃当前正在执行的操作并返回。如果 context实
//例是不可取消的,那么返回 nil, 比如空 context, valueCtx
Done() <-chan struct{}
// 返回一个error变量,从其中可以知道为什么context会被取消。
Err() error
// 让context在goroutine之间共享数据,当然,这些数据需要时协程并发安全的。比如,共享了一个
//map,那么这个map的读写要加锁。
Value(key interface{}) interface{}
}
Channel 被设计用来实现协程间通信的组件,其作用域和生命周期不可能仅限于某个函数内部,所以 golang 直接将其分配在堆上,准确地说,你并不需要知道。Golang 中的变量只要被引用就一直会存活,存储在堆上还是栈上由内部实现决定而和具体的语法没有关系。知道变量的存储位置确实和效率编程有关系。如果可能,Golang 编译器会将函数的局部变量分配到函数栈帧(stack frame)上。然而,如果编译器不能确保变量在函数 return (pop压出栈)之后不再被引用,编译器就会将变量分配到堆上。而且,如果一个局部变量非常大,那么它也应该被分配到堆上而不是栈上。当前情况下,如果一个变量被取地址,那么它就有可能被分配到堆上;然而还要对这些变量做逃逸分析,如果函数 return 之后,变量不再被引用,则将其分配到栈上。GO并发通信的模型是通信顺序进程(Communicating Sequential Processes, CSP),借助于内部实现的Channel机制,channel是golang中用来实现多个goroutine通信的管道,它的底层是一个叫做hchan的结构体。在go的runtime包下。
type hchan struct {
//channel分为无缓冲和有缓冲两种。
//对于有缓冲的channel存储数据,借助的是如下循环数组的结构
qcount uint // 循环数组中的元素数量
dataqsiz uint // 循环数组的长度
buf unsafe.Pointer // 指向底层循环数组的指针
elemsize uint16 //能够收发元素的大小
closed uint32 //channel是否关闭的标志
elemtype *_type //channel中的元素类型
//有缓冲channel内的缓冲数组会被作为一个“环型”来使用。
//当下标超过数组容量后会回到第一个位置,所以需要有两个字段记录当前读和写的下标位置
sendx uint // 下一次发送数据的下标位置
recvx uint // 下一次读取数据的下标位置
//当循环数组中没有数据时,收到了接收请求,那么接收数据的变量地址将会写入读等待队列
//当循环数组中数据已满时,收到了发送请求,那么发送数据的变量地址将写入写等待队列
recvq waitq // 读等待队列
sendq waitq // 写等待队列
lock mutex //互斥锁,保证读写channel时不存在并发竞争问题
}
结构图如下:
总结hchan结构体的主要组成部分有四个:
- 用来保存goroutine之间传递数据的循环链表。=> buf。
- 用来记录此循环链表当前发送或接收数据的下标值。=> sendx和recvx。
- 用于保存向该chan发送和从改chan接收数据的goroutine的队列。=> sendq 和 recvq
- 保证channel写入和读取数据时线程安全的锁。 => lock