面试题答案
一键面试向已满的有缓冲channel发送数据
- Goroutine状态转换:
- 当一个goroutine尝试向已满的有缓冲channel发送数据时,该goroutine会从运行态(
runnable
)转换为等待态(waiting
)。这是因为此时没有可用的缓冲区空间来接收数据,goroutine需要等待其他goroutine从channel接收数据以腾出空间。
- 当一个goroutine尝试向已满的有缓冲channel发送数据时,该goroutine会从运行态(
- 队列操作:
- Go运行时会将这个处于等待态的goroutine放入channel的发送等待队列(
sendq
)中。sendq
是一个双链表结构,用于存储所有等待向该channel发送数据的goroutine。 - 例如,假设我们有一个有缓冲channel
ch := make(chan int, 3)
,并且缓冲区已满。当goroutineg1
尝试向ch
发送数据时,g1
会被添加到ch
的sendq
队列中。
- Go运行时会将这个处于等待态的goroutine放入channel的发送等待队列(
- 内存管理:
- 此时并没有新的内存分配操作直接与这个发送操作相关。但是,由于goroutine进入等待态,它所占用的栈空间等资源仍然被保留,直到它被唤醒。
从一个空的channel接收数据
- Goroutine状态转换:
- 当一个goroutine尝试从一个空的channel接收数据时,该goroutine同样会从运行态转换为等待态。因为channel中没有数据可供接收,它需要等待其他goroutine向channel发送数据。
- 队列操作:
- 这个等待接收数据的goroutine会被放入channel的接收等待队列(
recvq
)中。recvq
也是一个双链表结构,用于存储所有等待从该channel接收数据的goroutine。 - 例如,对于一个空的channel
ch := make(chan int)
,当goroutineg2
尝试从ch
接收数据时,g2
会被添加到ch
的recvq
队列中。
- 这个等待接收数据的goroutine会被放入channel的接收等待队列(
- 内存管理:
- 与向已满channel发送数据类似,当goroutine进入等待态从空channel接收数据时,其占用的栈空间等资源被保留。一旦有数据发送到channel且该goroutine被唤醒接收数据,可能会涉及到将接收到的数据复制到接收方指定的内存位置(如果是值传递)。如果是指针传递,只是传递指针,不会有大规模的数据复制操作。
唤醒与调度
- 唤醒操作:
- 当channel有可用空间(对于发送操作)或有数据(对于接收操作)时,运行时会从相应的等待队列(
sendq
或recvq
)中取出一个goroutine并将其唤醒。例如,如果一个goroutine向一个满的channel发送数据后使channel有了空闲空间,运行时会从sendq
队列中取出一个等待发送的goroutine并唤醒它。
- 当channel有可用空间(对于发送操作)或有数据(对于接收操作)时,运行时会从相应的等待队列(
- 调度:
- 被唤醒的goroutine会被重新放入全局运行队列(
GRQ
)或本地运行队列(LRQ
)中,具体取决于实现细节和调度策略。然后,Go运行时的调度器会在适当的时候将这个goroutine调度为运行态,使其继续执行发送或接收操作之后的代码。
- 被唤醒的goroutine会被重新放入全局运行队列(