Keep control of goroutines with a Context construct
Simpler with Context
To wrap this common synchronization pattern into an easily deployed package, the Go standard library provides Context
structs [1]. They are used in Google's data centers by servers, which often have to call many goroutines to compile the results for incoming user requests. If this takes too long, the main function handling the request must be able to contact all the goroutines that are still running, forcing them to immediately abandon their current work, release any allocated resources, and terminate their program flow.
The context's interface uses Done()
to return an open channel from which the worker bees try to read. However, no message ever reaches the channel (like in the previous example). Instead, the main program calls close()
to close the channel abstracted in the struct when it's time for the final whistle, using the context's exported Cancel()
function, which suddenly gives the reading workers an error value. They field this and interpret it as a signal to close up shop.
Listing 2 imports context
to introduce the standard library package of the same name in line 4. The context.WithCancel()
function extends a standard context created by context.Background()
and returns two things: a context object in ctx
and a cancel()
function that the programmer calls later on (in line 17 in Listing 2) to send the signal to end the party and turn off the lights.
Listing 2
context.go
01 package main 02 03 import ( 04 "context" 05 "fmt" 06 "time" 07 ) 08 09 func main() { 10 ctx, cancel := context.WithCancel(context.Background()) 11 12 work(ctx) 13 work(ctx) 14 work(ctx) 15 16 time.Sleep(3 * time.Second) 17 cancel() 18 time.Sleep(3 * time.Second) 19 } 20 21 func work(ctx context.Context) { 22 go func() { 23 for i := 0; i < 10; i++ { 24 fmt.Printf("%d\n", i) 25 26 select { 27 case <-ctx.Done(): 28 fmt.Printf("Ok. I quit.\n") 29 return 30 case <-time.After(time.Second): 31 } 32 } 33 }() 34 }
Worker bees use ctx.Done()
to extract the channel to be monitored from the context and insert a case
statement with a read operation into their select loops; they then use this mechanism to receive control commands from their caller. After compilation, the output from Listing 2 looks exactly like Figure 1 and exhibits exactly the same behavior. This is not surprising, since the context implementation uses the same internal infrastructure.
Inside Google's Brain
In Google's data centers, all worker functions use a context variable as their first parameter; this controls any premature termination that may be necessary. However, it also helps pass down payloads of received requests, such as the name of the authenticated user or credentials for subsystems. In this way, all subsystems across all API boundaries support certain standard functions such as timeouts, cleanup signals due to unsolvable problems, or simply convenient access to global key/value values.
Brake and Lose
Listing 3 shows what this kind of server function could look like in a practical use case. It retrieves four URLs: the Google, Facebook, and Amazon splash pages, along with the artificially slowed down website deelay.me
. Line 23 shows that it calls the AOL website with a delay of 5,000 milliseconds to make the client think it has a lame Internet connection.
Listing 3
delay.go
01 package main 02 03 import ( 04 "context" 05 "fmt" 06 "net/http" 07 "time" 08 ) 09 10 type Resp struct { 11 rcode int 12 url string 13 } 14 15 func main() { 16 ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) 17 defer cancel() 18 19 urls := []string{ 20 "https://www.google.com", 21 "https://www.facebook.com", 22 "https://www.amazon.com", 23 "https://deelay.me/5000/www.aol.com", 24 } 25 26 results := make(chan Resp) 27 28 for _, url := range urls { 29 chkurl(ctx, url, results) 30 } 31 32 for _ = range urls { 33 resp := <-results 34 fmt.Printf("Received: %d %s\n", resp.rcode, resp.url) 35 } 36 } 37 38 func chkurl(ctx context.Context, url string, results chan Resp) { 39 fmt.Printf("Fetching %s\n", url) 40 httpch := make(chan int) 41 42 go func() { 43 // async url fetch 44 go func() { 45 resp, err := http.Get(url) 46 if err != nil { 47 httpch <- 500 48 } else { 49 httpch <- resp.StatusCode 50 } 51 }() 52 53 select { 54 case result := <-httpch: 55 results <- Resp{ 56 rcode: result, url: url} 57 case <-ctx.Done(): 58 fmt.Printf("Timeout!!\n") 59 results <- Resp{ 60 rcode: 501, url: url} 61 } 62 }() 63 }
The main
program now goes through these URLs in the for
loop starting in line 28 and passes each one to the chkurl()
function, along with a context variable and a results
channel. The latter returns the workers' results to the main program in the form of Resp
structures. This data type, defined starting in line 10, stores the URL obtained along with the request's HTTP return code.
In the process, chkurl()
processes the requests asynchronously. It starts a goroutine from line 42 for time-consuming retrieval over the web and therefore quickly returns to the main program. Results bubble up later on via the results
channel, where the for
loop collects them starting in line 32, outputting the URLs along with their numeric result codes.
« Previous 1 2 3 Next »
Buy this article as PDF
(incl. VAT)
Buy Linux Magazine
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters
Support Our Work
Linux Magazine content is made possible with support from readers like you. Please consider contributing when you’ve found an article to be beneficial.
![Learn More](https://www.linux-magazine.com/var/linux_magazin/storage/images/media/linux-magazine-eng-us/images/misc/learn-more/834592-1-eng-US/Learn-More_medium.png)
News
-
NVIDIA Released Driver for Upcoming NVIDIA 560 GPU for Linux
Not only has NVIDIA released the driver for its upcoming CPU series, it's the first release that defaults to using open-source GPU kernel modules.
-
OpenMandriva Lx 24.07 Released
If you’re into rolling release Linux distributions, OpenMandriva ROME has a new snapshot with a new kernel.
-
Kernel 6.10 Available for General Usage
Linus Torvalds has released the 6.10 kernel and it includes significant performance increases for Intel Core hybrid systems and more.
-
TUXEDO Computers Releases InfinityBook Pro 14 Gen9 Laptop
Sporting either AMD or Intel CPUs, the TUXEDO InfinityBook Pro 14 is an extremely compact, lightweight, sturdy powerhouse.
-
Google Extends Support for Linux Kernels Used for Android
Because the LTS Linux kernel releases are so important to Android, Google has decided to extend the support period beyond that offered by the kernel development team.
-
Linux Mint 22 Stable Delayed
If you're anxious about getting your hands on the stable release of Linux Mint 22, it looks as if you're going to have to wait a bit longer.
-
Nitrux 3.5.1 Available for Install
The latest version of the immutable, systemd-free distribution includes an updated kernel and NVIDIA driver.
-
Debian 12.6 Released with Plenty of Bug Fixes and Updates
The sixth update to Debian "Bookworm" is all about security mitigations and making adjustments for some "serious problems."
-
Canonical Offers 12-Year LTS for Open Source Docker Images
Canonical is expanding its LTS offering to reach beyond the DEB packages with a new distro-less Docker image.
-
Plasma Desktop 6.1 Released with Several Enhancements
If you're a fan of Plasma Desktop, you should be excited about this new point release.