go
44 lines · 6 steps
A lock-free request counter in Go
How atomic types let many goroutines tally requests and errors without a mutex.
Explained by
highlit
1package metrics
2
3import (
4 "sync/atomic"
5 "time"
6)
7
8type RequestCounter struct {
9 total atomic.Uint64
10 errors atomic.Uint64
11 lastHit atomic.Int64
12}
13
14func (c *RequestCounter) Record(failed bool) {
15 c.total.Add(1)
16 if failed {
17 c.errors.Add(1)
18 }
19 c.lastHit.Store(time.Now().UnixNano())
20}
21
22func (c *RequestCounter) Snapshot() (total, errors uint64, last time.Time) {
23 total = c.total.Load()
24 errors = c.errors.Load()
25 if ns := c.lastHit.Load(); ns != 0 {
26 last = time.Unix(0, ns)
27 }
28 return
29}
30
31func (c *RequestCounter) Reset() (total, errors uint64) {
32 total = c.total.Swap(0)
33 errors = c.errors.Swap(0)
34 c.lastHit.Store(0)
35 return
36}
37
38func (c *RequestCounter) ErrorRate() float64 {
39 total := c.total.Load()
40 if total == 0 {
41 return 0
42 }
43 return float64(c.errors.Load()) / float64(total)
44}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Atomic types give you safe concurrent counters without the overhead or ceremony of a mutex.
- 2Swap lets you read and reset a counter in one indivisible step, avoiding lost updates.
- 3Encoding a timestamp as an int64 of nanoseconds makes even time atomically storable.
Related explainers
go
package breaker import ( "errors"
How a circuit breaker works in Go
circuit-breaker
state-machine
concurrency
Intermediate
8 steps
ruby
module UniqueJob extend ActiveSupport::Concern class_methods do
Deduplicating Active Job enqueues in Rails
concurrency
idempotency
caching
Advanced
9 steps
java
public class TimedFetchService { private final ExecutorService executor = Executors.newFixedThreadPool(8); private final HttpClient httpClient = HttpClient.newHttpClient();
Enforcing HTTP timeouts with a Future
concurrency
timeouts
thread-pool
Intermediate
8 steps
go
package middleware import ( "net/http"
Role-based access control middleware in Gin
middleware
authorization
closures
Intermediate
7 steps
go
func UserDashboardCache(rdb *redis.Client, ttl time.Duration) gin.HandlerFunc { return func(c *gin.Context) { claims, ok := c.Get("claims") if !ok {
Per-user response caching in Gin with Redis
middleware
caching
redis
Advanced
9 steps
javascript
import { unstable_cache, revalidateTag } from 'next/cache' import { db } from '@/lib/db' export const getDashboardStats = unstable_cache(
Caching dashboard stats in Next.js
caching
cache-invalidation
tag-based-revalidation
Intermediate
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/a-lock-free-request-counter-in-go-explained-go-1581/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.