Channel in golang

Declaration of channel

All channels are associated with a type. Channels can only transport this type of data, while transporting other types of data is illegal.

chan T represents a channel of type T.

Channel's zero value is nil. Channel's zero value is useless and should be made to define the channel as it is for map s and slices.

Write the code below to declare a channel.

package main

import "fmt"

func main() {
 var a chan int
 if a == nil {
  fmt.Println("channel a is nil, going to define it")
  a = make(chan int)
  fmt.Printf("Type of a is %T", a)
 }
}

Since the zero value of the channel is nil, at line 6, the value of channel a is nil. Thus, the program executes the statement in the if statement to define channel a. A in the program is a channel of type int. The program outputs:

channel a is nil, going to define it
Type of a is chan int

Or state as follows:

a := make(chan int)

Send and receive over a channel:

data := <- a // Read channel a
a <- data // Write channel a

The direction of the arrow next to the channel specifies whether to send or receive data.

In the first line, the arrow points outward to a, so we read the value of channel A and store it in the variable data.

In the second line, the arrow points to a, so we are writing data to channel a.

Send and receive are blocked by default

Send and receive are blocked by default. What does this mean? When data is sent to a channel, program control will block at the statement where it is sent until there are other Go'sSimilarly, when reading data from a channel, if no other protocol writes data to the channel, the reading process is always blocked.

This feature of the channel helps efficient communication between Go protocols without requiring explicit locks or conditional variables common to other programming languages.

package main

import (
 "fmt"
 "time"
)

func hello() {
 fmt.Println("Hello world goroutine")
}
func main() {
 go hello()
 time.Sleep(1 * time.Second)
 fmt.Println("main function")
}

This is the code from the previous article. We used hibernation to make the Go master collaborator wait for the hello collaboration to end. If you don't understand it, we recommend you read the previous Gomaster collaboration [5].

Let's rewrite the above code using channels.

package main

import (
 "fmt"
)

func hello(done chan bool) {
 fmt.Println("Hello world goroutine")
 done <- true
}
func main() {
 done := make(chan bool)
 go hello(done)
 <-done
 fmt.Println("main function")
}

Resolution:

In the above program, we created a bool-type channel done on line 12 and passed done as a parameter to the hello protocol. In line 14, we receive data through the channel done. This line of code is blocked and the program will not jump to the next line unless a protocol writes data to the done. Thus, it does not need to use the previous line of code.time.Sleep to prevent the Go Main Consortium from exiting.

<-done This line of code receives data through a protocol done, but does not use the data or store it in a variable. This is entirely legal.

Now our Go master protocol is blocked, waiting for data sent by channel done. The channel is passed as a parameter to protocol hello, Hello prints out Hello world goroutine, and then writes data to Done. When the write is completed, the Go master protocol receives data through channel done, and it unblocks and prints out the text main function.
Output of results:

Hello world goroutine
main function

Let's modify the program a little to include a hibernate function in the hello protocol to better understand the concept of blocking.

package main

import (
 "fmt"
 "time"
)

func hello(done chan bool) {
 fmt.Println("hello go routine is going to sleep")
 time.Sleep(4 * time.Second)
 fmt.Println("hello go routine awake and going to write to done")
 done <- true
}
func main() {
 done := make(chan bool)
 fmt.Println("Main going to call hello go goroutine")
 go hello(done)
 <-done
 fmt.Println("Main received data")
}

In the program above, we added a 4-second hibernation to the hello function (line 10).

The program first prints Main going to call hello go go routine. Then it opens the hello protocol and prints hello go routine is going to sleep. After printing, the hello protocol hibernates for 4 seconds, while in the meantime, the main protocol jams in the <-done line and waits for data from the channel done. After 4 seconds, it prints hello go routine awake and going to sleep.Write to done, then print Main received data.

deadlock

One of the key considerations when using channels is deadlock. When a Go protocol sends data to a channel, it is assumed that there are other Go protocols to receive data. If not, the program triggers a panic at run time, creating a deadlock.

Similarly, when there is a Go protocol waiting to receive data from a channel, we expect other Go protocols to write data to that channel or else the program will trigger a panic.

package main

func main() {
 ch := make(chan int)
 ch <- 5
}

Parse: In the above program, we created a channel ch, and then on the next line ch <- 5, we sent 5 to this channel. For this program, no other protocol receives data from ch. So the program triggers panic with the following run-time error.

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
 /tmp/sandbox249677995/main.go:6 +0x80

One way channel

Channels we are currently discussing are two-way channels, i.e. channels that can both send and receive data. In fact, one-way channels can also be created, which can only send or receive data.

package main

import "fmt"

