站长博客
站长博客随手笔记
Toggle navigation
站长博客
Home
MacOS
Database
Linux
PHP
Git
Golang
About Me
Archives
Tags
Golang下文件锁的使用
2021-05-05 22:23:20
344
0
0
admin
前言 题目是golang下文件锁的使用,但本文的目的其实是通过golang下的文件锁的使用方法,来一窥文件锁背后的机制。 为什么需要文件锁 > 只有多线程/多进程这种并发场景下读写文件,才需要加锁 - 场景1-读写并发 读写并发场景下,如果不加锁,就会出现读到脏数据的情况。想象一下,读文件的进程,读到第500字节,有其它进程以覆盖写的方式向文件中写入1000字节,那读进程读到的后500字节就是脏数据。 - 场景2-写写并发 写写并发场景下,如果不加锁,假设A进程先写0-1000字节,B进程写0-900字节,以此类推,最后一个进程写0-100字节,那最终的文件内容就是每个进程前100个字节拼接起来的错乱的内容了。 文件锁的几个概念 ## 共享锁 共享锁,也叫读锁。某个进程首次获取共享锁后,会生成一个锁类型的变量L,类型标记为共享锁。其它进程获取读锁的时候,L中的计数器加1,表示又有一个进程获取到了共享锁。这个时候如果有进程来获取排它锁,会获取失败。 ## 排它锁 排它锁,也叫写锁。某个进程首次获取排他锁后,会生成一个锁类型的变量L,类型标记为排他锁。其它进程获取任何类型的锁的时候,都会获取失败。 ## 阻塞 阻塞的意思是说,新的进程发现当前的文件(数据)被加锁后,会一直处于等待状态,直到锁被释放,才会继续下一步的行为。 ## 非阻塞 非阻塞的意思是说,新的进程发现当前的文件(数据)被加锁后,立即返回异常。业务上需要根据具体的业务场景对该异常进行处理。 阻塞和非阻塞其实是进程遇到锁的时候的两种处理模式。 golang下如何使用文件锁 ## 基本使用 ``` package main import ( "log" "os" "syscall" ) func main() { f, err := os.Create("example.txt") if err != nil { log.Println("create file example.txt failed", err) } defer f.Close() // 非阻塞模式下,加共享锁 if err := syscall.Flock(int(f.Fd()), syscall.LOCK_SH|syscall.LOCK_NB); err != nil { log.Println("add share lock in no block failed", err) } // 这里进行业务逻辑 // TODO // 解锁 if err := syscall.Flock(int(f.Fd()), syscall.LOCK_UN); err != nil { log.Println("unlock share lock failed", err) } return } ``` 示例中 `LOCK_SH` 表示当前获取的是共享锁,如果是 `LOCK_EX`,则表示获取的是排他锁。而 `LOCK_NB` 表示当前获取锁的模式是非阻塞模式,如果需要阻塞模式,不加这个参数即可。`LOCK_UN` 则表示解锁,即释放锁。 golang 下这种文件锁的使用方式其实是Linux下的系统级调用,使用的是Linux的原生的文件锁的相关能力。 ## 使用flock的几个注意点 1、只要fd指向的是同一个文件指针,那么加锁解锁的行为都是继承和覆盖的(这个可以看最后的解释)。 2、flock这种方式加的是建议性锁,也就是说新的进程一上来不管三七二十一,不去通过flock获取锁,就对文件各种操作,也是可以正常生效的。 ## 说一说Linux下面的flock和fcntl 和flock一样,fcntl也是系统级调用,但是在具体的使用上却有很大不用,并且两种锁互不干扰,用flock加锁,fcntl无法感知,反之也一样。 ### 建议性锁和强制锁 flock加的是建议性锁,而fcntl加的是强制性锁。 建议性锁,本质是一种协议,约定读写操作前都去检查一下该文件是否有被其它进程加锁。如果不遵守该协议,一上来就对文件进行操作,不检查有没有锁,程序执行上是没有任何问题的,能执行成功。 强制性锁,才更像真正意义上的锁。只要加了锁,其它进程是无法执行非允许的操作的。 其实一些利用redis做的分布式锁,都是建议性锁。锁机的机制要生效,需要大家共同遵守这个约定才行。 全局锁和局部锁 对于一个文件,flock加锁的范围是整个文件内容,而fcntl能对文件的任意部分加锁。 ### 锁的持有者问题 flock认为,锁的持有者是文件表(可以理解为文件指针),所以对于fork和dup操作,他们都对应同一个文件指针,所有的操作都会作用到这个文件上。具体表现: > A进程加锁,A的子进程进程可以解锁,新的操作会覆盖之前的操作A进程加锁,A进程复制fd,仍然是可以通过新的fd操作文件锁,新的操作会覆盖之前的操作 fcntl 认为,锁的持有者是进程。加锁和解锁的行为都是跟着进程走,具体表现为: > A进程加锁,B进程得等A进程消亡或者解锁才能加锁
Prev:
Golang中的文件锁操作
Next:
通过UUID挂载磁盘
0
likes
344
Weibo
Wechat
Tencent Weibo
QQ Zone
RenRen
Table of content