Track down race conditions with Go
Classic Computer Science
The classical method for slowing down racers relies on what are known as mutex (mutually exclusive) constructs. Go's sync package provides mutex elements that use Lock()
to create a roadblock for subsequent goroutines and clear it again with Unlock()
after the critical region has been navigated.
Listing 5 shows the implementation of the book()
airplane-seat booking function; the call to the function in Line 16 is surrounded by a pair of mutexes. Before the call, line 15 uses mutex.Lock()
to block the section for subsequent goroutines. After the job is done, line 17 uses mutex.Unlock()
to lift the block again.
Listing 5
airline-mutex.go
01 package main 02 import ( 03 "fmt" 04 "sync" 05 "time" 06 ) 07 08 func main() { 09 seats := 1 10 mutex := &sync.Mutex{} 11 12 for i := 0; i < 2; i++ { 13 go func(id int) { 14 time.Sleep(100 * time.Millisecond) 15 mutex.Lock() 16 book(id, &seats) 17 mutex.Unlock() 18 }(i) 19 } 20 21 time.Sleep(1 * time.Second) 22 fmt.Println("") 23 } 24 25 func book(id int, seats *int) { 26 if *seats > 0 { 27 fmt.Printf("%d booked!\n", id) 28 *seats = 0 29 } else { 30 fmt.Printf("%d missed out.\n", id) 31 } 32 }
For the sake of clarity, the book()
function now encapsulates the posting process starting in line 25. As its input, it expects the ID of the goroutine and a pointer to the seats
variable, whose value it adjusts accordingly in case of a successful booking.
Height-Adjustable Barrier
One alternative to the mutex construct is provided by the sync.WaitGroup
construct, also from the sync package of the Go core library. Its Add()
function sets up a block with an adjustable height. A subsequent Wait()
waits for the block to be lifted, which a call to the Done()
function is happy to do.
The smart thing here is that multiple program parts can set up any number of blocks through subsequent calls to Add()
. You then need the same number of calls to Done()
before Wait()
grants passage again.
The code in Listing 6 waits for this to happen before starting the critical section with a prophylactic Wait()
statement in line 15, but the code continues immediately because there are no blocks in the way in the initial state. Line 16 then calls Add(1)
to add a barrier just before booking. Afterwards, book()
in line 17 can calmly make the booking, before line 18 uses Done()
to take down the barrier again.
Listing 6
airline-wg.go
01 package main 02 import ( 03 "fmt" 04 "sync" 05 "time" 06 ) 07 08 func main() { 09 seats := 1 10 wg := &sync.WaitGroup{} 11 12 for i := 0; i < 2; i++ { 13 go func(id int) { 14 time.Sleep(100 * time.Millisecond) 15 wg.Wait() 16 wg.Add(1) 17 book(id, &seats) 18 wg.Done() 19 }(i) 20 } 21 22 time.Sleep(1 * time.Second) 23 fmt.Println("") 24 } 25 26 func book(id int, seats *int) { 27 if *seats > 0 { 28 fmt.Printf("%d booked!\n", id) 29 *seats = 0 30 } else { 31 fmt.Printf("%d missed out.\n", id) 32 } 33 }
Conclusions
As always in Go, there are several possible solutions to the challenges of controlling racing goroutines – after all, these are well-known programming problems.
If two related instructions such as checking and placing a shared variable cannot be done atomically, you need to use a synchronization tool to temporarily block the critical time window between the instructions – to keep out any program threads attempting to rush in.
And always remember to clear the block later on, even if the booking fails for some reason. Otherwise, you have built a permanent lock into your program, which might consume resources or block access to some resources entirely. Test every case to make sure!
Infos
- Go: "Programming Snapshot – Golang" by Mike Schilli, Linux Magazine issue 250, September 2021, p. 54-60
- Listings for this article: ftp://ftp.linux-magazine.com/pub/listings/linux-magazine.com/251/
« Previous 1 2
Buy this article as PDF
(incl. VAT)
Buy Linux Magazine
Direct Download
Read full article as PDF:
Price $2.95
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters
Find SysAdmin Jobs
News
-
MNT Seeks Financial Backing for New Seven-Inch Linux Laptop
MNT Pocket Reform is a tiny laptop that is modular, upgradable, recyclable, reusable, and ships with Debian Linux.
-
Ubuntu Flatpak Remix Adds Flatpak Support Preinstalled
If you're looking for a version of Ubuntu that includes Flatpak support out of the box, there's one clear option.
-
Gnome 44 Release Candidate Now Available
The Gnome 44 release candidate has officially arrived and adds a few changes into the mix.
-
Flathub Vying to Become the Standard Linux App Store
If the Flathub team has any say in the matter, their product will become the default tool for installing Linux apps in 2023.
-
Debian 12 to Ship with KDE Plasma 5.27
The Debian development team has shifted to the latest version of KDE for their testing branch.
-
Planet Computers Launches ARM-based Linux Desktop PCs
The firm that originally released a line of mobile keyboards has taken a different direction and has developed a new line of out-of-the-box mini Linux desktop computers.
-
Ubuntu No Longer Shipping with Flatpak
In a move that probably won’t come as a shock to many, Ubuntu and all of its official spins will no longer ship with Flatpak installed.
-
openSUSE Leap 15.5 Beta Now Available
The final version of the Leap 15 series of openSUSE is available for beta testing and offers only new software versions.
-
Linux Kernel 6.2 Released with New Hardware Support
Find out what's new in the most recent release from Linus Torvalds and the Linux kernel team.
-
Kubuntu Focus Team Releases New Mini Desktop
The team behind Kubuntu Focus has released a new NX GEN 2 mini desktop PC powered by Linux.