func sendData(sendch chan<- int) {
 sendch <- 10
}

func main() {
 sendch := make(chan<- int)
 go sendData(sendch)
 fmt.Println(<-sendch)
}

In line 10 of the program above, we created Send Only channel sendch. Chan<-int defines a Send Only channel because the arrow points to chan. On line 12, we attempted to receive data over a Send Only channel, and the compiler reported an error:

main.go:11: invalid operation: <-sendch (receive from send-only type chan<- int)

Everything went well, but what is the meaning of a send-only channel that can't read data?

Channel Conversion is needed. Converting a two-way channel to a send-only channel or a Receive Only channel is possible, but the opposite is not possible.

package main

import "fmt"

func sendData(sendch chan<- int) {
 sendch <- 10
}

func main() {
 cha1 := make(chan int)
 go sendData(cha1)
 fmt.Println(<-cha1)
}

In line 10 of the above program, we created a two-way channel, cha1. In line 11, CHA1 is passed as a parameter to the sendData protocol. In line 5, the parameter sendch chan<-int in the function sendData converts CHA1 to a send-only channel. The channel is then a send-only channel in the sendData protocol, and in Go In the main coprocess is a two-way channel. The program prints out a final 10.

Close channel and traverse channel using for range

The data sender can close the channel and notify the receiver that no more data is being sent to the channel.

When receiving data from a channel, the receiver can use an extra variable to check if the channel is closed.

v, ok := <- ch

In the above statement, ok equals true if the data sent by the channel is successfully received. If ok equals false, we are trying to read a closed channel. The value read from the closed channel will be zero for that channel type. For example, when the channel is an int type channel, the value read from the closed channel will be 0.

package main

import (
 "fmt"
)

func producer(chnl chan int) {
 for i := 0; i < 10; i++ {
  chnl <- i
 }
 close(chnl)
}
func main() {
 ch := make(chan int)
 go producer(ch)
 for {
  v, ok := <-ch
  if ok == false {
   break
  }
  fmt.Println("Received ", v, ok)
 }
}

In the above program, the producer co-operation writes channel chn1 from 0 to 9, then closes the channel. The main function has an infinite for loop (line 16), using the variable ok (line 18)Check if the channel is closed. If ok equals false, the channel is closed and the for loop exits. If ok equals true, the received value and ok value are printed.

Received  0 true
Received  1 true
Received  2 true
Received  3 true
Received  4 true
Received  5 true
Received  6 true
Received  7 true
Received  8 true
Received  9 true

The for range loop is used to receive data from a channel before a channel is closed. Next, we rewrite the above code using the for range loop.

package main

import (
 "fmt"
)

func producer(chnl chan int) {
 for i := 0; i < 10; i++ {
  chnl <- i
 }
 close(chnl)
}
func main() {
 ch := make(chan int)
 go producer(ch)
 for v := range ch {
  fmt.Println("Received ",v)
 }
}

Parse: On line 16, the for range loop receives data from channel ch until the channel is closed. Once the ch is closed, the loop automatically ends.

We can use for range loops to rewrite the code in this section of another example of the channel [14] to improve code reuse.

If you look closely at this code, you'll find that the code that gets a number of bits per digit is repeated in the calcSquares and calcCubes functions. We'll pull this code out, put it in a separate function, and call it concurrently.

package main

import (
 "fmt"
)

func digits(number int, dchnl chan int) {
 for number != 0 {
  digit := number % 10
  dchnl <- digit
  number /= 10
 }
 close(dchnl)
}
func calcSquares(number int, squareop chan int) {
 sum := 0
 dch := make(chan int)
 go digits(number, dch)
 for digit := range dch {
  sum += digit * digit
 }
 squareop <- sum
}

func calcCubes(number int, cubeop chan int) {
 sum := 0
 dch := make(chan int)
 go digits(number, dch)
 for digit := range dch {
  sum += digit * digit * digit
 }
 cubeop <- sum
}

func main() {
 number := 589
 sqrch := make(chan int)
 cubech := make(chan int)
 go calcSquares(number, sqrch)
 go calcCubes(number, cubech)
 squares, cubes := <-sqrch, <-cubech
 fmt.Println("Final output", squares+cubes)
}

The digits function in the above program contains logic to get each digit of a number, and the calcSquares and calcCubes functions call digits concurrently. When each digit of a number is calculated, line 13 closes the channel. The calcSquares and calcCubes co-processes use for range Loops listen to their channels individually until the channel is closed. The program outputs the same if the rest of the program does not change:

Final output 1536

Tags: Go

Posted on Sun, 17 Oct 2021 13:36:19 -0400 by Scorptique