Everything about channels appears confusing as well as overcomplicated at first.
The fact that not many other common programming languages have a similar concept means that, if you start your journey with Go , channels are one concept that you have to spend some time learning them.
Channel - Basic Concept
Channel is an important concept in go, particularly when it comes to concurrent programming.
<-
is a operator only work with channel, it means sending or receiving a message from a channel.
ch <- z // Send z to channel ch.
z := <-ch // Receive from ch, and
// assign value to z.
To using channels, it need to be built like maps and slices. An array is a numbered sequence of single type elements which have a fixed length. A slice is a segment of an array. Like arrays slices are indexable and have a size that is allowed to change unlike arrays. A map is a non-ordered set of pairs with key value. Often known as an associative array or dictionary, maps are used by its associated key to look up a name.
ch := make(chan int)
Concept Of Goroutines
Knowing how to perceive what is channels first, it is important to properly understand what is Goroutines. Goroutines are functions or methods which run at the same time(concurrently) as other functions or methods. It can be called as threads with a light weight. Compared with a thread the cost of creating a Goroutine is low. Hence it is common to have thousands of Goroutines running simultaneously for Go applications.
package main
import (
"fmt"
"time"
)
func mygoroutine() {
fmt.Println("In goroutine named mygoroutine.")
}
func main() {
go mygoroutine()
time.Sleep(1 * time.Second)
fmt.Println("1s waited... Because goroutine call returns immediately and return values from the Goroutine will ignore.")
fmt.Println("Now executing main function...")
}
Run this in Go Playground: https://play.golang.org/p/MqXkYU7H4MJ
When a new Goroutine is started, the call to the goroutine returns immediately. Like functions, the control doesn't wait until the Goroutine finishes its execution. After the Goroutine call the control returns to the next line of code immediately and any return values from the Goroutine are neglected. Thats why I added Sleep of 1 second after calling mygoroutine else it does not printed. You can run the two cases in Playground for better understanding.
Difference Between Goroutines And Channels
Now lets come to concept of channel.
Channels are the pipes which connect concurrent goroutines. Channels can be used to block the Goroutine main until all other Goroutines have completed their execution. For one goroutine, you can send values into channels and get those values into another goroutine.
Every channel has a type associated with it. This type is the type of data that can be transported by the pipe or channel. No other type is allowed to be transported via the channel. ch := make(chan int)
- here, int type. The control is blocked in the send statement when a data is sent to a channel until some other Goroutine reads from that channel. Likewise, the read is blocked when data is read from a channel until some Goroutine writes data to that channel. Channel can effectively communicate without the use of explicit locks or conditional variables.
We use a sleep in above Goroutine example to make the main Goroutine wait for the "mygoroutine" Goroutine to finish. Now come to channel, Look at some code that introduces how a channel is made and used in Go.
package main
import (
"fmt"
)
func mygoroutine(done chan bool) {
fmt.Println("In goroutine named mygoroutine.")
done <- true
}
func main() {
done := make(chan bool)
go mygoroutine(done)
<-done
fmt.Println("Now executing main function without Sleep after mygoroutine...")
}
Run this in Go Playground: https://play.golang.org/p/6RvVS3S-wgZ
Main Goroutine will be blocked as it awaits data from the "mygoroutine" channel. Until some Goroutine writes data into the channel the control will not transfer to the next code line. Therefore this removes the need of time.Sleep. Sleep that was present in the old program so that the main Goroutine could not exit. In here, When this write is complete, the main Goroutine receives the data from the done channel, then unblocked and the main text function is written.
Playing With Multiple Goroutines
If two functions are run as separate Goroutines and each is passed a channel to write to as the parameter. The main Goroutine waits for both of these channels to receive data. Once both channels receive the data, they are stored in the two variables, and the final output is computed and printed.
Lets look another one example for finding sum of square and cube of a number. The code has explanation it self.
package main
import "fmt"
func square(c chan int) {
fmt.Println("IN square -- reading from channel")
num := <-c
fmt.Println("IN square -- writing square to variable c")
c <- num * num
}
func cube(c chan int) {
fmt.Println("IN cube -- reading from channel")
num := <-c
fmt.Println("IN cube -- writing cube to variable c")
c <- num * num * num
}
func main() {
fmt.Println("Finding sum of square and cube of a number using channel | insafweb")
fmt.Println("IN main -- main function just started")
squareChannel := make(chan int)
cubeChannel := make(chan int)
go square(squareChannel)
go cube(cubeChannel)
Number := 3
fmt.Println("IN main -- sent Number to squareChannel")
squareChannel <- Number
fmt.Println("IN main -- resuming")
fmt.Println("IN main -- sent Number to cubeChannel")
cubeChannel <- Number
fmt.Println("IN main -- resuming")
fmt.Println("IN main -- reading from channels")
squareVal, cubeVal := <-squareChannel, <-cubeChannel
fmt.Println("IN main -- square and cube of", Number, "is recieved and stored in variables")
sum := squareVal + cubeVal
fmt.Println("IN main -- sum of square and cube of", Number, " is", sum)
fmt.Println("IN main -- main function stopped")
}
Run this in Go Playground: https://play.golang.org/p/CXH3Bt0UmZ9
OHH..!! Man.. Deadlock Here Too ??
Another thing, if a Goroutine sends data on a channel, the data is supposed to be received by some other Goroutine and vice versa. If this does not happen, then the program will panic at runtime with Deadlock. This will render runtime error as fatal error: all goroutines are asleep - deadlock!
.
Closing A Channel
A channel can be closed so that it can no longer send data. Receiver goroutine can find out the state of the channel using val, ok := <- channel
where ok is true if the channel is open or read operations can be performed and false if the channel is closed and no more read operations can be performed. A channel can be closed using close built-in function with close(channel)
.
Feel free to ask or share your thought in comment section below :-)
References:
https://gobyexample.com/channels
https://tour.golang.org/concurrency/2
Comments...
Nice work