Upgrade to Pro — share decks privately, control downloads, hide ads and more …

[Short] The monsters inside the `sync.Locker`

[Short] The monsters inside the `sync.Locker`

Video: https://www.dotconferences.com/2019/03/roberto-clapis-the-monsters-inside-the-sync-locker

We all know what a mutex is and what it can be used for, and we also know that mutex contention can degrade performance, but what is a Mutex, really? This talk will explain what happens in the runtime when a goroutine attempts to acquire a lock (or to send a value on a channel), and how can this impact execution time. An idiomatic solution to reduce contention in multi-producer multi-consumer cases will be introduced

51a79d752c98d83196a9bdb4db9b1c01?s=128

Roberto (Rob) Clapis

March 25, 2019
Tweet

Transcript

  1. The monsters inside the sync.Locker ... and how to tame

    them.
  2. What are Channels? Channels are structs protected by a runtime

    mutex. The runtime mutex defined in runtime/runtime2.go has very similar logic to sync.Mutex // runtime/chan.go type hchan struct { // Lots of unexported // fields lock mutex }
  3. What are Mutexes? Mutexes are 64 bits structs with two

    ints. In case of low contention, only state is used. // sync/mutex.go type Mutex struct { state int32 sema uint32 }
  4. Mutexes do two things Create critical sections. Make sure all

    changes are atomic and propagated. mu.Lock() // Only one goroutine here a = 2 mu.Unlock() mu.Lock() // 2 for all goroutines print(a) mu.Unlock()
  5. A trip down memory lane a++ // Time 0 //

    Time 1 print(a) var a int Change of `a` might have not propagated Compiler might reorder operations
  6. A trip down memory lane a++ atomic.StoreInt32(&b,1) // Time 0

    // Time 1 _ = atomic.LoadInt32(&b) print(a) var a int var b int32 Write barrier Read barrier
  7. Mutexes fast path Mutexes want to make sure they are

    synchronizing bot reads and writes. They use atomic.CompareAndSwapInt32 and atomic.AddInt32 to emit read and write barriers mutexLocked = 1 func (m *Mutex) Lock() { if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) { return func (m *Mutex) Unlock() { new := atomic.AddInt32(&m.state, -mutexLocked)
  8. Mutexes handle starvation Mutexes have several lock-free optimization to make

    sure goroutines don't starve. Failure to acquire spins for a while, then changes mutex state to starving. All this is done with atomics and runtime interaction. See change 34310.
  9. Why aren't Channels lock-free? Because it is hard to do.

    See issue 8899.
  10. How do I reduce contention? Stay away from a central

    state or a single channel. Shorten critical sections. Don't use atomics, the standard library does it for you.
  11. Thanks Have questions? Ask them! Twitter: @empijei Roberto Clapis