Go routine and channel internals

Buffered channel

  • our circular queue is the buf where we write to the circular queue
  • ch <- 3 we push a value into the channel. In the case when its full, we create a sudog object as we cannot write to the channel buffer. This sudog object is placed in the sendq DLL and this is our queue for items that are trying to be written to the buffer.
  • The go routine calls gopark and the go scheduler changes the state of the go routine to waiting.
  • result <- ch will read from the buf circular queue.
ch <- 1
ch <- 2
ch <- 3 // this is pushed to sendq
	res <- ch // 1	
// sendq value of 3 pushed into queue
	res <- ch // 2
	res <- ch // 3
	res <- ch // we now call gopark as there is nothign in the buf. We have a sudog allocated and puhsed into the recvq
  • when a sender now publishes an element into the channel, instead of writing to the buf we directly do a memcopy() to the recv. We directly copy to goroutine stack and we do not touch the circular queue buf
func recv(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) {
	// snip
	if c.dataqsiz == 0 {
		if raceenabled {
			racesync(c, sg)
		}
		if ep != nil {
			// copy data from sender
			recvDirect(c.elemtype, sg, ep)
		}
		// snip
	}
	// snip
}
 
func recvDirect(t *_type, sg *sudog, dst unsafe.Pointer) {
	// dst is on our stack or the heap, src is on another stack.
	// The channel is locked, so src will not move during this
	// operation.
	src := sg.elem.get()
	typeBitsBulkBarrier(t, uintptr(dst), uintptr(src), t.Size_)
	memmove(dst, src, t.Size_)
}

Unbuffered channel flow

  • we have direct stack to stack copy. We do not have a circular queue holding elements being sent

Select statement

select {
	case <-ch1:
	case <-ch2:
	default: // default non blocking exit
}
 
select { // blocking multi-select
	case <-ch1:
	case <-ch2:
}

Patterns

dbw by hashicorp (https://github.com/hashicorp/go-dbw) as a database wrapper in boundary