Concurrency in Go: Part 1
In this blog, we will discuss about Go concurrency, how concurrency is achieved in Go using Goroutines and Channels (about Channels in Part 2). If you are familiar with Basic Go and exploring topics in it then go ahead.
Concurrency in Go is a core feature of the programming language that allows you to efficiently execute multiple tasks simultaneously.
Lets understand with an example, a chef in a kitchen does multitask by switching between cooking dishes, and managing one task at a time but handling multiple overall. In the same way the concurrency deals with many tasks but doesn't do them simultaneously.
Go has rich support for concurrency using goroutines and channels. In this Article we will look into how Goroutines work.
Goroutines
Goroutine is a lightweight thread managed by Go runtime. It allows multiple tasks to be executed concurrently. Goroutines are more efficient than threads because they are light weight, use less memory and can be scheduled more quickly.
It is lightweight, costing little more than the allocation of stack space. And the stacks start small, so they are cheap, and grow by allocating (and freeing) heap storage as required.
Goroutine always works in the background. Every program contains at least a single Goroutine and that Goroutine is known as the main Goroutine. All the Goroutines are working under the main Goroutines if the main Goroutine terminated, then all the goroutine present in the program also gets terminated.
To create a goroutine, we use the go keyword followed by the function/method that we want to run concurrently.
Syntax:
func name(){
// statements
}
// using go keyword as the prefix of function call
go name()
Example:
package main
import "fmt"
func display(str string) {
fmt.Println(str)
}
func main() {
go display("executed Goroutine")
display("executed normal function")
}
Output: executed normal function
In the above example, we simply create a display() function and then call this function in two different ways first one is a Goroutine, i.e. go display(“executed Goroutine”) and another one is a normal function, i.e. display(“executednormal function”). But we see, it only displays the result of the normal function and not result of Goroutine because when a new Goroutine executed, the Goroutine call return immediately. The control does not wait for Goroutine to complete their execution just like normal function they always move forward to the next line after the Goroutine call and ignores the value returned by the Goroutine.
So, to execute a Goroutine properly, we made some changes in our program as shown in the below code:
package main
import (
"fmt"
"time"
)
func display(str string) {
fmt.Println(str)
}
func main() {
go display("executed Goroutine")
time.Sleep(1 * time.Second)
display("executed normal function")
}
Output:
executed Goroutine
executed normal function
We added the Sleep() method in our program which makes the main Goroutine sleeps for 1 second in between 1-second the new Goroutine executes, displays “executed Goroutine” on the screen, and then terminate after 1-second main Goroutine re-schedule and perform its operation. Here, both Goroutine and the normal function work concurrently.
Multiple Goroutines
Let's see one more example that start multiple goroutines to better understand goroutine.
package main
import (
"fmt"
"time"
)
func display1() {
for i := 1; i <= 3; i++ {
time.Sleep(200 * time.Millisecond)
fmt.Printf("%d ", i)
}
}
func display2() {
for i := 4; i <= 6; i++ {
time.Sleep(500 * time.Millisecond)
fmt.Printf("%d ", i)
}
}
func main() {
go display1()
go display2()
time.Sleep(2000 * time.Millisecond)
fmt.Println("main terminated")
}
In the above program we see 2 goroutines display1 and display2. these goroutines run concurrently.display1() sleeps for 200ms and prints 1, continues the same cycle till prints 3. Similarly display2() sleeps for 500ms and prints 4 continues till it prints 6. The main goroutine starts display1 and display2, sleeps for 2000ms and terminates.
Output: 1 2 4 3 5 6 main terminated
Advantages
Goroutines are cheap when compared to threads. They are only a few kb in stack size and the stack can grow and shrink according to the needs of the application whereas in the case of threads the stack size is fixed.
Goroutines communicate using channels. Channels by design prevent race conditions from happening when accessing shared memory using Goroutines. Channels can be thought of as a pipe using which Goroutines communicate.
Here continues the next part.
Thank you!
Subscribe to my newsletter
Read articles from SHIVANI GANIMUKKULA directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by