goroutine
使用
func F() {
for i := 0; i < 10; i++ {
fmt.Printf("%d%s ", i, "f")
}
}
func main() {
go F()
for i := 0; i < 10; i++ {
fmt.Printf("%d%s ",i,"main")
time.Sleep(time.Second)
}
}
MPG
M:内核线程
P:协程需要的上下文
G:协程
如果M在一个cpu运行就是并发;不同cpu运行就是并行
M 和 P 以及 P 和 G 之间的关系都是动态可变的
多个可执行 G 将会顺序排成一个队列挂在某个 P 上面,等待调度和执行
G0 中的代码片段处于正在运行的状态,而右边的 G 队列处于待执行状态
M 和 P 会适时的组合和断开,保证 P 中的待执行 G 队列能够得到及时运行。比如说上图中的 G0 此时因为网络 I/O 而阻塞了 M,那么 P 就会携带剩余的 G 投入到其他 M 的怀抱中。这个新的 M1 可能是新创建的,也可能是从调度器空闲 M 列表中获取的,取决于此时的调度器空闲 M 列表中是否存在 M,从而避免 M 的过多创建
当 M 对应的内核线程被唤醒时,M 将会尝试为 G0 捕获一个 P 上下文,可能是从调度器的空闲 P 列表中获取,如果获取不成功,M 会被 放入到调度器的可执行 G 队列中,等待其他 P 的查找。为了保证 G 的均衡执行,非空闲的 P 会运行完自身的可执行 G 队列中,会周期性从调度器的可执行 G 队列中获取代执行的 G,甚至从其他的 P 的可执行 G 队列中掠夺 G
channel
原理
底层是hchan的结构体,在堆分配内存;缓冲区类似环形数组
type hchan struct {
qcount uint //当前元素个数(len)
dataqsiz uint //队列长度(cap)
buf unsafe.Pointer //指向缓冲区
elemsize uint16 //单个元素大小
closed uint32 //标识通道状态
elemtype *_type //元素类型
sendx uint //下一个元素存放的位置
recvx uint //下一个元素读取的位置
recvq waitq //等待读消息的协程队列
sendq waitq //等待发消息的协程队列
lock mutex //互斥锁,保证不存在并发读写管道
}
存数据:加锁,存,释放锁
取数据:加锁,取,释放锁
底层是通过hchan结构体的buf指向的缓冲区,达到了共享内存的目的,这里也体现了Go中的CSP并发模型
- 要通过通信的方式来共享内存