Golang Concurrency — Keep it simple and stupid

The biggest selling point of GoLang is its Go Rountines for concurrency.

Following are important points regarding Go Routines:

  • Concurrency is a primitive in golang just as datatypes like int, float etc.
  • Among all languages that provide concurrency, I find GoLang has the most simplistic approach.
  • Go rountines speed up a program to a great extent in multi-core processors.
  • Golang go routines are independent of the actual number of OS threads. Hence, without worrying about thrashing (when the overhead of context switching in threads exceed actual execution) one can spawn any number of go routines. Typically in Java, one has to use a thread pool to keep a fixed number of threads and submit tasks to threads.
  • It would also be incorrect to term Go-Routines as green threads since they don’t run/managed in any VM.

Go Routines

The following program finds the number of prime numbers in a given range using go routines:

package main

import "fmt"

func isPrime(num int) bool {
for i:=2; i< num; i++ {
if num%i == 0 {
return false
}
}
return true
}

func getPrimesCountAsync(start, end int) chan int {
resChan := make(chan int)

go func() {
cnt := 0
for i:= start; i<=end; i++ {
if isPrime(i) {
cnt++
}
}
resChan <- cnt
close(resChan)
}()
return resChan
}

func main() {
inputs := [][]int { {4, 58000}, {20, 95000}, {50, 148000}, {1020, 5400}, {4, 48000}, {4, 90000} }
resChans := make([]chan int, len(inputs))

for idx, input := range inputs {
resChans[idx] = getPrimesCountAsync(input[0], input[1])
}

for _, resChan := range resChans {
fmt.Println(<- resChan)
}
}

As we can see, constructs are very simple.

  • go func() spawns a new co-routine.
  • chan can be used for communication with main thread.

Timing:

time go run playground.go25.26s user 0.31s system 209% cpu 12.213 total

Comparison with single threaded program

Following is a simple single threaded program:

package main

import "fmt"

func isPrime(num int) bool {
for i:=2; i< num; i++ {
if num%i == 0 {
return false
}
}
return true
}


func getPrimesCountSync(start, end int) int {
cnt := 0
for i:= start; i<=end; i++ {
if isPrime(i) {
cnt++
}
}
return cnt
}

func main() {
inputs := [][]int { {4, 58000}, {20, 95000}, {50, 148000}, {1020, 5400}, {4, 48000}, {4, 90000} }

for _, input := range inputs {
fmt.Println(getPrimesCountSync(input[0], input[1]))
}
}

Timing

time go run playground.go
18.33s user 0.27s system 99% cpu 18.750 total

Conclusion

  • Speed up: We can see while single threaded program takes 18.750s, multi-threaded go routine program takes 12.213s.
  • Simplicity: The multi-threaded program is very much similar to the single-threaded program except it returns a channel of result instead of the result itself and doesn’t require importing any additional utilities.

Thanks to Nirmal Ganesh M and Rajdeep Das for reviewing the article.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade