This article is translated

Original address: golangbot.com/channels/

Other examples of channels 🌰

Let’s write more code to better understand channels. The program will print the sum of the squares and cubes of the single digits of the number.

For example, if 123 is entered, the program computes the output as:

squares = (1 * 1) + (2 * 2) + (3 * 3)
cubes = (1 * 1 * 1) + (2 * 2 * 2) + (3 * 3 * 3)
output = squares + cubes = 50
Copy the code

We will structure the program so that squares are computed in a separate Goroutine, cubes are computed in another Goroutine, and summation occurs in the main Goroutine.

package main

import (  
    "fmt"
)

func calcSquares(number int, squareop chan int) {  
    sum := 0
    fornumber ! =0 {
        digit := number % 10
        sum += digit * digit
        number /= 10
    }
    squareop <- sum
}

func calcCubes(number int, cubeop chan int) {  
    sum := 0 
    fornumber ! =0 {
        digit := number % 10
        sum += digit * digit * digit
        number /= 10
    }
    cubeop <- sum
} 

func main(a) {  
    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)
}
Copy the code

The calcSquares function checks if the number equals zero and sends the initial sum to the Squareop channel. If not, the calcSquares function calculates the remainder of number modulo 10 to the sum of squares and sends it to the Squareop channel. The calcCubes function is similar, except that it computes the sum of eventcubes.

Each of these functions is allowed as a separate Goroutine and passed in a channel as an argument. The main Goroutine then reads data from both channels, storing squares and cubes variables, respectively.

Program output:

Final output 1536  
Copy the code

A deadlock

An important factor to consider when using channels is deadlocks. If a Goroutine is sending data over a channel, you expect other Goroutines to be receiving data. If the expected result does not occur, a deadlock panic occurs.

Similarly, if one Goroutine receives data to the channel, it is expected that other Goroutines should be sending data to the channel, or else panic exceptions will also be sent.

package main


func main(a) {  
    ch := make(chan int)
    ch <- 5
}
Copy the code

In the code above, we create a channel ch and write data 5 to the channel using the statement ch < -5. In this program, no other Goroutine receives data from the CH channel. Therefore, the program will run with a panic exception.

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:  
main.main()  
    /tmp/sandbox046150166/prog.go:6 +0x50
Copy the code

A one-way passage

All of the channels we have discussed so far are bidirectional, meaning that data can be sent and received on them. You can also create one-way channels, that is, channels that send or receive data only.

package main

import "fmt"

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

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

In the code above, I created a channel that can only send data, sendch. The statement chan< -int indicates that only channels are sent because the arrow points to chan. I tried to read data from the channel, which is not allowed, and when the program runs, the compiler will display:

./prog.go:12:14: invalid operation: <-sendch (receive from send-only type chan<- int)

All well and good, but what’s the purpose of writing if you can’t read just send channels!

This is where channel transformations are used. A bidirectional channel can be converted to a send – or receive – only channel and vice versa.

package main

import "fmt"

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

func main(a) {  
    chnl := make(chan int)
    go sendData(chnl)
    fmt.Println(<-chnl)
}
Copy the code

In the above code, a two-way channel CHNL is created. It is passed in as a parameter to sendData Goroutine. The sendData function to the parameter sendch chan< -int converts the channel to a write-only one-way channel. So the Sendch channel is still bidirectional in Goroutine.

Close channels and use range loops on channels

The sender can close the channel to inform the receiver that the channel will not send any more data.

A receiver can use additional variables when receiving data from a channel to check if the channel is closed.

v, ok := <- ch  
Copy the code

In the above statement, OK is true if the value was received through a successful send operation on the channel. If OK is false, we are reading data from a closed channel. The value read from a closed channel will be zero for the channel type.

For example, if the channel is an int channel, the value received 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(a) {  
    ch := make(chan int)
    go producer(ch)
    for {
        v, ok := <-ch
        if ok == false {
            break
        }
        fmt.Println("Received ", v, ok)
    }
}
Copy the code

In the code above, Producer Goroutine writes the numbers 0-9 to CHNL and then closes the channel. A for loop is defined in the main function and the OK variable is used to check if the channel is closed. If OK is false, the channel is closed, so the loop will be closed. Otherwise it accepts the data and prints the data and the value of OK.

Program output:

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 trueCopy the code

The for loop receives data from the channel until the channel is closed.

Let’s rewrite the for loop above:

package main

import (  
    "fmt"
)

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

The for loop receives data from the CH channel until it closes. After ch is closed, the loop exits automatically.

The program output:

Received  0  
Received  1  
Received  2  
Received  3  
Received  4  
Received  5  
Received  6  
Received  7  
Received  8  
Received  9  
Copy the code

You can use a for range loop to rewrite the program in another example section of the channel to improve code reusability.

If you take a closer look at the program, you’ll see that the code for finding a single number is repeated in both the calcSquares and calcCubes functions. We move the code to its own function and call it at the same time.

package main

import (  
    "fmt"
)

func digits(number int, dchnl chan int) {  
    fornumber ! =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 main(a) {  
    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)
}
Copy the code

Now, the digits function in the above program contains the logic to get a single number from a number, and the calcSquares and calcCubes functions call this logic simultaneously. Once there are no more digits in the number, the channel is closed. CalcSquares and calcCubes Goroutines use a for range loop to listen on their respective channels until they are turned off. The rest of the program is the same.

The program will print:

Final output 1536  
Copy the code