Skip to content
Go Basics: Count Concurrent Events with Typed Atomics

Go Basics: Count Concurrent Events with Typed Atomics

October 31, 2026

A shared count++ operation is not safe merely because the variable is an integer. Use a typed atomic for an independent event counter and wait before interpreting its final total.

Prepare a scratch directory

Tested on Linux amd64 with Go 1.26.10. Install Go and make go available on PATH. You also need Bash, a text editor, and the standard mktemp, rm, and rmdir utilities. The example uses only the standard library and needs no module or external dependency.

Keep the same terminal open. Stop if setup fails, before saving the source:

start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 1

Run the example

Save this as atomic-counter-demo.go:

package main

import (
	"fmt"
	"sync"
	"sync/atomic"
)

func main() {
	var total atomic.Int64
	var workers sync.WaitGroup
	for worker := 0; worker < 4; worker++ {
		workers.Add(1)
		go func() {
			defer workers.Done()
			for event := 0; event < 1000; event++ {
				total.Add(1)
			}
		}()
	}
	workers.Wait()
	fmt.Println("Events:", total.Load())
	old := total.Swap(0)
	fmt.Println("Reset returned:", old)
	fmt.Println("Current:", total.Load())
}

Run it:

go run atomic-counter-demo.go

Observed output:

Events: 4000
Reset returned: 4000
Current: 0

Read the result

Each Add updates the counter atomically. Wait establishes that all four loops have finished before the final Load. Swap resets the value while returning the previous value as one operation; a separate load followed by store would not provide that combined behavior.

Apply it carefully

Do not copy an atomic value after first use, or mix atomic access with unsynchronized ordinary access to the same state. A counter is a good isolated use case. Invariants involving multiple fields usually need a mutex or another design that protects the complete operation.

The official Go reference documents the behavior used here.

Clean up

Remove the saved source, return to the starting directory, and remove the empty practice directory. go run does not leave an executable here; Go manages its build cache separately.

rm -- atomic-counter-demo.go
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dir

If the directory is not empty, inspect its remaining contents before removing anything else.

Acknowledgements

This post was created with assistance from AI.

Last updated on