DEV Community

Vaibhav Thukral
Vaibhav Thukral

Posted on

What Exactly Concurrency in Go?: Running Tasks in Parallel

In this post we will explore about concurrency in Go:

  • Understanding Goroutines
  • Sending data to Channels
  • Controlling Code Flow and Simultaneous Tasks

Understanding Concurrency & Goroutines

What is concurrency in Go and what are these Goroutines?

Before understanding concurrency and Goroutines first understand how go program normally executes when we are not using any concurrency features because in a normal program if we have bunch of function calls that do stuff these functions are simply executes after each other in a below image

Image description

From this above image it will call from left to right in a sequence until all each function return something and most important thing is second function only start after the first function is finished and so on..

In Go you can run code in parallel and execute function concurrently by using a feature called Goroutines.

Image description

func main(){
greet("Nice to meet you!")
greet("How are you")
slowGreet("How...are...you?")
greet("I hope you're liking this content")
}
Enter fullscreen mode Exit fullscreen mode

This above code will work in a sequence the function will call one by one when after completing the each function execution

Assume slowGreet() functions take more than 2 mins to complete the execution of function after that last greet() function will execute.

Now to make it more faster we can use feature called Goroutines

To run this above code parallel by adding the go keyword

func main(){
go greet("Nice to meet you!")
go greet("How are you")
go slowGreet("How...are...you?")
go greet("I hope you're liking this content")
}
Enter fullscreen mode Exit fullscreen mode

By adding this built-in go keyword you tell go that you wanna run these functions as go routines which in end simply means they still will be execute but they now run in parallel instead of after each other.

We just added the go keyword here nothing else.

When you run this code it finishes instantly but you also see we don't get any output here in the console.

Image description

Why this happens?

To understand this it's important to understand and keep in mind the idea behind running function as goroutine is to run it in a non blocking way so that the next operation their after next function call their after and so on.. can immediately be invoked so if we run all four functions as goroutines as we doing it above then we essentially just dispatch these four goroutines task in above code then we are done in the main function.

And it also doesn't return a value and that's a place where a function called doesn't wait for the goroutine to complete that's the idea it's just dispatch and then it goes it's own.

Now you might think that these greet functions are so simple that they should immediately print something to the console but dispatching these four goroutines starting these four goroutines is faster that's why its program ends without output.

So what's the solution?

The solution for this problem is called channels

Channels are typed conduits that allow goroutines to exchange data. They're like pipes that connect goroutines, enabling them to send and receive values.

Typed: Channels are declared with a specific type, which determines the type of data that can be sent and received through the channel.

Conduits: Channels act as a pipeline or a communication channel between goroutines, allowing them to exchange data.

Think of a channel like a pipe that can only carry a specific type of fluid (data). Just as a water pipe can't carry oil, a channel declared with a specific type can only carry data of that type.

Declaring Channels

Channels are declared using the chan keyword followed by the type of data they'll carry:

ch := make(chan int) // declares a channel of integers

Sending and Receiving

Goroutines can send and receive values on channels using the <- operator:

ch <- 42 // send 42 on the channel
x := <-ch // receive a value from the channel

Channel Types

There are two types of channels:

  • Unbuffered Channels: No capacity, sending and receiving blocks until the other side is ready.
  • Buffered Channels: Has a capacity, sending and receiving doesn't block until the buffer is full or empty.

Buffered Channels
Buffered channels are declared with a capacity:

ch := make(chan int, 10) // declares a buffered channel of integers with capacity 10

Range and Select
Channels can be used with the range keyword to iterate over received values:

for x := range ch { // iterates over received values
fmt.Println(x)
}

Best Practices

  • Use channels to communicate between goroutines.
  • Use buffered channels for performance-critical code.
  • Close channels to avoid deadlocks.
  • Use range and select to handle channel operations.
  • By mastering channels, you'll be able to write efficient, concurrent, and safe Go programs.

Top comments (0)