CategoryGo-routine

More on Channels in Go

In our last post, we talked about channels in Go. In this post we will go a little deeper into the concurrency features of Go has. Before we move on, we should briefly look at an overview of unbuffered and buffered channels.

Reviewing Channels

As we mentioned in the last post, by default channels are unbuffered. This means that they will only accept incoming values when there is a value that is being sent. The best way to look at this kind of behavior is to imagine train tracks. Imagine there are two parallel tracks that combine into one single track. In an unbuffered world, these tracks will always switch when there is exactly one train ready to cross. This means that if a train is coming from the right side, then the tracks will switch to that side. If a train is coming from the left side, then the tracks will switch to that side.

The trouble starts when we have multiple trains coming to the fork at once. The only way to resolve this problem is to use a buffering system. In this situation, one train will stop and wait while the other one crosses. If the buffer is increased then more trains can cross this fork without crashing into one another.train-switch

Like the metaphor, a buffered channel allows the program to send a specified number of values into a channel before they need to be received. Lets look at an example of this.

This example is very much like our metaphor. Both of our strings are trying to go into the channel before the channel is received. If we didn’t use a buffer then this would fail and cause a deadlock. A deadlock in Go is what happens when the switch between our two forking tracks can not decide which side to let through. As a result, both trains get stuck at the fork indefinitely.

Select in Go

Lets now look at another feature of Go’s concurrency model; Select. Select in Go allows a goroutine to wait for multiple communication operations to occur. The select operation will block the code until one of its cases is matched. It is a bit like a switch statement in this way.

If we have two or more channels and we want all of them to resolve at the same time we can use a select statement. Here is an example.

In this example, we have two channels both which are unbuffered. We run two anonymous functions concurrently to one another using goroutines. In these functions we pass a string to each of the channels. We then use our select operator to print out this message. We use a for loop to iterate twice through our select operator so that both cases are printed.go-channel

Like a switch statement, our select operator can also have a default case. This default case will try to execute when no other case is ready. Here is a simple example.

This example is pretty self explanatory. Based on the time after execution various events will happen as a result of the select operator.

Conclusion

In this post, we talked further about Channels and we introduced the select operator. We also talked further about channel buffering and how it works and we touched on deadlocks. In our next posts, we will start to look at the built in Http-server in Go using all of the concepts we’ve learned thus far.

Channels in Go

In the last post, we looked at goroutines. Goroutines allow us to deploy multiple threads in a concurrent fashion. In this post, we will be looking at channels in Go. Channels in go let these goroutines communicate with one another.

Channeling and Piping

With goroutines, we have the ability to do multiple things at once. However, we do not have the ability to synchronize the threads effectively when we want them to communicate. This is where channels come in. Channels allow us to synchronize the execution of these concurrently running functions. They also give us a mechanism by which we can pass values from one thread to another safely.go-channel

Channels have several different characteristics: the type of data, the buffer size, and the direction of communication. You may specify all of these features by using the built in channel operator <-. Lets look at some basic examples.

As with many of the other complex data structures in go, we can use the make operator to create a channel. We write chan followed by the type of information we want to send. We can further change the definitions of the channels by adding the arrow operator <- before or after chan. This arrow operator tells us which direction the channel flows in; placing it before indicates that the channel will be read only and placing it after makes the channel write only. Finally, we can indicate the buffer size by passing a second argument to the make statement.

Channels are first class elements in Go. This means that they can be used almost anywhere; in struct fields, function arguments, function return statements, and even other channels. Lets look at some sample code of this.

Using Channels in Go

Now that we have a basic understanding of what channels are, let us actually see them in action.

In this example, we create a channel in the main function body. This channel takes in a Boolean value and has no specified buffer. We send the channel into our function which is a goroutine, this goroutine returns true and then back in the main function we wait for a value to be passed through the channel. Unlike our examples from last post, we don’t need to add a fmt.Scanln() function at the end because the program will only end after the channel reads a value from the goroutine. This happens because our channel has no specified buffer.go-channels

In Go, all of the operations on these unbuffered channels block or stop the execution until the sender and receiver are ready to communicate with one another. If a channel has a buffer in Go, all read operations succeed without blocking if the buffer is not empty and write operations will not block if the buffer is not full. Unbuffered channels are called synchronous channels while buffered channels are called asynchronous channels. Lets look at a buffered example.

In this example, we’ve given our channel a buffer of 3. Our code will run through all of the goroutine iterations before the time.Sleep execution call. If we remove the buffer, only one iteration of the goroutine will run before time.Sleep is called. The buffer is letting all of the write operations happen without waiting for the first read. Without the buffer, the channel will only make one write before it has to read the value in the final line of code.

Conclusion

In this post, we took a look at channels in Go. We looked at unbuffered channels and we briefly looked at buffered channels. In our next posts, we will continue to look at channels and the other concurrent features in Go.  

Concurrency and Goroutines in Go

In this post, we will be looking at goroutines in Go. Go is a language that was built with concurrency in mind. Given that almost all of the computers of the modern era employ the use of multiple cpu cores, this is a natural approach. Go makes use of a special feature called the Goroutine to handle concurrency. You can use these goroutines to call on lightweight threads to run a function. Before we take a look at examples, lets talk about concurrency for a moment.

Concurrency in Computing

Concurrency is the use of independent processes that work in an asynchronous order to complete a task. There are many examples of concurrency in the real world. We can look at how trains share tracks with one another, how in multiplayer games each player can make an action independent of one another, and how society in general works with each person making their own decisions. Naturally, many things use a model of concurrency.

Logically, it makes sense to apply this type of behavior to a computer program. When you download a file, listen to an audio stream, send a message over the web, print out a document, or even type in a text editor, you are making use of concurrency in programs. Some programming languages use different methods to handle concurrency and Go is certainly no exception with it’s goroutines. 

go-routines-gopher

Examples of Goroutines in Go

The simple definition of a goroutine, is a function that is capable of running concurrently with other functions. To invoke a goroutine, we simply place the word go in front of a function execution call. Let us look at a very simple example of this.

In this example we use a for-loop to iterate through ten values (0 to 9) inside of our function. Our main function has its own thread and in a way, the main function itself runs like a goroutine. We invoke a second goroutine with our function by typing go a(0). If we were to remove the goroutine and the Scanln function call, the function would run normally and then the program would end after execution. However, because the function is running on a separate thread then the main function, if we were to remove the Scanln call and leave the goroutine, the program will terminate before the end of the function. This happens because the main function doesn’t wait for the goroutine to end before moving on to execute the rest of the code.gopher_pipe

Because goroutines are very lightweight threads, we can literally call on thousands of them in a piece of code (the average 64 bit cpu can use 100,000 goroutines at once). Let us look at an example that uses many goroutines.

In this example, we are assigning ten threads (labeled 0 to 9) to our function by using our for loop. In our function, we are iterating through ten values again, however, each time we are now stopping and waiting for a random period of time which is between 0 and 250 milliseconds before the thread continues execution. In this way, we can see the way the threads are actually running parallel and concurrent to one another. You will notice that threads that were deployed before others may end execution well after the others have finished.

Conclusion

In this post we looked at how we can spawn threads and run functions concurrently in Go. The talked about goroutines and gave a general overview of concurrency. In our next few posts we will talk about how we can send data from one goroutine to the other using channels.