Golang map并发 读写锁
golang并发
一:只有写操作
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 在同一时间 只能有一个获取到互斥锁
二:读写都有
//不加锁的话 有可能是读的错误的值
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 获得读锁定 同时只能存在写锁定或读锁定(读和写互斥)。
一:Question
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.
1. Lets take a look at the code you provided.
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
}
}
2. What's missing here is how the map's themselves are initialized. And so far the maps are not being mutated. If the counter names are predetermined and cannot be added to later, you don't need the RWMutex. That code might look something like this:
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.)
Now, lets say you didn't want your counters to be predetermined. In that case you would need a mutex to synchronize access.
3. Lets try it with just a Mutex. It's simple because only one thread* can hold Lock at a time. If a second thread* tries to Lock before the first releases theirs with Unlock, it waits (or blocks)** until then.
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)
}
二:The code above will work just fine. But there are two problems.
1.If there is a panic between Lock() and Unlock() the mutex will be locked forever, even if you were to recover from the panic. This code probably won't panic, but in general it's better practice to assume it might.
Problem #1 is easy to solve. Use defer:
```
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.
2.An exclusive lock is taken while fetching the counter. Only one thread* can read from the counter at one time.
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.
3.Lets modify the earlier example with an RWMutex:
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
}
三:With the code above I've separated the logic out into getCounter and initCounter functions to:
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.
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:
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).
- When I say thread I mean go-routine. A thread in other languages is a mechanism for running one or more sets of code simultaneously. A thread is expensive to create and tear-down. A go-routine is built on top of threads, but re-uses them. When a go-routine sleeps the underlying thread can be used by another go-routine. When a go-routine wakes up, it might be on a different thread. Go handles all this behind the scenes. -- But for all intents and purposes you would treat a go-routine like a thread when it comes to memory access. However, you don't have to be as conservative when using go-routines as you do threads.
** 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.
原文链接
https://stackoverflow.com/questions/19148809/how-to-use-rwmutex-in-golang
Golang map并发 读写锁的更多相关文章
- Java 并发 —— 读写锁(ReadWriteLock)
读写锁(ReadWriteLock),顾名思义,就是在读写某文件时,对该文件上锁. 1. ReentrantReadWriteLock 三部曲: 加锁: 读写操作: 解锁:(为保证解锁操作一定执行,通 ...
- Java之——redis并发读写锁,使用Redisson实现分布式锁
原文:http://blog.csdn.net/l1028386804/article/details/73523810 1. 可重入锁(Reentrant Lock) Redisson的分布式可重入 ...
- Go语言协程并发---读写锁sync.RWMutex
package main import ( "fmt" "sync" "time" ) /* 读写锁 多路只读 一路只写 读写互斥 */ / ...
- golang map 读写锁与深度拷贝的坑
0X01 golang中,map(字典)无法并发读写 简单来说,新建万条线程对同一个map又读又写,会报错. 为此,最好加锁,其实性能影响并不明显. type taskCache struct{ sy ...
- 读/写锁的实现和应用(高并发状态下的map实现)
程序中涉及到对一些共享资源的读和写操作,且写操作没有读操作那么频繁.在没有写操作的时候,两个线程同时读一个资源没有任何问题,所以应该允许多个线程能在同时读取共享资源.但是如果有一个线程想去写这些共享资 ...
- Golang之并发资源竞争(读写锁)
前面的有篇文章在讲资源竞争的时候,提到了互斥锁.互斥锁的根本就是当一个goroutine访问的时候,其他goroutine都不能访问,这样肯定保证了资源的同步,避免了竞争,不过也降低了性能. 仔细剖析 ...
- 【GoLang】GoLang map 非线程安全 & 并发度写优化
Catena (时序存储引擎)中有一个函数的实现备受争议,它从 map 中根据指定的 name 获取一个 metricSource.每一次插入操作都会至少调用一次这个函数,现实场景中该函数调用更是频繁 ...
- 多线程并发编程之显示锁ReentrantLock和读写锁
在Java5.0之前,只有synchronized(内置锁)和volatile. Java5.0后引入了显示锁ReentrantLock. ReentrantLock概况 ReentrantLock是 ...
- Java并发编程原理与实战十八:读写锁
ReadWriteLock也是一个接口,提供了readLock和writeLock两种锁的操作机制,一个资源可以被多个线程同时读,或者被一个线程写,但是不能同时存在读和写线程. 基本规则: 读读不互斥 ...
随机推荐
- BZOJ 1047: [HAOI2007]理想的正方形 单调队列瞎搞
题意很简明吧? 枚举的矩形下边界和右端点即右下角,来确定矩形位置: 每一个纵列开一个单调队列,记录从 i-n+1 行到 i 行每列的最大值和最小值,矩形下边界向下推移的时候维护一下: 然后在记录的每一 ...
- Codeforces Round #562 (Div. 2) A.Circle Metro
链接:https://codeforces.com/contest/1169/problem/A 题意: The circle line of the Roflanpolis subway has n ...
- EOJ Problem #3261 分词 trie + dp + 小剪枝
http://acm.ecnu.edu.cn/problem/3261/ 分词 Time limit per test: 1.0 seconds Time limit all tests: 1.0 s ...
- Python网络编程中的服务器架构(负载均衡、单线程、多线程和同步、异步等)
这篇文章主要介绍服务器架构. 网络服务需要面对两个挑战. 第一个问题是核心挑战,要编写出能够正确处理请求并构造合适响应的代码. 第二个挑战是如何将网络代码部署到随系统自动启动的Windows服务或者是 ...
- Web可用性设计的247条指导方针
首页可用性设计 首页元素要清晰的关注用户的关键任务(避免“增加功能倾向(featuritis)”) 如果网站比较大,那么首页应包含搜索输入框 首页要十分清楚的提供产品(内容)分类 在首页或首页内一次点 ...
- 传入泛型类型(T.class)的方法
java中当我们需要T.class时会报错,这是我们只需定义一个Class<T>类作为参数传入即可,具体如下: public List<T> findStuByQuery(De ...
- nginx只允许域名访问网址,禁止ip访问
修改nginx配置 文件 在server段里插入如下正则: if ( $host != 'www.baidu.com') { return 403; } 说明:如果访问讨还不是www.baidu.co ...
- Bezier(贝塞尔曲线)
CDC::PolyBezierBOOL PolyBezier( const POINT* lpPoints, int nCount ); 和 曲线原理及多段曲线连接处如何光滑连接:第一段曲线要有4个点 ...
- [dp]uestc oj 邱老师看电影
定义状态dp[w][b]表示有w只白老鼠,b只黑老鼠时妹子赢的概率,分两种情况妹子抓到白老鼠概率为w/(w+b)和否则只有妹子抓黑老鼠和邱老师抓黑老鼠妹子才可能赢,再分两种情况:酱神抓白老鼠,状态 ...
- 【转】NSBundle的使用,注意mainBundle和Custom Bundle的区别
1.[NSBundle mainBundle],文件夹其实是Group,如左侧的树形文件管理器 Build之后,文件直接就复制到了根目录下,于是读取的方法,应该是这样: NSString *earth ...