Channels

The philosophy for sharing data with Go is Don't communicate by sharing memory; instead, share memory by communicating. Channels are the language construct that support this approach  they allow the sharing of data by communicating between goroutines correctly, rather than sharing common data. This is the main way that Go avoids race conditions (that is, one thread writing data while others read the same data). Channels are used for all sorts of patterns in Go  they can communicate the result of a goroutine (or pass data between routines), provide updates when data changes, or even signal that processes should finish.

Channels, just like all variables and constants in Go, require a type. The type of a channel determines the data that can be communicated through it. The type could be bool if you want to send information that is just true/false, or it could be a custom struct data type if you wish to communicate more information, such as a data-changed notification. In this example of channels, we are using a simple string channel that is read from a number of times while a goroutine continues to write into it:

package main

import "fmt"

func say(words string, to chan string) {
fmt.Println("Speaking:", words)
to <- words
}

func talk(to chan string) {
say("Hello", to)
say("Everyone", to)
say("My name is...", to)
fmt.Println("Never mind")
}

func listen(to chan string) {
heard := <-to
fmt.Println("I heard:", heard)}

func main() {
chat := make(chan string)

go talk(chat)

listen(chat)
listen(chat)
fmt.Println("Bye")
}

Running this sample will demonstrate that each time the channel is written to (in say), it must wait until the channel is read (in listen) before it can be written to again. You can also see that the talk goroutine never completed the message because we didn't read all of the data it was waiting to write:

Communicating through a simple channel

By default, writing to a channel will block until some code is ready to read from the other end, and likewise, reading will block until data is written to the channel, at which point the program flow will continue. This behavior can be altered by using a buffered channel  if a channel has a buffer size of 5, it could be written to 5 times before blocking; similarly, reading from that channel would potentially return 5 values before blocking (reading a channel will always block when no data is available). If we updated the preceding example to create a buffered channel of size 3 (by using make(chan string, 3)), we would see that the full message is written and the talk method completes:

Adding buffering to the channel

This trivial example indicates how you can safely communicate between goroutines, but let's look at some more practical examples by including some additional features. For example, a configuration struct could be communicated through a channel each time it changes, so that the application can respond accordingly: 

go func() {
for {
config := <-configManager
myWidget.applyConfiguration(config)}
}()

To be able to manage concurrency and communication between multiple goroutines, the language has an enhancement for the select keyword, which provides the ability to wait on multiple channels. This means that you don't have to have a goroutine for each blocking channel. The following example illustrates how a background function can work on some complex calculations (in this case, square) that are fed back to the main function, and also wait on a signal to finish processing:

package main

import "fmt"

func square(c, quit chan int) {
sq := 2
for {
select {
case c <- sq:
sq*=sq
case <-quit:
fmt.Println("quitting")
return
}
}
}

func main() {
c := make(chan int)
quit := make(chan int)
go square(c, quit)

func() {
for i := 0; i < 5; i++ {
fmt.Println("Square", <-c)
}
quit <- 1
}()
}

Running this example will output the calculations returned until the process is signaled to stop:

Reading from the calculation channel until quit is signaled

And finally, the channels in Go can be closed by the writer; this means that the function reading from a channel may stop getting new values. To avoid this deadlock situation, a reader of a channel can detect whether the channel has been closed. The syntax to check for the status of a channel is to read an optional second parameter, val, ok := <-ch, where val is the value read and ok indicates that the channel isn't closed. In addition to this, a new range keyword has been added, which will iterate through the values of a channel until it closes. The following example includes a download() function that simulates downloading data and updating its percentage to completion. The process reaches a logical conclusion and so the main function can complete. You can see how this could be used to ensure a progress bar stays up to date while other parts of the program keep running:

package main

import "fmt"

func download(file string, c chan int) {
fmt.Println("Downloading", file)

c <- 10
c <- 40
c <- 65
c <- 100

close(c)
}

func main() {
c := make(chan int)
go download("myfile.jpg", c)

for i := range c {
fmt.Printf("Progress %d%%... ", i)
}
fmt.Println("Download complete")
}

Running this example will show how a simulated download progresses and returns once the process is complete. The simple range keyword is used to avoid handling the channel-close condition directly:

Iterating on a channel range

Sometimes, you need to go beyond the concurrency primitives and handle special cases. This is what the standard library sync package provides.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.84.169