In this tutorial, we will learn about channel in Golang with 5 best examples. Channels are really just a way for goroutines to communicate in synchronized manner. We will look at different use cases of channel in just a few minute and also see how channels are implemented in different ways. So let’s get started.
Understanding Golang Channel
Also read: Concurrency in Golang with Best Examples
In Golang, channels are the medium used by Goroutines for communication and synchronization between them including the main function. Go philosophy says ‘Do not communicate by sharing memory, share memory by communicating’. It means two or more Goroutines sharing the memory or reading the variable at the same time will be problematic. So, share the memory only by communicating via channels as they are streamlined and efficient. There is a specific syntax to create a Channel. Let’s see below to understand the syntax
- ch := make(chan <type>)
- ch <- sendData (BLOCKING)
- receiveData <- ch (BLOCKING)
Line 1 is used to create a channel using make function which expect the data type of channel to be created. Channel data type could be any i.e string, int, boolean etc. Line 2 puts the data into the channel. BLOCKING denotes that sendData can not put the data into the channel if there is no receiver(in case of unbuffered channel) or if the channel is completely filled(in case of buffered channel). Blocking is necessary to ensure synchronization between Goroutines. Similarly Line 3 receiveData will read the data from the channel. Channel in this case also will be blocked if there is no sender (in case of unbuffered channel) or if the channel is empty(in case of buffered channel).
Types of Channel Operations
Also read: How to Install ChatGPT in VS Code [8 Steps]
In Golang, send and receive operations are the fundamental ways of communicating between goroutines. There are two types of major operations which can be done on a channel. They are:
Send Operation: Send operation will send the data on a channel using syntax ‘ch <- sendData‘ where sendData is the variable name. It sends the data from the sender Goroutine to the channel, making it available for a receiver Goroutine to retrieve. It also provides synchronization between sender and receiver Goroutines to ensure sender and receiver synchronize their actions, allowing controlled data exchange.
Receive Operation: Receive operation will receive the data from a channel using the syntax ‘receiveData <- ch‘ where receiveData is the variable number. Receive operation retrieves date from the channel making it available for the receiver Goroutine to process or use the received data. Similar to send operation, receive operation too provides synchronization between Goroutines.
Types of Golang Channels
There are two types of channels in Golang. They are:
Unbuffered Channel: They are also known as synchronous channel and have capacity of zero. This means they can only hold one data at a time. When a sender tries to send a data on an unbuffered channel, it will block until a receiver is ready to receive the data. Similarly, when a receiver tries to receive a data from an unbuffered channel, it will block until a sender is ready to send a value.
Buffered Channel: They have a specified capacity greater than 0. This means they will hold multiple data, till their capacity is reached before blocking. When a sender tries to send a data on a buffered channel, it will block only if the channel is full. When a receiver tries to receive a value from a buffered channel, it will block only if the channel is empty.
Channels in Golang: [5 Best Examples]
Now that we have understanding of what channels are and what all operations are permitted on channels, let’s look at few examples and understand how different use cases of channels are implemented. You can use online go editor to write and execute go code.
Example1: Unbuffered Channel
In this example, We have created an unbuffered channel. We then have created two subroutines, senderGoroutine which will send the data on channel and receiverGoroutine which will receive data from the channel.
package main import ( "fmt" "time" ) func main() { start_timer := time.Now() var ch = make(chan string) go senderGoroutine(ch, "Hello from sender goroutine") go receiverGoroutine(ch) time.Sleep(time.Second * 2) fmt.Println("\nMain function execution is completed") stop_timer := time.Since(start_timer) fmt.Println("\nProcess took", stop_timer, "to complete\n") } func senderGoroutine(ch chan<- string, value string) { fmt.Println("\nSending data: ", value) ch <- value } func receiverGoroutine(ch <-chan string) { Data := <-ch fmt.Println("\nReceived data:", Data) }
PS C:\Users\linuxnasa\OneDrive\Desktop\Go-Dump> go run .\hello-world.go Sending data: Hello from sender goroutine Received data: Hello from sender goroutine Main function execution is completed Process took 2.0040093s to complete
Example2: Multiple Receiver One Sender
In this example, we have created one sender and two receiver goroutines. Notice that at a time only one receiver will receive the data from the channel. This is because channel is synchronous.
package main import ( "fmt" "time" ) func main() { start_timer := time.Now() var ch = make(chan string) go senderGoroutine(ch, "Hello from sender goroutine") go receiverGoroutine(ch) go receiverGoroutine2(ch) time.Sleep(time.Second * 2) fmt.Println("\nMain function execution is completed") stop_timer := time.Since(start_timer) fmt.Println("\nProcess took", stop_timer, "to complete\n") } func senderGoroutine(ch chan<- string, value string) { fmt.Println("\nSending data: ", value) ch <- value } func receiverGoroutine(ch <-chan string) { Data := <-ch fmt.Println("\nreceiverGoroutine Received data:", Data) } func receiverGoroutine2(ch <-chan string) { Data2 := <-ch fmt.Println("\nreceiverGoroutine2 Received data:", Data2) }
PS C:\Users\linuxnasa\OneDrive\Desktop\Go-Dump> go run .\hello-world.go Sending data: Hello from sender goroutine receiverGoroutine2 Received data: Hello from sender goroutine Main function execution is completed Process took 2.0065849s to complete
Example3: Multiple Sender Multiple Receiver
In this example, we have created two sender and two receiver. receiverGoroutine will receive the data from senderGoroutine and receiverGoroutine2 will receive the data from senderGoroutine2.
package main import ( "fmt" "time" ) func main() { start_timer := time.Now() var ch = make(chan string) go senderGoroutine(ch, "Hello from senderGoroutine ") go senderGoroutine2(ch, "Hello from senderGoroutine2 ") go receiverGoroutine(ch) go receiverGoroutine2(ch) time.Sleep(time.Second * 2) fmt.Println("\nMain function execution is completed") stop_timer := time.Since(start_timer) fmt.Println("\nProcess took", stop_timer, "to complete\n") } func senderGoroutine(ch chan<- string, value string) { fmt.Println("\nSending data from senderGoroutine: ", value) ch <- value } func senderGoroutine2(ch1 chan<- string, value1 string) { fmt.Println("\nSending data from senderGoroutine2: ", value1) ch1 <- value1 } func receiverGoroutine(ch <-chan string) { Data := <-ch fmt.Println("\nreceiverGoroutine Received data:", Data) } func receiverGoroutine2(ch1 <-chan string) { Data2 := <-ch1 fmt.Println("\nreceiverGoroutine2 Received data:", Data2) }
PS C:\Users\linuxnasa\OneDrive\Desktop\Go-Dump> go run .\hello-world.go Sending data from senderGoroutine2: Hello from senderGoroutine2 receiverGoroutine2 Received data: Hello from senderGoroutine2 Sending data from senderGoroutine: Hello from senderGoroutine receiverGoroutine Received data: Hello from senderGoroutine Main function execution is completed Process took 2.0061387s to complete
Example4: Buffered Channel
In this example, We have created a buffered channel of capacity 3. So channel will not be blocked until it’s capacity is full.
package main import ( "fmt" ) func main() { ch := make(chan string, 3) ch <- "I am" ch <- "Buffered" ch <- "Channel" data := <-ch fmt.Println(data) data = <-ch fmt.Println(data) data = <-ch fmt.Println(data) }
PS C:\Users\linuxnasa\OneDrive\Desktop\Go-Dump> go run .\hello-world.go I am Buffered Channel
Example5: Multiple Channel
In this example, we will create 3 channel each carrying the string datatype. We have put sleep of 1 second to channel-1, sleep of 3 second to channel-2, sleep of 10 second to channel-3. We then have defined infinite for loop where it will listen to all channel’s activity via select statement.
package main import ( "fmt" "os" "time" ) func main() { ch1 := make(chan string) ch2 := make(chan string) ch3 := make(chan string) go func() { for { time.Sleep(time.Second) ch1 <- "Data sent from Channel-1" } }() go func() { for { time.Sleep(time.Second * 3) ch2 <- "Data sent from Channel-2" } }() go func() { for { time.Sleep(time.Second * 10) ch3 <- "Data sent from Channel-3....Done" } }() for { select { case receiveData := <-ch1: fmt.Println(receiveData) case receiveData := <-ch2: fmt.Println(receiveData) case receiveData := <-ch3: fmt.Println(receiveData + "\tEnd of execution!!") os.Exit(0) } } }
PS C:\Users\linuxnasa\OneDrive\Desktop\Go-Dump> go run .\hello-world.go Data sent from Channel-1 Data sent from Channel-1 Data sent from Channel-1 Data sent from Channel-2 Data sent from Channel-1 Data sent from Channel-1 Data sent from Channel-2 Data sent from Channel-1 Data sent from Channel-1 Data sent from Channel-1 Data sent from Channel-2 Data sent from Channel-1 Data sent from Channel-1 Data sent from Channel-1 Data sent from Channel-2 Data sent from Channel-1 Data sent from Channel-3....Done End of execution!!
Conclusion
We learnt about channels and different ways in which channels are implemented. We also saw how multiple channels can be monitored using select statement in infinite for loop. You can work on more examples and channel use cases for getting the hands on on channels.