Golang Tutorial
Fundamentals
Control Statements
Functions & Methods
Structure
Arrays & Slices
String
Pointers
Interfaces
Concurrency
In Go, channels are used primarily for communication between goroutines, providing a way to send and receive data with synchronization. By default, channels are bidirectional, meaning you can both send and receive values on them. However, sometimes you might want to be explicit about the direction of data flow by using unidirectional channels.
Unidirectional channels are either send-only or receive-only. This tutorial covers the basics of unidirectional channels in Go.
Send-only channel: You can only send data to the channel.
var chSend chan<- int
Receive-only channel: You can only receive data from the channel.
var chReceive <-chan int
package main import ( "fmt" "time" ) func sendData(chSend chan<- int) { for i := 0; i < 5; i++ { chSend <- i time.Sleep(time.Second) } close(chSend) } func receiveData(chReceive <-chan int) { for v := range chReceive { fmt.Println(v) } } func main() { ch := make(chan int) go sendData(ch) receiveData(ch) }
In the example above:
sendData
function takes a send-only channel as an argument. It can only send data to this channel.receiveData
function takes a receive-only channel. It can only read data from this channel.You can't change the direction of a channel once it's set: If you have a receive-only channel, you can't later make it bidirectional or send-only, and vice-versa.
Unidirectional channels are mainly for function and method parameters: It's common to use unidirectional channels as function/method parameters to restrict usage inside the function/method. This makes code more readable and provides a level of safety.
Safety: By making a channel send-only or receive-only, you're indicating its intended use. This can prevent errors, like accidentally reading from a channel that should only be written to.
Code clarity: Using unidirectional channels can make code easier to understand by clarifying the intent of the channel.
Remember, while unidirectional channels can be a powerful feature for writing clear and safe concurrent code, they are not always necessary. Often, bidirectional channels are sufficient, especially when the intended usage is clear.
Golang unidirectional channel example:
Unidirectional channels restrict the direction of data flow, either for sending or receiving.
package main import "fmt" func main() { // Creating unidirectional channels sendOnlyChan := make(chan<- int) receiveOnlyChan := make(<-chan int) fmt.Printf("Send-only channel: %T\n", sendOnlyChan) fmt.Printf("Receive-only channel: %T\n", receiveOnlyChan) }
Send-only channel in Golang:
Creating a send-only channel allows only sending data into the channel.
package main import "fmt" func sendData(ch chan<- int, data int) { ch <- data } func main() { sendOnlyChan := make(chan<- int) go sendData(sendOnlyChan, 42) }
Receive-only channel in Golang:
Creating a receive-only channel allows only receiving data from the channel.
package main import "fmt" func receiveData(ch <-chan int) { data := <-ch fmt.Println("Received data:", data) } func main() { receiveOnlyChan := make(<-chan int) go receiveData(receiveOnlyChan) }
Unidirectional channel vs bidirectional channel in Go:
Unidirectional channels restrict the direction of data flow, whereas bidirectional channels allow both sending and receiving.
package main import "fmt" func main() { // Unidirectional channel sendOnlyChan := make(chan<- int) receiveOnlyChan := make(<-chan int) // Bidirectional channel bidirectionalChan := make(chan int) fmt.Printf("Send-only channel: %T\n", sendOnlyChan) fmt.Printf("Receive-only channel: %T\n", receiveOnlyChan) fmt.Printf("Bidirectional channel: %T\n", bidirectionalChan) }
Golang select statement with unidirectional channels:
The select
statement can be used with unidirectional channels for non-blocking communication.
package main import ( "fmt" "time" ) func main() { sendChan := make(chan<- int) receiveChan := make(<-chan int) select { case sendChan <- 42: fmt.Println("Sent data to sendChan") case data := <-receiveChan: fmt.Println("Received data from receiveChan:", data) default: fmt.Println("No communication") } }
Closing a unidirectional channel in Go:
Unidirectional channels can be closed, and attempting to send or receive on a closed channel will cause a panic.
package main import "fmt" func main() { sendOnlyChan := make(chan<- int) close(sendOnlyChan) // Closing a send-only channel will cause a panic receiveOnlyChan := make(<-chan int) close(receiveOnlyChan) // Closing a receive-only channel will cause a panic }
Buffered unidirectional channels in Golang:
Unidirectional channels can also be buffered to allow a certain number of elements to be stored in the channel without a corresponding receiver.
package main import "fmt" func main() { sendOnlyChan := make(chan<- int, 2) sendOnlyChan <- 42 sendOnlyChan <- 33 // sendOnlyChan <- 55 // Uncommenting this line would result in a deadlock }
Using unidirectional channels for goroutine communication:
Unidirectional channels are often used for communication between goroutines.
package main import ( "fmt" "sync" ) func producer(ch chan<- int, wg *sync.WaitGroup) { defer wg.Done() ch <- 42 } func consumer(ch <-chan int, wg *sync.WaitGroup) { defer wg.Done() data := <-ch fmt.Println("Received data:", data) } func main() { ch := make(chan int) var wg sync.WaitGroup wg.Add(1) go producer(ch, &wg) wg.Add(1) go consumer(ch, &wg) wg.Wait() close(ch) }
Golang fan-out, fan-in with unidirectional channels:
Fan-out involves multiple goroutines reading from a single channel, and fan-in involves multiple goroutines writing to a single channel.
package main import ( "fmt" "sync" ) func producer(ch chan<- int, wg *sync.WaitGroup) { defer wg.Done() for i := 0; i < 5; i++ { ch <- i } } func consumer(ch <-chan int, resultChan chan<- int, wg *sync.WaitGroup) { defer wg.Done() for { data, ok := <-ch if !ok { break } resultChan <- data * 2 } } func main() { ch := make(chan int) resultChan := make(chan int) var wg sync.WaitGroup wg.Add(1) go producer(ch, &wg) for i := 0; i < 3; i++ { wg.Add(1) go consumer(ch, resultChan, &wg) } go func() { wg.Wait() close(resultChan) }() for result := range resultChan { fmt.Println("Result:", result) } }