Go Channel(for IPC)

Go channels are used for synchronizing IPC between goroutines(Same as Rust Tasks = Green Threads). Go routines are managed by Go runtime.
we can send/recv data via channel with operator, <- between goroutines. The data flows in the direction of the arrow.
channels are datastructures allocated on heap that reference both sender and receiver goroutines
close(): Only sender can close a channel to indicate that no more values will be sent.

// Create a channel
ch := make(chan int)

// Send/recv values on Channel
ch <- v            // Send v to channel ch.
v := <-ch          // Receive from ch, and assign value to v.

// close the channel
// Reciever can check if channel is closed?
v, ok := <-ch
if ok == false {
  // channel is closed
}

// Reading all values from channel until it is closed
for v := range ch {
  fmt.Println(v)
}
        

//        (channel, capacity)
ch := make(chan int, 100)
// Creates 1 channel that can hold up to 100 items
// ie ints without blocking the sender
        

select(wait on multiple channels)

Channels need not to be closed because channels are not files; you don't need to close them

Code

1. Write/Read data on channels

package main
import (
	"fmt"
)
func main() {
  // 1. Declare 3 unbuffered channels ch1,ch2,done 
	ch1 := make(chan int)
	ch2 := make(chan int)
	done := make(chan int)

  // 2. go routine. Send data on channels
	go func() {
		ch1 <- 1
		ch2 <- 2
		close(done)
	}()

  // 3. Keep waiting on channels
	for {
		select {
		case v1, ok := <-ch1:  // Recieve data on ch1
			if ok {
				fmt.Println("ch1:", v1)
			}
		case v2, ok := <-ch2:  // Recieve data on ch2
			if ok {
        fmt.Println("ch2:", v2)
			}
		case <-done:           // Receive data on done
      fmt.Println("Bye!")
      return
    default:
      // Print this, when no channel is ready
      fmt.Println("No case ready")
    }
  }
}
$ go run main.go
ch1: 1
ch2: 2
Bye!
      

Types of Channels

Buffered / Bounded Unbuffered / Unbounded
What When buffer length is provided a buffered channel is created

ch := make(chan int, 100)
        
Channel created without any size

ch := make(chan int)
        
No reciever No issue Memory Leak in goroutine

1.

. Send to a buffered channel block only when the buffer is full. Receives block when the buffer is empty.

Bidirectional / Directional

In Go, all channels start as bidirectional, but you can use directional types to enforce API safety and prevent bugs.
1. Bidirectional 2. Directional (Recieve only) 3. Directional (Send only)
What Can both send and receive data Data can only read data from it Data can only put data into it
Format (chan T) <-chan T chan<- T
Why use directional Channels?
  1. Safety: If we have a function that only processes results, we should use directional(RO) channel <-chan T. Using bidirectional may accidently send data on the channel

Internal Implementation of Channels

When we call make(chan int, 10), the runtime allocates a struct hchan on heap

func main() {
	ch1 := make(chan int)
}

// Channel struct 
// (heap, hchan with buffer + sendq/recvq queues)
type hchan struct {
    qcount   uint           // bytes queued
    dataqsiz uint           // buffer size
    buf      unsafe.Pointer // ring buffer (heap)
    elemsize uint16  // elem size
    closed   uint32
    elemtype *_type
    sendx    uint          // send index
    recvx    uint          // recv index
    recvq    waitq         // blocked RECEIVERS  
    sendq    waitq         // blocked SENDERS ← your goroutine lives here
}
      

Why Channels are Fast

1. Direct Copying: If a sender is waiting and a receiver arrives, the Go runtime copies the data directly from the sender's stack to the receiver's stack, skipping the channel buffer entirely
2. No busy-waiting: Instead of "busy-waiting" (spinning), a blocked goroutine is "parked" by the Go scheduler.