Documentation
¶
Overview ¶
Package spin provides minimal spin-based primitives for performance-critical code paths:
- Lock — non-fair spinlock for extremely short critical sections
- Wait — adaptive spin-wait helper for tight polling loops
- Pause — architecture-specific CPU hint for busy-wait loops
- Yield — cooperative yield/sleep for non-hot paths
Design notes
- Hot paths should prefer Pause (light CPU hint) and Wait (adaptive backoff) instead of ad-hoc loops with runtime.Gosched().
- Lock is intentionally non-fair and intended only for very short critical sections where OS mutex overhead is prohibitive.
- No allocations in hot paths; functions return immediately and never block unless explicitly documented (Yield with a positive sleep duration).
Architectures: amd64, arm64, 386, arm, riscv64, ppc64le, s390x, loong64, wasm.
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Pause ¶
func Pause(cycles ...int)
Pause executes CPU pause instructions to reduce energy consumption in spin-wait loops. On hardware-pause targets, it does not block or yield the scheduler. On wasm and unsupported fallback targets, it may yield via runtime.Gosched.
Defaults to 30 pause hints if not specified. The cycles parameter is a historical repeat-count name, not a calibrated CPU-cycle delay. Uses optimized assembly on supported hardware-pause targets.
Usage:
Pause() // 30 pause hints (default) Pause(1) // 1 pause hint Pause(50) // 50 pause hints
func SetYieldDuration ¶
SetYieldDuration sets the base duration unit for Yield(). Safe for concurrent use. Recommended: 50-250 microseconds for real-time systems, 1-4 ms for general workloads.
func Yield ¶
Yield cooperatively yields execution to reduce CPU burn in tight loops.
By default, Yield sleeps for a small duration (configured by SetYieldDuration) to give other goroutines and the OS scheduler a chance to run.
If an explicit duration is provided, it overrides the default for this call. A non-positive duration disables sleeping and falls back to runtime.Gosched() (a purely cooperative yield without a timer sleep).
For automatic adaptive backoff in tight loops, use Wait instead.
Example ¶
package main
import (
"fmt"
"time"
"code.hybscloud.com/spin"
)
func main() {
spin.Yield(10 * time.Millisecond)
fmt.Println("yielded")
}
Output:
Types ¶
type Lock ¶
type Lock struct {
// contains filtered or unexported fields
}
Lock is a minimal, non-fair spin lock intended for very short critical sections on hot paths. It avoids allocations and OS mutex overhead but should not be used as a general-purpose lock.
Acquisition uses FAA (Fetch-And-Add) with a TTAS (test-and-test-and-set) slow path. FAA completes in a single atomic instruction (LOCK XADD on x86, LDADDAL on arm64) — under contention, CAS produces O(n²) cache-line invalidations while FAA keeps traffic at O(n). The TTAS slow path spins on read-only Load (MESI Shared) to avoid bouncing the cache line, and only attempts FAA when the lock appears free.
Example ¶
package main
import (
"fmt"
"code.hybscloud.com/spin"
)
func main() {
var lk spin.Lock
lk.Lock()
// critical section
lk.Unlock()
fmt.Println("locked")
}
Output: locked
func (*Lock) Lock ¶
func (sl *Lock) Lock()
Lock acquires the lock. Fast path: single FAA. Slow path: TTAS (test-and-test-and-set) — spin on read-only Load to keep the cache line in MESI Shared state, then attempt FAA only when the lock appears free.
func (*Lock) Try ¶
Try attempts to acquire the lock without blocking. It returns true if the lock was acquired.
Uses FAA rather than CAS: on x86 LOCK CMPXCHG takes exclusive cache-line ownership regardless of comparison outcome, so a failed CAS costs the same coherence traffic as FAA. FAA keeps the atomic pattern consistent across the Lock API.
A failed Try increments n without a corresponding decrement. This is intentional: Unlock resets n to zero via Store(0), clearing all accumulated increments. The uintptr counter space is large enough that overflow is not a practical concern.
type Wait ¶
type Wait struct {
// contains filtered or unexported fields
}
Wait is a lightweight adaptive spin-wait helper used in tight polling loops.
It escalates from a short CPU pause to a cooperative scheduler yield based on the recent history of calls. The zero value is ready to use.
Example ¶
package main
import (
"fmt"
"sync/atomic"
"time"
"code.hybscloud.com/spin"
)
func main() {
var sw spin.Wait
var ready atomic.Bool
go func() {
time.Sleep(50 * time.Microsecond)
ready.Store(true)
}()
for !ready.Load() {
sw.Once()
}
fmt.Println("ready")
}
Output: ready