var ( count int l = sync.Mutex{} m = make(map[int]int) ) //全局變量併發寫 致使計數錯誤 func vari() { for i := 0; i < 10000; i++ { go func(i int) { //defer l.Unlock() //l.Lock() count++ }(i) } fmt.Println(count) } //map 併發寫 不加鎖 fatal error: concurrent map writes func mp() { for i := 0; i < 1000; i++ { go func() { defer l.Unlock() l.Lock() m[0] = 0 }() } } func main() { //vari() mp() time.Sleep(3 * time.Second) }
sync.Mutex 互斥鎖 多個groutine 在同一時間 只能有一個獲取到互斥鎖golang
//不加鎖的話 有多是讀的錯誤的值 func read() { defer rwL.RUnlock() rwL.RLock() fmt.Println("read ", m[0]) } //若是不加鎖 會報錯 fatal error: concurrent map writes func write() { defer rwL.Unlock() rwL.Lock() m[0] = m[0] + 1 } func rwLock() { for i := 0; i < 10000; i++ { go read() } for i := 0; i < 10000; i++ { go write() } } func main() { //vari() //mp() rwLock() time.Sleep(3 * time.Second) }
同時只能有一個 goroutine 可以得到寫鎖定 同時能夠有任意多個 gorouinte 得到讀鎖定 同時只能存在寫鎖定或讀鎖定(讀和寫互斥)。併發
When more than one thread* needs to mutate the same value, a locking mechanism is needed to synchronizes access. Without it two or more threads* could be writing to the same value at the same time, resulting in corrupt memory that typically results in a crash. The atomic package provides a fast and easy way to synchronize access to primitive values. For a counter it is the fastest synchronization method. It has methods with well defined use cases, such as incrementing, decrementing, swapping, etc. The sync package provides a way to synchronize access to more complicated values, such as maps, slices, arrays, or groups of values. You use this for use cases that are not defined in atomic. In either case locking is only required when writing. Multiple threads* can safely read the same value without a locking mechanism.
type Stat struct { counters map[string]*int64 countersLock sync.RWMutex averages map[string]*int64 averagesLock sync.RWMutex } func (s *Stat) Count(name string) { s.countersLock.RLock() counter := s.counters[name] s.countersLock.RUnlock() if counter != nil { atomic.AddInt64(counter, int64(1)) return } }
type Stat struct { counters map[string]*int64 } func InitStat(names... string) Stat { counters := make(map[string]*int64) for _, name := range names { counter := int64(0) counters[name] = &counter } return Stat{counters} } func (s *Stat) Count(name string) int64 { counter := s.counters[name] if counter == nil { return -1 // (int64, error) instead? } return atomic.AddInt64(counter, 1) }
(Note: I removed averages because it wasn't being used in the original example.)app
Now, lets say you didn't want your counters to be predetermined. In that case you would need a mutex to synchronize access.ide
type Stat struct { counters map[string]*int64 mutex sync.Mutex } func InitStat() Stat { return Stat{counters: make(map[string]*int64)} } func (s *Stat) Count(name string) int64 { s.mutex.Lock() counter := s.counters[name] if counter == nil { value := int64(0) counter = &value s.counters[name] = counter } s.mutex.Unlock() return atomic.AddInt64(counter, 1) }
Problem #1 is easy to solve. Use defer:oop
``` func (s *Stat) Count(name string) int64 { s.mutex.Lock() defer s.mutex.Unlock() counter := s.counters[name] if counter == nil { value := int64(0) counter = &value s.counters[name] = counter } return atomic.AddInt64(counter, 1) } ```
This ensures that Unlock() is always called. And if for some reason you have more then one return, you only need to specify Unlock() once at the head of the function.fetch
Problem #2 can be solved with RWMutex. How does it work exactly, and why is it useful? RWMutex is an extension of Mutex and adds two methods: RLock and RUnlock. There are a few points that are important to note about RWMutex: RLock is a shared read lock. When a lock is taken with it, other threads* can also take their own lock with RLock. This means multiple threads* can read at the same time. It's semi-exclusive. If the mutex is read locked, a call to Lock is blocked**. If one or more readers hold a lock, you cannot write. If the mutex is write locked (with Lock), RLock will block**. A good way to think about it is RWMutex is a Mutex with a reader counter. RLock increments the counter while RUnlock decrements it. A call to Lock will block as long as that counter is > 0. You may be thinking: If my application is read heavy, would that mean a writer could be blocked indefinitely? No. There is one more useful property of RWMutex: If the reader counter is > 0 and Lock is called, future calls to RLock will also block until the existing readers have released their locks, the writer has obtained his lock and later releases it. Think of it as the light above a register at the grocery store that says a cashier is open or not. The people in line get to stay there and they will be helped, but new people cannot get in line. As soon as the last remaining customer is helped the cashier goes on break, and that register either remains closed until they come back or they are replaced with a different cashier.
type Stat struct { counters map[string]*int64 mutex sync.RWMutex } func InitStat() Stat { return Stat{counters: make(map[string]*int64)} } func (s *Stat) Count(name string) int64 { var counter *int64 if counter = getCounter(name); counter == nil { counter = initCounter(name); } return atomic.AddInt64(counter, 1) } func (s *Stat) getCounter(name string) *int64 { s.mutex.RLock() defer s.mutex.RUnlock() return s.counters[name] } func (s *Stat) initCounter(name string) *int64 { s.mutex.Lock() defer s.mutex.Unlock() counter := s.counters[name] if counter == nil { value := int64(0) counter = &value s.counters[name] = counter } return counter }
Keep the code simple to understand. It would be difficult to RLock() and Lock() in the same function.
Release the locks as early as possible while using defer.
The code above, unlike the Mutex example, allows you to increment different counters simultaneously.ui
Another thing I wanted to point out is with all the examples above, the map map[string]*int64 contains pointers to the counters, not the counters themselves. If you were to store the counters in the map map[string]int64 you would need to use Mutex without atomic. That code would look something like this:this
type Stat struct { counters map[string]int64 mutex sync.Mutex } func InitStat() Stat { return Stat{counters: make(map[string]int64)} } func (s *Stat) Count(name string) int64 { s.mutex.Lock() defer s.mutex.Unlock() s.counters[name]++ return s.counters[name] }
You may want to do this to reduce garbage collection - but that would only matter if you had thousands of counters - and even then the counters themselves don't take up a whole lot of space (compared to something like a byte buffer).atom
** When a go-routine is blocked by Lock, RLock, a channel, or Sleep, the underlying thread might be re-used. No cpu is used by that go-routine - think of it as waiting in line. Like other languages an infinite loop like for {} would block while keeping the cpu and go-routine busy - think of that as running around in a circle - you'll get dizzy, throw up, and the people around you won't be very happy.spa
https://stackoverflow.com/questions/19148809/how-to-use-rwmutex-in-golang