Go并发编程-RWMutex

Go并发编程-RWMutex

简单用

​ 读写互斥锁RWMutex与互斥锁Mutex使用方法非常类似。在使用互斥锁Mutex时,并行会变成串行化,降低了效率,分析的时候就会发现读写如果能够分离开能极大的提高效率。也就是将串行读改为并行读,串行写保持不变。这是一个经典的readers-wriders问题。解决readers-writers有三种方案,read-prefer,write-prefer,不指定优先级。

  1. Read-prefer:读优先,提高很好的并发性,但是会导致写饥饿问题。

  2. Write-prefer:写优先,当有读请求时,新来的read请求会阻塞。解决了写饥饿问题

  3. 不区分优先级:没有优先,没有饥饿问题

    RWMutex采用了write-prefer方案,一个正在阻塞的Lock调用会排除新的reader请求到锁。

type Counter struct {
	mu    sync.RWMutex
	count int
}

func (c *Counter) Count() int {
	c.mu.RLock()
	defer c.mu.RUnlock()

	return c.count
}

func (c *Counter) Incr() {
	c.mu.Lock()
	c.count++
	c.mu.Unlock()
}
  1. 使用Mutex是不需要初始化的,在使用时会自动去初始化。所以在使用时不需要自己去创建一个对象
  2. Mutex变量声明时最好声明在其临界变量的上面,然后使用空格把字段分隔开来。逻辑会跟清晰,便于维护
  3. Lock和Unlock方法成对出现,使用defer去解锁。逻辑会跟清晰,便于维护,防止出现漏掉的情况

看实现

type RWMutex struct {
	w           Mutex  // 互斥锁
	writerSem   uint32 // writer信号量
	readerSem   uint32 // reader信号量
	readerCount int32  // reader的数量
	readerWait  int32  // writer等待完成的reader的数量
}
const rwmutexMaxReaders = 1 << 30
func (rw *RWMutex) RLock() {
	if atomic.AddInt32(&rw.readerCount, 1) < 0 {
		runtime_SemacquireMutex(&rw.readerSem, false, 0)
	}
}
func (rw *RWMutex) RUnlock() {
	if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 {
		rw.rUnlockSlow(r)
	}
}
func (rw *RWMutex) Lock() {
	// First, resolve competition with other writers.
	rw.w.Lock()
	// Announce to readers there is a pending writer.
	r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders
	// Wait for active readers.
	if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 {
		runtime_SemacquireMutex(&rw.writerSem, false, 0)
	}
}
func (rw *RWMutex) Unlock() {
	// Announce to readers there is no active writer.
	r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders)
	if r >= rwmutexMaxReaders {
		race.Enable()
		throw("sync: Unlock of unlocked RWMutex")
	}
	// Unblock blocked readers, if any.
	for i := 0; i < int(r); i++ {
		runtime_Semrelease(&rw.readerSem, false, 0)
	}
	// Allow other writers to proceed.
	rw.w.Unlock()
}

​ rwmutexMaxReaders 定义了最大的reader的数量

​ readerCount:不仅表示reader的数量,同时也标记着writer竞争锁或者持有锁的状态

别踩坑

  1. 不是成对出现

    要保证读写锁必须成对出现,不遗漏不多余。否则都会panic

  2. 不可复制

    与Mutex不能复制的原因相同。sync的包中的同步原语是不能复制的,因为他们是有状态的对象,复制一个已经加锁的RWMutex给一个新的变量明显不符合预期。虽然这种情况很简单,但是还是会很容易出错,因为go的函数调用会自动复制。所以有关锁的参数传递要使用指针的方式。

  3. 重入锁

    与Mutex相同,RWMutex也是不可重入锁。由于重入锁导致的死锁情况有很多。

    1. write锁重入调用Lock时发生死锁
    func foo(rwm *sync.RWMutex) {
    	fmt.Println("in foo")
    	rwm.Lock()
    	bar(rwm)
    	rwm.Unlock()
    }
    func bar(rwm *sync.RWMutex) {
    	rwm.Lock()
    	fmt.Println("in bar")
    	rwm.Unlock()
    }
    
    func main() {
    	var rwm sync.RWMutex
    	foo(&rwm)
    }
    
    1. 读写依赖导致死锁
    func foo(rwm *sync.RWMutex) {
    	fmt.Println("in foo")
    	rwm.RLock()
    	bar(rwm)
    	rwm.RUnlock()
    }
    func bar(rwm *sync.RWMutex) {
    	rwm.Lock()
    	fmt.Println("in bar")
    	rwm.Unlock()
    }
    
    func main() {
    	var rwm sync.RWMutex
    	foo(&rwm)
    }
    
    1. writer依赖reader. reader依赖新来的reader. 新来的reader依赖writer形成环状
    func main() {
    	var m sync.RWMutex
    	go func() {
    		time.Sleep(200 * time.Millisecond)
    		m.Lock()
    		fmt.Println("Lock")
    		time.Sleep(100 * time.Millisecond)
    		m.Unlock()
    		fmt.Println("Unlock")
    	}()
    
    	factorial(&m, 10)
    }
    
    func factorial(m *sync.RWMutex, n int) int {
    	if n < 1 {
    		return 0
    	}
    	fmt.Println("RLock")
    	m.RLock()
    	defer func() {
    		fmt.Println("RUnlock")
    		m.RUnlock()
    	}()
    
    	time.Sleep(100 * time.Millisecond)
    	return factorial(m, n-1) * n
    }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值