Go Concurrency
Links: 103 Golang Index
Concurrency vs Parallelism¶
- Concurrency means loading more goroutines at a time. If one goroutine blocks, another one is picked up and started. On single core CPU you can run ONLY concurrent applications but they are not run in parallel.
- Parallelism means multiple goroutines executed at the same time.
- It requires multiple CPUs.
- Concurrency means independently executing processes or dealing with multiple things at once, while parallelism is the simultaneous execution of processes and require multiple core CPUs.
- Concurrency means context switching.
- CPU time is divided between different processes.
- Most languages were designed with a single processor in mind.
Goroutines¶
- A goroutine is a lightweight thread of execution
- Goroutines are key ingredients to achieve concurrency in Go.
- A goroutine is a function that is capable of running concurrently with other functions.
- To create a goroutine we use the keyword
go
followed by a function invocation - Goroutines are far smaller that threads, they typically take around 2kB of stack space to initialise compared to a thread which takes a fixed size of 1-2Mb.
- An OS Thread Stack is fixed size but a goroutine stack size shrinks and grows as needed.
- OS threads are scheduled by the OS kernel, but goroutines are scheduled by its own Go Scheduler using a technique called
m:n
scheduling, because it multiplexes (or schedules) m goroutines on n OS threads.- Scheduling a goroutine is much cheaper than scheduling a thread.
- Goroutines have no identity. There is no notion of identity that is accessible to the programmer.
Miscellaneous¶
-
We can explicitly control the number of threads (logical CPUs) that will be used using
runtime.GOMAXPROCS(0)
- This is the n in
m:n
scheduling - If argument is < 1 then it doesn't change the setting
- It is by default equal to the number of logical cpu cores
runtime.NumCPU()
- More threads will generally increase performance but a lot can slow it down.
- This is the n in
-
If you set
GOMAXPROCS
to 1 then you will have a truly concurrent application.
In general if you are developing an application you should start with GOMAXPROCS > 1
to find any concurrency or race conditions in the application.
WaitGroups¶
- By default
main
doesn't wait for other go routines to finish.- We need some sort of synchronisation.
- In the above example
f2
didn't get time to execute andmain
didn't wait for it.- If we would have added a delay then
f2
would have got time to execute but this is not ideal.
- If we would have added a delay then
- This synchronisation problem is solved by
waitgroups
- It will wait of all the go routines that have been launched to finish.
-
The pattern to use
sync.WaitGroup
is:- Create a new variables of a
sync.WaitGroup
(wg) - Call
wg.Add(n)
wheren
is the number of goroutines to wait for - Execute
defer wg.Done()
in each goroutine to indicate to theWaitGroup
that the goroutine has finished executing - Call
wg.Wait()
in main() where we want to block.import ( "fmt" "sync" ) func f2(wg *sync.WaitGroup) { defer (*wg).Done() // same as wg.Done() fmt.Println("function 2") } func main() { var wg sync.WaitGroup wg.Add(1) // no of goroutines fmt.Println("hello there") go f2(&wg) // takes a pointer to wg wg.Wait() fmt.Println("last line of main") } // hello there // function 2 // last line of main
- Create a new variables of a
-
We can also define
waitgroups
globally and use them in our functions.
var wg = sync.WaitGroup{}
func p1(a int) {
defer wg.Done()
fmt.Println(a)
}
func main() {
wg.Add(1)
go p1(45)
wg.Wait()
}
Example¶
import (
"fmt"
"log"
"net/http"
"runtime"
"strings"
"sync"
)
func checkAndSaveBody(url string, wg *sync.WaitGroup) {
resp, err := http.Get(url)
if err != nil {
fmt.Printf("%s is DOWN!\n", url)
} else {
fmt.Printf("Status Code: %d ", resp.StatusCode)
}
wg.Done()
}
func main() {
urls := []string{"https://www.golang.org", "https://www.google1.com", "https://www.medium.com"}
var wg sync.WaitGroup
wg.Add(len(urls)) // n is len(urls) - the number of goroutines to wait for
// this would be very fast if we had thousands of urls to check
for _, url := range urls {
go checkAndSaveBody(url, &wg)
}
fmt.Println("No. of Goroutines:", runtime.NumGoroutine())
wg.Wait()
}
//** EXPECTED OUTPUT: **//
// No. of Goroutines: 4
// https://www.google1.com is DOWN!
// Status Code: 200 Writing response Body to www.golang.org.txt
// Status Code: 200 Writing response Body to www.medium.com.txt
Last updated: 2022-06-18