协程和管道

45 阅读2分钟

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 之间的关系都是动态可变的

image-20230516200251112

多个可执行 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

image-20230516200532135

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并发模型

  • 要通过通信的方式来共享内存