七叶笔记 » golang编程 » 进大厂系列-Golang基础-03

进大厂系列-Golang基础-03

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
}  

相关文章