Concurrency
In the context of Go, concurrency is the capacity to work on many tasks at once. Goroutines in GoLang and channels are two potent structures that provide strong concurrency support in the Go programming language, which was created by Google. This characteristic is a major factor in Go’s appeal, particularly when it comes to creating large-scale distributed systems and high-performance applications.
Concurrency and parallelism are different, although they are related. The simultaneous execution of several entities is known as parallelism, whereas the architecture of your program’s components to allow for independent execution when feasible is known as concurrency. Program design and maintainability can be enhanced by a legitimate concurrent design even in the absence of actual parallelism.
Goroutines in Golang
A goroutine is a function that can execute in parallel to other functions. By design, Go is a concurrent programming language since each program is executed by one or more goroutines. The main function of a Go program is the only implicit goroutine in which it is initially executed.
Characteristics of goroutines
Lightweight: Because of their design, goroutines are much lighter than conventional operating system (OS) threads, using only a few kilobytes of overhead. Because of its efficiency, thousands or even millions of goroutines can be created without experiencing serious performance problems.
Easy to Create: Simply using the go keyword and a function invocation results in a goroutine. Either an anonymous (inline) function or a regular named function may be used.
Managed by Go Scheduler: The Go runtime’s internal scheduler (an m:n scheduler model) multiplexes several goroutines onto fewer OS threads in order to schedule them, in contrast to OS threads. The developer is mainly unaware of how complicated this mapping and scheduling are.
Non-Deterministic Execution Order: You must realise that you cannot predict the sequence in which the goroutines will be performed. The output may vary with each run because their order is dependent on the OS scheduler, the Go scheduler, and the system load.
Immediate Return: The program doesn’t wait for the goroutine to finish when a function is called as a goroutine; instead, it moves straight on to the following line of code.
Goroutine Code Examples
Basic Goroutine Example
This straightforward program shows you how to launch a goroutine:
package main
import (
"fmt"
"time"
)
func sleepyGopher() {
time.Sleep(3 * time.Second) // Simulate some work
fmt.Println("... snore ...")
}
func main() {
go sleepyGopher() // Start sleepyGopher as a goroutine
time.Sleep(4 * time.Second) // Wait for the goroutine to finish (this is a simple, but not ideal, way to wait)
fmt.Println("Main function exited.")
}
Output
... snore ...
Main function exited.
Explanation: The main goroutine is used in this example. Upon calling go sleepyGopher(), a new goroutine is started to run sleepyGopher alongside main. Without waiting for sleepyGopher to finish, the main function then continues. the moment.In this case, sleep in main is essential because without it, main would automatically exit, ending the program before sleepyGopher could print its message.
An anonymous function can also be used to generate a goroutine for brief code segments:
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("start")
go func() {
fmt.Println("processing")
}()
time.Sleep(time.Millisecond * 10) // Small delay to allow goroutine to run
fmt.Println("done")
}
Output
start
processing
done
Explanation: This sample demonstrates how to build a goroutine by using the go keyword directly in conjunction with an anonymous function. After initiating the anonymous goroutine, the main function promptly resumes its execution.
Multiple Goroutines Example
Multiple goroutines can be simply created, frequently by looping:
package main
import (
"fmt"
"math/rand"
"time"
)
func f(n int) {
for i := 0; i < 10; i++ {
fmt.Println(n, ":", i)
amt := time.Duration(rand.Intn(250)) // Random delay
time.Sleep(time.Millisecond * amt)
}
}
func main() {
rand.Seed(time.Now().UnixNano()) // Seed the random number generator
for i := 0; i < 10; i++ {
go f(i) // Launch a new goroutine for each i
}
var input string
fmt.Scanln(&input) // Wait for user input to keep the main goroutine alive
}
Output
9 : 0
3 : 0
7 : 0
1 : 0
2 : 0
0 : 0
5 : 0
4 : 0
8 : 0
6 : 0
2 : 1
0 : 1
3 : 1
0 : 2
7 : 1
4 : 1
2 : 2
7 : 2
8 : 1
5 : 1
6 : 1
0 : 3
1 : 1
0 : 4
9 : 1
0 : 5
3 : 2
6 : 2
7 : 3
4 : 2
1 : 2
1 : 3
2 : 3
3 : 3
5 : 2
1 : 4
7 : 4
0 : 6
8 : 2
2 : 4
3 : 4
5 : 3
6 : 3
9 : 2
5 : 4
5 : 5
4 : 3
8 : 3
7 : 5
8 : 4
6 : 4
3 : 5
3 : 6
3 : 7
8 : 5
1 : 5
6 : 5
5 : 6
4 : 4
8 : 6
7 : 6
0 : 7
3 : 8
4 : 5
1 : 6
2 : 5
9 : 3
9 : 4
5 : 7
7 : 7
4 : 6
5 : 8
2 : 6
8 : 7
0 : 8
2 : 7
6 : 6
4 : 7
9 : 5
3 : 9
8 : 8
6 : 7
4 : 8
1 : 7
2 : 8
0 : 9
4 : 9
1 : 8
7 : 8
5 : 9
1 : 9
6 : 8
9 : 6
9 : 7
8 : 9
6 : 9
2 : 9
7 : 9
9 : 8
9 : 9
Explanation: Ten goroutines, each with a distinct n value, are launched by this program to execute the f function. Because of the time.Rest and make money.During calls inside f, you’ll see that the output lines from many goroutines interleave, indicating that they are running concurrently. To keep all other goroutines from stopping if the main goroutine were to depart too soon, the fmt.Scanln(&input) line is utilised. The precise order in which concurrent goroutines produce their output is non-deterministic in the absence of adequate synchronisation.
Need for Synchronization
time.Although sleep can make goroutine output visible, it is not a reliable method of coordinating or ensuring that all goroutines are finished. In order to enable goroutines to interact, synchronise their execution, and securely manage shared data, Go offers channels and primitives in the sync package (such as sync.WaitGroup and sync.Mutex) for reliable concurrent programming. Avoiding problems like race situations, when several goroutines access shared data at once and produce unpredictable results, requires this coordination. “Don’t communicate by sharing memory, share memory by communicating” is a fundamental tenet of Go’s concurrency paradigm.
You can also read Nil Values In Go: Zero Value For Pointers, Interfaces, Maps