syncaccess
About 490 words
Detects potential data races and synchronization issues.
Category
Safety
What It Checks
This analyzer detects:
- Goroutines that use a map, slice, array or pointer the enclosing function declared, when the goroutine doesn't synchronize
- Goroutines that don't synchronize and assign a local variable or parameter (or, for a map, write or delete an element) while the enclosing function still uses it after the
gostatement, or while the samegostatement runs again in a loop - Maps that the goroutines a loop starts write without taking a lock, even when they otherwise synchronize (for example with a
sync.WaitGroup) - Loop variables that every iteration shares and a goroutine captures. From Go 1.22 on, a variable the
forclause declares is a new variable in every iteration, so only loops that assign a variable declared outside them are reported there. In a file whose language version is older, a variable theforclause declares is reported too. - Methods of a struct with a mutex field that access the struct's fields without calling
LockorRLock
A goroutine synchronizes when it takes a lock (Lock or RLock of a sync type or a sync.Locker), uses a channel, calls any other function of sync or sync/atomic, or calls a function value or an interface method, which might do any of these. A goroutine that takes a lock is never reported for sharing a variable. Variables are matched by identity, so a goroutine's own variable that shadows an outer one, or a field that shares a local variable's name, is not a capture.
Why It Matters
Data races cause:
- Corrupted data
- Unpredictable behavior
- Hard-to-reproduce bugs
- Security vulnerabilities
Examples
Bad: Unsynchronized Counter
type Counter struct {
value int // Accessed from multiple goroutines
}
func (c *Counter) Increment() {
c.value++ // Data race!
}
func (c *Counter) Get() int {
return c.value // Data race!
}Good: Mutex Protection
type Counter struct {
mu sync.Mutex
value int
}
func (c *Counter) Increment() {
c.mu.Lock()
defer c.mu.Unlock()
c.value++
}
func (c *Counter) Get() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.value
}Good: Atomic Operations
type Counter struct {
value atomic.Int64
}
func (c *Counter) Increment() {
c.value.Add(1)
}
func (c *Counter) Get() int64 {
return c.value.Load()
}Bad: Map Access
type Cache struct {
data map[string]string
}
func (c *Cache) Set(key, value string) {
c.data[key] = value // Data race!
}
func (c *Cache) Get(key string) string {
return c.data[key] // Data race!
}Good: sync.Map or RWMutex
type Cache struct {
data sync.Map
}
func (c *Cache) Set(key, value string) {
c.data.Store(key, value)
}
func (c *Cache) Get(key string) (string, bool) {
v, ok := c.data.Load(key)
if !ok {
return "", false
}
return v.(string), true
}Configuration
# .golint-sl.yaml
analyzers:
syncaccess: true # enabled by defaultWhen to Disable
- Single-threaded code
- Code protected by external synchronization
analyzers:
syncaccess: falseRelated Analyzers
- goroutineleak - Goroutine safety
- nilcheck - Nil safety
