Go 中的互斥锁和读写锁

304 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第6天,点击查看活动详情

互斥锁

互斥锁是并发程序对共享资源进行访问控制的主要方式。Go中 sync 包提供了 Mutex 结构体来控制互斥。sync.Mutex 类型只有2个方法

  • Lock:用于锁定当前的互斥量
  • Unlock :用来对当前互斥量进行解锁

基本用法

var mutex *sync.Mutex
mutex.Lock()
defer mutex.UnLock()

defer 语句可以让我们降低发生忘记及时截开已被锁住的锁。如果未解锁就会导致流程发生异常,导致死锁的情况。defer 语句保证了函数被执行结束之前解锁互斥锁。只需在互斥锁的锁定操作和解锁操作成对出现就可以。

读写锁

读写锁就是对读写操作的互斥锁。可以分别针对读操作和写操作进行锁定和解锁操作。但其访问控制的操作和互斥锁不同:

读写锁允许任意读操作同时进行,但同一时刻,只允许一个写操作在进行,并且写操作在进行时,读操作也是不被允许的。所以说,读写锁控制下的多个写操作之间是互斥的,并且写操作和读操作之间也都是互斥的。但多个读操作之间不存在互斥关系。

var mutex sync.RWMutex 
mutex.Lock
mutex.UnLock

mutex.RLock
mutex.RUnlock

锁的实践

场景:一个文件,在同一时刻,可能有多个协程分别对该文件进行写操作和读操作。每次写数据时,该数据都是独立的数据块存在,写之间不互相干扰。读操作都是从这个文件读取一个独立完整的数据块。

首先,使用 os.File 类型,提供操作文件系统的支持。同时需要对读写操作进行访问控制,因为该类型没有对并发操作的安全性保证。需要使用读写锁比互斥锁更好。

type DataType []byte
// 数据文件的接口类型
type DataFile interface {
    Read()(rsn int64,d DataType, err Error)
    Write(d DataType)(wsn int64,err Error)
    Rsn() int64 // 获取最后读取的数据块序列号
    Wsn() int64 // 获取最后写入的数据块序列号
    DataLen() uint32 // 数据长度
}

类型 DataType 被声明未一个[]byte的别名类型,创建 DataFile 类型,定义该类型可进行读写操作,能够获取读写最后的序列号,和整个数据块的长度。

需要定义一个类型实现 DataFile类型的方法,读取文件时需要控制读写偏移量woffset 写偏移量和 roffset 读偏移量,读和写操作都需要使用互斥锁,还需要有一个用于文件读写锁RWMutex

type myDataFile struct {
    f  *os.File
    fmutex sync.RWMutex
    woffset int64
    roffset int64
    wmutex sync.Mutex
    rmutex sync.Mutex
    dataLen uint32
}

实现 Read 方法,先要获取并更新读偏移量,然后再根据偏移量从文件中读取数据块。当进行多个读操作时不能读取同一个数据块,并且需要按顺序读取数据块。读取数据块时也需要用读写锁fmutex 进行保护。

var rmutex *sync.Mutes
func (df *myDataFile)Read()(rsn int64,d DataType, err Error){
  var offset int64 // 读取并更新偏移量  
  df.rmutex.Lock()
   offset =df.roffset
   df.roffset+=int64(df.dataLen)
    df.rmutex.UnLock()
  // 读取数据块
    rsn = offset /int64(df.dataLen)
    df.fmutex.Rlock()
    defer df.fmutex.RUnlock()
    bytes := make([]byte,df.dataLen)
    _,err := df.f.ReadAt(bytes,offset)
}