虽然提供了channel来保证协程中的通信,在某些情况下,还是更适合使用锁来保证线程的安全.
go语言中的锁分两种:互斥锁mutex和读写锁rwmutex
1.互斥锁 mutex
互斥锁是线程安全中最常用的锁,基本原理就是对某个操作进行加锁,无论读写同一时间内只有一个协程可以对当前数据进行操作,只有对当前锁解锁后其他协程才可以继续进行操作,互斥锁不可以重入,对一个已经加过锁的数据再次加锁会引起恐慌,同样的对一个未加锁的操作进行解锁也会引发恐慌.
2.读写锁rwmutex
互斥锁可以保证线程安全,但是相应的,互斥锁由于强制性的串行操作,会导致性能有一定下降,为了提高加锁性能,go语言也提供了高性能锁读写锁.对数据的操作主要是读写互斥,因为对数据加锁的主要原因是需要对数据进行修改,若不加锁可能会导致读取到的数据不同,而对于读多,写少的场景来说,在数据不需要写的时候可以任意读,因为数据不会发生改变, 所以此时不会有线程安全问题,所以读写锁的读锁可以重入,在已经有读锁的情况下可以重复加任意多的读锁,读锁和写锁互斥,在读锁未全部解锁的情况下,写锁操作会阻塞,直到所有读锁都解锁才会加写锁,在写锁定的情况下,其他协程的读和写都是被禁止的, 写锁无法重入,直到写锁解锁,其他读写操作才可以继续进行
源码解析:
func (rw *RWMutex) RLock() {
if race.Enabled {
_ = rw.w.state
race.Disable()
}
if atomic.AddInt32(&rw.readerCount, 1) < 0 {
// A writer is pending, wait for it.
runtime_Semacquire(&rw.readerSem)
}
if race.Enabled {
race.Enable()
race.Acquire(unsafe.Pointer(&rw.readerSem))
}
}
以上代码为读锁的源码,可以看到其中第6行,若当前有写锁定,则等待,即在写锁定时无法进行读加锁,读加锁协程会阻塞
func (rw *RWMutex) Lock() {
if race.Enabled {
_ = rw.w.state
race.Disable()
}
// 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_Semacquire(&rw.writerSem)
}
if race.Enabled {
race.Enable()
race.Acquire(unsafe.Pointer(&rw.readerSem))
race.Acquire(unsafe.Pointer(&rw.writerSem))
}
}
以上代码为写锁的源码,写锁首先是解决与其他写锁的冲突,在其他写锁定解锁的情况下才可以继续执行写锁定,同样的,在有读锁定的情况下也无法进行写锁定,在第11行可以看到,需要等待所有读锁都解锁的情况下才可以继续写锁定,负责写锁定协程会阻塞.
由代码可以看出,写锁定和读锁定互斥,读锁定和读锁定不互斥,写锁定和写锁定互斥.