You asked two questions:
- What kind of structure are they?
Channels in go are indeed "kind of like a thread-safe queue", to be more precise, channels in Go have the following properties:
- goroutine-safe
- Provide FIFO semantics
- Can store and pass values between goroutines
- Cause goroutines to block and unblock
Every time you create a channel, an hchan struct is allocated on the heap, and a pointer to the hchan memory location is returned represented as a channel, this is how go-routines can share it.
The first two properties described above are implemented similarly to a queue with a lock.
The elements that the channel can pass to different go-routines are implemented as a circular queue (ring buffer) with indices in the hchan struct, the indices account for the position of elements in the buffer.
Circular queue:
qcount uint // total data in the queue
dataqsiz uint // size of the circular queue
buf unsafe.Pointer // points to an array of dataqsiz elements
And the indices:
sendx uint // send index
recvx uint // receive index
Every time a go-routine needs to access the channel structure and modify it's state it holds the lock, e.g: copy elements to/ from the buffer, update lists or an index. Some operations are optimized to be lock-free, but this is out of the scope for this answer.
The block and un-block property of go channels is achieved using two queues (linked lists) that hold the blocked go-routines
recvq waitq // list of recv waiters
sendq waitq // list of send waiters
Every time a go-routine wants to add a task to a full channel (buffer is full), or to take a task from an empty channel (buffer is empty), a pseudo go-routine sudog struct is allocated and the go-routine adds the sudog as a node to the send or receive waiters list accordingly. Then the go-routine updates the go runtime scheduler using special calls, which hints when they should be taken out of execution (gopark
) or ready to run (goready
).
Notice this is a very simplified explanations that hides some complexities.
- Does their implementation depend on the architecture?
Besides the lock implementation that is OS specific as @mna already explained, I'm not aware of any architecture specific constraints optimizations or differences.