A lot has been written on concurrency and it is a deep subject. In order to understand the subtlety of how concurrency is implemented in Go and Javascript respectively, consider this concept through this high-level, relatively simple example.
For deeper understanding of how coroutines, channels, and generators work I will include some links at the bottom.
Example
Simple Async / Await for a http request. The goal is to wait until we have the data, but not block the main execution context while we wait.
Javascript
const axios = require('axios');
(async function() {
console.log('here')
const str = await axios.get("https://google.com")
console.log('there')
console.log(str.status)
console.log('everywhere')
})()
console.log('also here')
With output:
here
also here
there
200
everywhere
Go
package main
import (
"fmt"
"net/http"
)
func get(url string, c chan int) {
resp, _ := http.Get(url)
fmt.Println("there")
c <- resp.StatusCode
}
func main() {
c := make(chan int)
fmt.Println("here")
go get("https://google.com", c)
status := <-c
fmt.Println("everywhere")
fmt.Println(status)
}
with output
here
there
everywhere
200
In both of these examples, the main execution thread is not blocked.
Let's explore what that means.
In javascript, we see the rest of the program executing while the http request is being made. Note the "also here" log order. With the await
we are avoiding callback hell by blocking the current function execution without blocking the overall process execution.
In Go, everything in the current context stops while we wait for the http request. This is because the main function waits for the return from our channel. But the thread is still available to the program as a whole see this explanation under "Blocking is fine". This is a result of the Go runtime and the core scheduler. It's very subtle the way that the main thread is blocked, but this is expected and encouraged behavior in Go because there is a top level language construct to schedule multiple processes in multiple threads and intelligently manage suspended contexts along the way.
We can also modify this example to show how execute other code while waiting for the http call to return.
package main
import (
"fmt"
"net/http"
"time"
)
func get(url string, c chan int) {
resp, _ := http.Get(url)
fmt.Println("Everywhere")
c <- resp.StatusCode
}
func main() {
fmt.Println("here")
c := make(chan int)
go get("https://google.com", c)
fmt.Println("there")
for {
select {
case status := <-c:
fmt.Println(status)
return
default:
fmt.Println("Also Here")
time.Sleep(1000 * time.Millisecond)
}
}
}
With Output:
here
Also Here
there
200
everywhere
In this case, we loop in the main function using for { select {.... The main function will hit the default case over and over while waiting for the return value from our spawned channel. We can spawn other coroutines and add to the cases in the select, or write additional procedural code in the default case. In Go this is an expected pattern to allow the language scheduler to handle these routines and potentially multiple threads along the way.
A note on resource limitations.
Remember that Javascript is single threaded. Concurrency is based on coroutines. Go is multi threaded, but a coroutine does not always mean a new thread. See here
Conclusion
Concurrency is important, and coroutines make concurrency possible on fewer resources. Javascript and Go both have schemes to make concurrent, non-blocking, executions more readable and tolerable. The differences are subtle, but result in two tools with a robust ability to execute a lot of async calls on fewer resources.
Reading List
- https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/
- https://codeburst.io/why-goroutines-are-not-lightweight-threads-7c460c1f155f
- https://golang.org/doc/faq#goroutines
- https://tour.golang.org/concurrency/1
- https://medium.com/javascript-scene/the-hidden-power-of-es6-generators-observable-async-flow-control-cfa4c7f31435
Top comments (0)