持续创作,加速成长!这是我参与「掘金日新计划 · 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)
}