>Less messy than callbacks but more messy than async/await imho. Maybe there are nicer examples?
That book chapter doesn't really illustrate the utility of Go's concurrency very well, it just explains the basic components that make it work. Let's say you wanted to fetch three text documents via HTTP and print each of them. Here's the regular, blocking way to do that (error handling, package imports, etc. omitted for brevity):
func main() {
documentURLs := []string{
"http://example.org/foo.txt",
"http://example.net/bar.txt",
"http://example.com/quux.txt",
}
for _, url := range documentURLs {
response, err := http.Get(url)
body, err := ioutil.ReadAll(response.Body)
fmt.Print(string(body))
}
}
For each URL in the documentURLs list, make a GET request to that URL, read the body of the HTTP response, and convert it to a string (from an array of bytes) and print it. First it'll fetch the first document and print, then the second, then the third. Of course, we'd prefer not to wait for the previous request to finish before we perform the next, so let's make it concurrent.
func main() {
documentURLs := []string{
"http://example.org/foo.txt",
"http://example.net/bar.txt",
"http://example.com/quux.txt",
}
// `documents` is a channel of values of type `string`.
// A channel is a safe FIFO queue.
documents := make(chan string)
for _, url := range documentURLs {
go func() {
response, err := http.Get(url)
body, err := ioutil.ReadAll(response.Body)
documents
OK, let's see what's new here. First, we're making a "channel" which we will put our text documents into as we receive them. Second, the loop over documentURLs is a little different. We put the code into an anonymous function and run it with the `go` keyword. This starts the function in a "goroutine", which is like a light-weight thread. Because we run the function in a new goroutine, the loop does not wait for the anonymous function to complete and the loop continues immediately to the next URL, for which a function is again launched in a new goroutine, and so on for all the URLs. Anonymous functions are closures in Go, so we don't need to explicitly pass in the `documents` and `url` variables (actually this program is buggy, but that's a minor detail).
In the closure we make an HTTP request and read the response body, just like before, but instead of printing the result directly, we send it to the `documents` channel. Basically, we start jobs on three new threads and tell them to put the result of the work in a queue. When we have started the jobs with the first loop, we proceeed to the next loop where we read the strings being sent to the `documents` channel and print them as we receive them. Reading on a channel blocks until there is something to receive.
So I hope you'll agree that this is a pretty simple way to run things concurrently: just use multi-threading. Except we're not using OS threads, which are expensive, we are using goroutines—light-weight green threads managed by the Go runtime. You can have thousands or hundreds of thousands of goroutines running at once, OS threads don't scale that well. If you block a goroutine (e.g., when performing an HTTP request), the Go runtime will just schedule another goroutine, which is cheap. The Go runtime may use a single thread (in which case the program is concurrent but not parallel) or it may use multiple threads (in which the program is both concurrent and parallel).
If instead you were writing a network server with Go, here's how you'd do it (example from https://golang.org/pkg/net/):
ln, err := net.Listen("tcp", ":8080")
for {
conn, err := ln.Accept()
go handleConnection(conn)
}
You listen for new TCP connections, and when you get one, you hand over the connection to a function started in a new goroutine, and then you go back to listening for new connections again. It's the simple model of one thread per connection, except with goroutines it's actually scalable. You can block as much as you want in the handler goroutine and it won't block other goroutines. You can even start additional goroutines inside your handler goroutines. For example, if you wanted to fetch the text documents from the concurrent GET example and send them to your clients, you could adapt the `main` function from the concurrent GET example just a little bit and use that as your `handleConnection` function.
>It appears that with Go I still need to alter my own code to use libraries that are written to run async or am I wrong?
You can make asynchronous library APIs with goroutines and channels: when a function is called, start the work in a goroutine and return a channel. The goroutine sends the return value on the channel. It's kind of ugly and generally frowned upon; it's cumbersome for users who want to use it synchronously, and it's no better than doing it yourself if you do want to use it asynchronously. Instead it is preferred to expose synchronous APIs, which can then be made to run concurrently as desired.