Skip to content

Commit

Permalink
feat(syscall.Flock): 新增 golang 文件锁操作
Browse files Browse the repository at this point in the history
  • Loading branch information
mickey0524 authored and yangwenmai committed Nov 11, 2018
1 parent e028f4d commit 9a74c56
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 9 deletions.
76 changes: 76 additions & 0 deletions articles/2018-11-11-golang-file-lock.md
@@ -0,0 +1,76 @@
# golang 的文件锁操作

这篇文章给大家介绍一下 golang 的文件锁。我们在使用 golang 开发程序的时候,经常会出现多个 goroutine 操作同一个文件(或目录)的时候,如果不加锁,很容易导致文件中的数据混乱,于是,Flock应运而生。

Flock是对于整个文件的建议性锁(不强求 goroutine 遵守),如果一个 goroutine 在文件上获取了锁,那么其他 goroutine 是可以知道的。默认情况下,当一个 goroutine 将文件锁住,另外一个 goroutine 可以直接操作被锁住的文件,原因在于 Flock 只是用于检测文件是否被加锁,针对文件已经被加锁,另一个 goroutine 写入数据的情况,内核不会阻止这个 goroutine 的写入操作,也就是建议性锁的内核处理策略。

## 函数

```go
import "syscall"

func Flock(fd int, how int) (err error)
```

Flock 位于 syscall 包中,fd 参数指代文件描述符,how 参数指代锁的操作类型。

how 主要的参数类型:

* LOCK_SH,共享锁,多个进程可以使用同一把锁,常被用作读共享锁;
* LOCK_EX,排他锁,同时只允许一个进程使用,常被用作写锁;
* LOCK_NB,遇到锁的表现,当采用排他锁的时候,默认 goroutine 会被阻塞等待锁被释放,采用 LOCK_NB 参数,可以让 goroutine 返回 Error;
* LOCK_UN,释放锁;

## 示例

下面的例子来自于 NSQ,位于 `nsq/internal/dirlock`,用于实现对目录的加锁

```go
// +build !windows

package dirlock

import (
"fmt"
"os"
"syscall"
)

// 定义一个 DirLock 的struct
type DirLock struct {
dir string // 目录路径,例如 /home/XXX/go/src
f *os.File // 文件描述符
}

// 新建一个 DirLock
func New(dir string) *DirLock {
return &DirLock{
dir: dir,
}
}

// 加锁操作
func (l *DirLock) Lock() error {
f, err := os.Open(l.dir) // 获取文件描述符
if err != nil {
return err
}
l.f = f
err = syscall.Flock(int(f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB) // 加上排他锁,当遇到文件加锁的情况直接返回 Error
if err != nil {
return fmt.Errorf("cannot flock directory %s - %s", l.dir, err)
}
return nil
}

// 解锁操作
func (l *DirLock) Unlock() error {
defer l.f.Close() // close 掉文件描述符
return syscall.Flock(int(l.f.Fd()), syscall.LOCK_UN) // 释放 Flock 文件锁
}
```

## 总结

1. Flock 是建议性的锁,使用的时候需要指定 `how` 参数,否则容易出现多个 goroutine 共用文件的问题
2. `how` 参数指定 `LOCK_NB` 之后,goroutine 遇到已加锁的 Flock,不会阻塞,而是直接返回错误
10 changes: 5 additions & 5 deletions articles/sync/sync_mutex_source_code_analysis.md
Expand Up @@ -4,7 +4,7 @@

## 结构体

```
```go
type Mutex struct {
state int32 // 指代mutex锁当前的状态
sema uint32 // 信号量,用于唤醒goroutine
Expand All @@ -13,7 +13,7 @@ type Mutex struct {

Mutex 中的 state 用于指代锁当前的状态,如下所示

```
```go
1111 1111 ...... 1111 1111
\_________29__________/|||
存储等待 goroutine 数量 ||表示当前 mutex 是否加锁
Expand All @@ -24,7 +24,7 @@ Mutex 中的 state 用于指代锁当前的状态,如下所示

## 几个常量

```
```go
const (
mutexLocked = 1 << iota
mutexWoken
Expand Down Expand Up @@ -62,7 +62,7 @@ Lock 方法申请对 mutex 加锁,Lock 执行的时候,分三种情况
2. **有冲突 开始自旋**,并等待锁释放,如果其他 goroutine 在这段时间内释放了该锁,直接获得该锁;如果没有释放,进入3
3. **有冲突,且已经过了自旋阶段** 通过调用 semacquire 函数来让当前 goroutine 进入等待状态

```
```go
func (m *Mutex) Lock() {
// 查看 state 是否为0,如果是则表示可以加锁,将其状态转换为1,当前 goroutine 加锁成功,函数返回
if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
Expand Down Expand Up @@ -153,7 +153,7 @@ func (m *Mutex) Lock() {

Unlock方法释放所申请的锁

```
```go
func (m *Mutex) Unlock() {
// mutex 的 state 减去1,加锁状态 -> 未加锁
new := atomic.AddInt32(&m.state, -mutexLocked)
Expand Down
8 changes: 4 additions & 4 deletions articles/sync/sync_rwmutex_source_code_analysis.md
Expand Up @@ -14,7 +14,7 @@ RWMutex 是抢占式的读写锁,写锁之后来的读锁是加不上的

## 结构体

```
```go
type RWMutex struct {
w Mutex // 互斥锁
writerSem uint32 // 写锁信号量
Expand Down Expand Up @@ -55,7 +55,7 @@ func (rw *RWMutex) Lock() {

提供写锁释放操作

```
```go
func (rw *RWMutex) Unlock() {
// 加上 Lock 的时候减去的 rwmutexMaxReaders
r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders)
Expand Down Expand Up @@ -94,7 +94,7 @@ func (rw *RWMutex) RLock() {

RUnLock 方法对读锁进行解锁

```
```go
func (rw *RWMutex) RUnlock() {
// 写锁等待状态,检查当前是否可以进行获取
if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 {
Expand Down Expand Up @@ -127,7 +127,7 @@ type Locker interface {

而方法 `RLocker` 就是将 `RWMutex` 转换为 `Locker`

```
```go
func (rw *RWMutex) RLocker() Locker {
return (*rlocker)(rw)
}
Expand Down

0 comments on commit 9a74c56

Please sign in to comment.