Go, often referred to as Golang, is well-known for making it remarkably easy to work with concurrency. In order to make a particular function run concurrently, all we have to do is prepend the word "go" to the function call, and it cheerfully runs in the background, as a GoRoutine. Go's built in scheduler takes are of making sure that a given GoRoutine runs when it should, and as efficiently as it can.
Go, often referred to as Golang, is well-known for making it remarkably easy to work with concurrency. In order to make a particular function run concurrently, all we have to do is prepend the word "go" to the function call, and it cheerfully runs in the background, as a GoRoutine. Go's built in scheduler takes are of making sure that a given GoRoutine runs when it should, and as efficiently as it can.
However, this does not mean that working with concurrency is simple in Go—thread safe programming takes careful planning, and most importantly it requires that developers have an absolutely solid understanding of how Go deals with concurrency.
In the standard library, Go offers us several ways of dealing with concurrently running parts of our program, right in the standard library: sync.WaitGroup, which lets us wait for tasks to finish; sync.Mutex, which allows us to lock and unlock resources, so that no two GoRoutines can access the same memory location at the same time; and finally, Channels, which allow GoRoutines to send and receive data to and from each other.
Go's approach to concurrency is fairly straightforward, and is more or less summed up this mantra: Don't communicate by sharing memory; instead, share memory by communicating. Channels are the means by which we usually share memory by communicating.
In this course, we'll cover the use of WaitGroups, Mutexes, and Channels, and we'll do so in detail. We'll also cover some of the problems inherent in concurrency, including early program termination and race conditions. Initially, we'll gain a good understanding of how these things work by solving some of the classic problems found in the field of computer science, including the Dining Philosophers, the Producer/Consumer problem, and the Sleeping Barber. These problems are classics for a reason: they force a developer to figure out the best approach to working with code that run concurrently, or in parallel.
Finally, we'll finish the course out with a more "real-world" problem, where we have to register a customer for some kind of subscription service, and take care of invoicing, registration, and all the things necessary to get a new customer up and running. We'll do so, naturally, as quickly as we can by dividing the necessary tasks up into smaller tasks, and having them run concurrently.
An overview of what we'll be doing in this course.
Just a bit of information about me and my background.
Obviously, we'll need Go installed. Let's make sure we have the latest version.
Chances are you already have an IDE installed, but if not, Visual Studio Code will do the job.
I'm happy to answer questions, but make it easy for me to give you the help you need.
No one is perfect, and that includes even experienced developers. I'll make mistakes, and I will not hide them.
Let's have a look at the "go" keyword, which allows us to create goroutines for functions, and send them off to run concurrently with the main program. Let's also have a look at the problems that having things run concurrently can create, and figure out one (very bad!) way of solving such problems.
Let's get rid of the (awful) time.Sleep() call in our program, and instead take advantage of Go's built-in sync.WaitGroup type, which allows us to wait for one or more concurrently running tasks—GoRoutines—to finish before moving on.
Race conditions occur when multiple GoRoutines attempt to access and modify the same data, and this leads to unpredictable results. Let's look at an example.
Go lets us test for race conditions when running a program, and when we write tests. Let's give this a try.
Let's write a slightly more complex program that gives us a race condition, and then use sync.Mutex to solve that problem.
Let's write a simple test for our weekly income program.
Waitgroups and Mutexes are wonderful tools, but let's add Channels to the mix by solving a classic computer science problem.
Let's get started with the Producer part of our solution to the Producer/Consumer problem.
Our producer is going to be producing pizzas, so let's write a function that will attempt to make a pizza.
Let's finish writing our Producer by completing the makePizza function, and listening for data sent to channels in the pizzeria function.
We have a Producer ready to go, but of course it has nothing to do until we create and run our Consumer. Let's take care of that now.
Let's put the finishing touches on our Producer/Consumer project.
Let's have a look at another classic computer science problem: The Dining Philosophers.
A gentle introduction to sending and receiving on channels.
Let's have a look at the select statement, which is more or less a switch statement, but for channels.
So far, every channel we have created has been unbuffered, so the channel can only hold one thing at a time (you can send as much information as you want to it, but sending will block until the channel is empty). Let's have a look at buffered channels, where we queue up more than one thing at a time for a given channel.
Let's write a few comments to get started with the Sleeping Barbers project.
In order to create a barbershop, we'll need a type which describes it, and a function to create it. Let's take care of that now. Let's also create a stub run() function that we'll use to start program execution.
Let's take care of adding a barber to the shop. This barber will be a method on the BarberShop type, and will take care of spawning a GoRoutine for that barber which will run until the shop closes for the day. Depending on what it receives from the ClientsChan channel, the barber associated with this GoRoutine will alternately nap and cut hair, depending on whether or not there is something sent to the ClientsChan channel.
So we can add barbers to the shop, and now we need to start another goroutine that waits until everything is done for the day. Let's get started.
Let's finish up our solution to the Sleeping Barber problem by sending clients the barbershop.
Let's try things out, and see how our solution works.
Just an overview of what we will do in this section of the course.
Let's write a bit of code and outline what we want to do in this application, and lets go and get the necessary packages for our database, sessions, and a router.
To make things simpler, let's get Docker installed, and bring up a Docker development environment with Postgres, Redis, and a dummy mail server.
Let's write the code necessary to connect to our Postgres database.
Setting up a Makefile will make our lives ever so much easier, so let's take care of that now.
We'll be using Alex Edward's session package with a Redis store in order to manage sessions for our web application. That mean's we'll need to set up a pool of Redis connections, and implement sessions for our application. Let's take care of that now.
Let's set up an application configuration type and variable that we can use to share configuration information with the various parts of our application.
Let's define a stub handler for the home page, set up a routes file, and start the web server.
Before we can display any pages on our site, we'll need to set up some templates and build a render function that parses and displays those templates. Let's get started.
Before we can hit a page on our web application, we'll need to set up a bit of middleware to load and save the session on every request. Let's take care of that now.
Let's set up a few more stub handlers and routes to them for the rest of our pages.
When we have an application that has goroutines running in the background, it's never a good idea to just stop everything abruptly when the application stops. Instead, we should take advantage of Go's concurrency features and shut down gracefully. Let's take care of that now.
Let's put some content into our database.
Let's install a data package that consists of models for our database tables, functions to interact with the database, and a simple means of making the models and functions available to our application.
Let's finish up the authentication portion of our web application, so that we can move on with some concurrency in the next section.
In this section, we'll figure out how to send email behind the scenes, in its own GoRoutine.
In order to send email, we'll need some code that actually sends email messages. Let's get started.
Let's finish up the mailer.go file.
Let's make sure our code to send mail actually works by sending a message synchronously.
Let's write the code necessary to allow us to send email in the background.
Whenever we send an email concurrently, we'll need to increment the WaitGroup in our application config by 1. Let's make our lives simpler by writing a simple helper function that does that for us (because I know I'll forget, otherwise).
Let's update our Login handler to send an email notification to a user when there is a failed login attempt on their account.
Finally, let's update our shutdown() function to clean things up by closing the channels.
We're going to be sending an activation email, so let's create the necessary templates, and let's also secure that email by signing the URL, which will make it tamper proof.
Let's write the handler to add a user, and to send that user an activation email.
Now that we can send an activation email, let's write the code necessary to make the user active after he or she clicks the link in the email.
Let's give all user data for an authenticated to user to our templates, in case we need it.
Let's get started with the page that will display available subscription packages to authenticated users, and allow them to purchase one.
Let's add a route to the plans page, and try things out.
Let's write a stub handler with some comments which will run when an authenticated user subscribes to a given plan.
Let's get started with our SubscribeToPlan handler.
Next, let's generate an invoice and email it to the customer.
Let's try things out, fix a couple of mistakes, and subscribe a user to a plan.
In order to test our project, we'll need to set up a testing environment first. Let's take care of that now.
Let's write a test to ensure that our routes are properly registered.
Let's write a test for our render.go file.
Let's modify our data package to use interfaces instead of concrete types, so that we can create UserTest and PlanTest types which don't touch the database. That way, we can run unit tests without actually needing a running database.
Let's finish up our data package interfaces by implementing the methods necessary for PlanTest to satisfy the PlanInterface type.
Let's get started by writing some tests for our site pages.
Let's write a test for our login handler, and fix the way that we access the database in our handlers.
Finally, let's test a handler that makes use of concurrency, and update our setup_test.go file to decrement our WaitGroup when discarding emails.
OpenCourser helps millions of learners each year. People visit us to learn workspace skills, ace their exams, and nurture their curiosity.
Our extensive catalog contains over 50,000 courses and twice as many books. Browse by search, by topic, or even by career interests. We'll match you to the right resources quickly.
Find this site helpful? Tell a friend about us.
We're supported by our community of learners. When you purchase or subscribe to courses and programs or purchase books, we may earn a commission from our partners.
Your purchases help us maintain our catalog and keep our servers humming without ads.
Thank you for supporting OpenCourser.