1.什么是channel?
channel即管道,是golang的重要核心之一,是golang中协程通信的方式之一。Golang的并发哲学,不要通过内存共享来通信,而是通过通信来实现内存共享,其具体的体现就是channel。传统的mutex锁都是通过共享临界资源区来实现通信,而golang支持通过channel来进行通信,从而实现内存共享。
2.channel有哪几种
channel分为两种,带缓冲的channel和不带缓冲的channel
- make(chan int),这样创建的是不带缓冲的chan
- make(chan int, 1),这样创建的是带一个缓冲的chan
3.介绍一下channel的结构体组成(3_2021_11_3)
type hchan struct {
qcount uint // total data in the queue
dataqsiz uint // size of the circular queue
buf unsafe.Pointer // points to an array of dataqsiz elements
elemsize uint16
closed uint32
elemtype *_type // element type
sendx uint // send index
recvx uint // receive index
recvq waitq // list of recv waiters
sendq waitq // list of send waiters
// lock protects all fields in hchan, as well as several
// fields in sudogs blocked on this channel.
//
// Do not change another G's status while holding this lock
// (in particular, do not ready a G), as this can deadlock
// with stack shrinking.
lock mutex
}
这里需要了解几个重要的属性,一个是buf指向存储数据的数组,sendx发送数据的索引,recvx接收数据的索引,sendq发送数据的等待队列,recvq接收数据的等待队列以及lock
当一个协程发送数据到带缓冲的channel时,把数据存储在缓冲数组的那个位置由sendx决定。如果缓冲池满了,就会加入到sendq等待队列当中,由接收的协程负责将其唤醒。
如何保证channel的并发安全,就是使用互斥锁来保证的。
4.向channel里面发送数据的逻辑
向channel里面发送数据分为以下几步:
- 首先看接收区的等待队列是否有正在等待的receiver,如果有的话,则直接把数据发送给receiver,并将其唤醒
- 然后看是否有缓冲区,如果缓冲区没满的话,则直接把数据放到缓冲区
- 如果是非阻塞的则直接返回,否则加入到发送区的等待队列当中去
5.goroutine接收的逻辑是怎么样的?
goroutine接受的逻辑和发送的逻辑差不多,分为以下几步:
- 首先查看sendq队列中是否有等待的g,如果有的话,则直接把等待的g中的数据取出,然后将其唤醒,返回即可
- 然后查看是否有缓冲区,缓冲队列是否有数据,如果有的话则直接从缓冲区中拿取数据
- 否则查看是否阻塞,如果阻塞则加入到recvq队列当中,否则直接返回
6.goroutine的堆栈信息保存在哪里?(3_2021_11_03)
保存在g的结构体里面,具体在g.sched这个属性里面,g.sched这个属性的内容,如下:
type gobuf struct {
// The offsets of sp, pc, and g are known to (hard-coded in) libmach.
//
// ctxt is unusual with respect to GC: it may be a
// heap-allocated funcval, so GC needs to track it, but it
// needs to be set and cleared from assembly, where it's
// difficult to have write barriers. However, ctxt is really a
// saved, live register, and we only ever exchange it between
// the real register and the gobuf. Hence, we treat it as a
// root during stack scanning, which means assembly that saves
// and restores it doesn't need write barriers. It's still
// typed as a pointer so that any other writes from Go get
// write barriers.
sp uintptr
pc uintptr
g guintptr
ctxt unsafe.Pointer
ret sys.Uintreg
lr uintptr
bp uintptr // for framepointer-enabled architectures
}