Irbisa · Шпаргалка
sync, атомики и модель памяти
Мьютексы, sync.Once, атомики, happens-before, детектор гонок
Какие примитивы синхронизации даёт пакет
syncи для чего нужен каждый из них?Что не так с этим кодом на
WaitGroupи какwg.Goиз Go 1.25 исключает эту ошибку?func fetchAll(ids []int) []Row { var wg sync.WaitGroup rows := make([]Row, len(ids)) for i, id := range ids { go func() { wg.Add(1) defer wg.Done() rows[i] = load(id) }() } wg.Wait() return rows}Почему
defer mu.Unlock()сразу послеmu.Lock()считается вариантом по умолчанию и когда вместо него стоит снимать блокировку явно?// wrong: the cache-hit path returns with the mutex still heldfunc (c *Cache) Get(k string) ([]byte, error) { c.mu.Lock() if v, ok := c.m[k]; ok { return v, nil // never unlocked: every later Get blocks forever }…Чем отличаются deadlock, livelock и starvation?
IncrвызываетGet, удерживая тот же мьютекс. Что произойдёт при выполнении и почему в Go нет реентерабельного мьютекса?type Cache struct { mu sync.Mutex m map[string]int}func (c *Cache) Get(k string) int { c.mu.Lock() defer c.mu.Unlock() return c.m[k]} func (c *Cache) Incr(k string) int { c.mu.Lock() defer c.mu.Unlock() c.m[k]++ return c.Get(k)}Найдите баг: после того как один клиент перестал читать, весь чат-хаб замирает, и новые
Subscribeвисят, как и любой другойPublish.type Hub struct { mu sync.Mutex subs map[int]chan string} func (h *Hub) Subscribe(id int) <-chan string { h.mu.Lock() defer h.mu.Unlock() ch := make(chan string) h.subs[id] = ch return ch} func (h *Hub) Publish(msg string) { h.mu.Lock() defer h.mu.Unlock() for _, ch := range h.subs { ch <- msg }}Как сделать потокобезопасный ленивый синглтон в Go и что гарантирует
sync.Onceтакого, чего не даёт проверкаif instance == nil?// wrong: two goroutines can both see nil, and the read is a data racevar client *http.Client func Client() *http.Client { if client == nil { client = newClient()…Чем data race отличается от race condition и почему
-raceможет быть зелёным, пока защищённый мьютексом баланс уходит в минус?// wrong: -race is clean, every access is locked, and it is still brokenfunc (a *Account) Withdraw(n int64) error { if a.Balance() < n { // RLock ... RUnlock return ErrInsufficient } a.add(-n) // Lock ... Unlock: a *different* critical section…Почему несинхронизированный флаг остановки типа
boolсчитается багом, даже если «в худшем случае горутина прочитает устаревшее значение», а один байт не может порваться?// wrong: "it is just a bool"; there is no such thing as a benign racevar ready boolvar payload *Report func producer() { payload = build(); ready = true } // two plain stores, nothing orders them…Что даёт
sync/atomicи когда стоит использовать атомик вместоMutex?var hits atomic.Int64var ready atomic.Boolvar cfg atomic.Pointer[Config] hits.Add(1) // counter: read-modify-write in one stepready.Store(true) // flag: visible to every later Load…Этот датчик должен хранить максимальное увиденное значение. Каждое обращение атомарно,
-raceчист, но максимум иногда оказывается занижен. Почему и как исправить?type Gauge struct{ max atomic.Int64 } func (g *Gauge) Observe(n int64) { if g.max.Load() < n { g.max.Store(n) }}Чем
sync.Mutexотличается отsync.RWMutexи почему переход наRWMutexможет замедлить map, которую в основном читают?Как устроен
sync.Mutexвнутри и что такое его нормальный режим и режим голодания?Что такое
sync.Mapи когда он лучше или хуже обычнойmapподMutex?// right use: written once, read forever, e.g. a registry of compiled codecsvar codecs sync.Map // string -> Codec func codecFor(name string) (Codec, error) { if v, ok := codecs.Load(name); ok { return v.(Codec), nil // every read pays a type assertion…Как работает race detector и чего он не может найти?
// The detector sees only what runs, so make the test run the overlap.func TestLimiterConcurrent(t *testing.T) { l := NewLimiter(100) var wg sync.WaitGroup for range 64 { // one goroutine would find nothing wg.Go(func() {…Как работает
sync.Condи почему в большинстве Go-проектов вместо него используют канал?// before: Cond cannot be selected on, so no timeout and no shutdown wake-upfunc (q *CondQueue) Pop() Job { q.mu.Lock() defer q.mu.Unlock() for len(q.jobs) == 0 { // `for`, never `if`: the predicate may be false q.cond.Wait() // again once Wait has re-taken the lock…Найдите баг: health checker, который перебирает
Snapshot(), иногда падает сconcurrent map iteration and map write.type Registry struct { mu sync.RWMutex peers map[string]Peer} func (r *Registry) Add(p Peer) { r.mu.Lock() defer r.mu.Unlock() r.peers[p.ID] = p} func (r *Registry) Snapshot() map[string]Peer { r.mu.RLock() defer r.mu.RUnlock() return r.peers}Как
sync.Poolсокращает аллокации и когда он вредит, а не помогает?var bufs = sync.Pool{New: func() any { return new(bytes.Buffer) }} func render(w io.Writer, v any) error { b := bufs.Get().(*bytes.Buffer) b.Reset() // reset on the way IN: Get may return a used one…Что означает happens-before в модели памяти Go и какие операции его создают?
// Each pair is free of data races: the marked operation is the edge.var ready = make(chan struct{}) func publishByChannel() { cfg = load(); close(ready) } // close ...func readByChannel() { <-ready; use(cfg) } // ... before this receive…Почему double-checked locking на обычном указателе в Go сломан и как подменять на лету конфиг, который читает каждый запрос?
// wrong: textbook double-checked locking, a data race in Govar cfg *Configvar mu sync.Mutex func Get() *Config { if cfg == nil { // unsynchronised read…Вызовы
transfer(a, b)иtransfer(b, a)виснут навсегда, а остальной сервер продолжает работать. В чём баг и как сделать его невозможным?// wrong: two lock orders form a cycle; transfer(x, y) and transfer(y, x) hangfunc transfer(a, b *Account, n int64) { a.mu.Lock() defer a.mu.Unlock() b.mu.Lock() defer b.mu.Unlock()…Счётчики по шардам в
[]struct{ n atomic.Int64 }работают медленнее на операцию с ростом числа ядер, хотя две горутины никогда не трогают один элемент. Почему?// wrong: 8 bytes each, so eight of these share one 64-byte cache linetype packed struct{ n atomic.Int64 } // right: one line each. 128, not 64: internal/cpu.CacheLinePadSize is 128// on arm64, and sync.Pool pads its per-P struct to 128 for the same reason.type padded struct {…