每天3分钟操作系统修炼秘籍(21):进程间通信(7):锁
锁
计算机领域中,锁机制使用的非常多。它主要是为了避免多个进程访问同一资源时,可能出现的数据不一致问题。
例如,cat命令输出一个比较大的文件内容,cat命令的特性是需要先将所有磁盘文件数据读取到内存后再输出,所以cat输出一个大文件可能需要花费一些时间。如果在cat在加载文件时,在另一个终端上向这个文件追加了一行数据,那么cat最终加载的数据会包含这行新追加的数据吗?
再更简单的一个示例是,两个用户同时用vim打开一个文件去修改数据,那么以谁修改的数据为准呢?所以,vim为了避免这种问题,每次打开一个文件的时候,都会在文件的同一个目录下创建一个隐藏的.swp临时文件(例如vim a.log
时会生成一个.a.log.swp
的文件),如果有其它用户用vim去打开同一个文件,会检查这个隐藏文件是否存在,如果存在说明有人正在编辑这个文件。
锁的内容非常多,这里简单介绍下它最基本也最实用的基础知识。
如果不使用锁,也就是允许多个进程同时更新、读取同一份数据,将可能出现以下问题:
1.脏读:读取到脏数据。例如用户A用vim将文件中的字母x修改为了y,用户B在vim保存之前通过cat读取,得到的这个字母将是x,这种现象称为脏读。但其实A已经将它修改为了y,只不过该修改只存在于A的vim缓存中,还没有保存到磁盘文件中。通常,将缓存中修改后但没有保存的数据称为脏数据。
2.更新丢失:某用户的数据更新操作被其它用户覆盖。例如,用户A和用户B同时修改同一文件中的最后一个字符a,A将a修改为x,B将a修改为y,那么A先保存的话,B的修改将覆盖A的修改,最终保存的结果是y,如果B先保存的话,A的修改将覆盖B的修改,最终结果得到字符x。所以,同时修改数据时,有一个进程的更新被覆盖了,也就是丢失了。
除这两个问题之外,在多进程同时更新、读取同一份数据时,还可能会出现其它现象,这里不再多做描述。下面介绍一下锁的机制。
当需要读数据时,将申请读锁,当需要修改数据时,将申请写锁。
申请锁的时候,需要先检查该资源上是否已经有锁,如果有锁,检查已存在的锁与待申请的锁是否可以兼容共存。
读锁和写锁的兼容性如下表:
S | X | |
---|---|---|
S | YES | NO |
X | NO | NO |
这个兼容性是很容易理解的:
- 1.当多个进程都只是读取同一份资源(即都申请S锁),因为没有修改数据,所以可以允许它们同时读取,所以S锁与S锁是可以共存的
- 2.如果有一个进程修改数据,它将申请X锁,这时显然不能让其它进程读取或写入数据,所以X锁与S锁、X锁和X锁都是互斥的
- 3.如果一个进程正在读取数据(即已申请S锁),其它进程想修改数据,也是不允许的,所以S锁和X锁是互斥的
这就不难理解,为什么写锁被称为排它锁或互斥锁的缘故:排除异己。
此外,使用锁需要考虑锁的粒度,即对多少资源量上锁。例如,对于一个文件来说,可以直接对整个文件上锁,也可以只对文件中想要访问的那部分数据上锁。如果直接对整个文件上锁,其它进程申请该文件的互斥锁将总是被阻塞,而如果只是锁定该文件中的前10K数据,那么其它进程如果申请的互斥锁从第20K开始的数据,就不会收到影响。
所以,锁的粒度越大,阻止其它进程的可能性就越大,多进程并发的能力就越差。锁的粒度越小,阻止其它进程的可能性就越小,并发的能力就越强。
但是,锁的粒度太小也不一定好,因为每个锁都是需要额外管理的,粒度越小,需要维护的锁数量越多。比如频繁创建锁和频繁释放锁的开销并不一定小,甚至在极端的时候比维护单个粗粒度的锁效率更低。
在shell命令行下,提供了一个flock命令,它可以通过某个文件来实现锁机制:运行某个命令时在某个文件上申请锁(读锁或写锁),另外一个命令运行时也申请该文件上的锁(读锁或写锁),如果锁可以共存,则第二个命令可以执行,否则默认阻塞。
下面是shell命令行下flock命令的简单用法,更详细内容可man flock自行探索。
# 以下代码在终端1上执行
# 在/tmp/a.lock上申请共享锁(-s),申请成功就运行sleep 10命令
# 因为此时/tmp/a.lock上还没有任何锁,所以申请成功
$ flock -s /tmp/a.lock sleep 10
# 以下代码在终端2上执行
# 在/tmp/a.lock上申请互斥锁(-x),申请成功就运行cat /etc/passwd命令
# 因为/tmp/a.lock上已经有共享锁,所以阻塞,直到10s后共享锁释放
$ flock -x /tmp/a.lock cat /etc/passwd
原文地址:https://www.cnblogs.com/f-ck-need-u/p/11881488.html
- 最新机器学习必备十大入门算法!都在这里了
- ASP.NET MVC的Razor引擎:IoC在View激活过程中的应用
- 深度学习笔记:深度学习在计算机视觉的应用
- 快速添加永久存储到到Minishift / CDK 3
- 张小龙发布2018微信全新计划(内附演讲全文)
- 使用JClouds在Java中获取和发布云服务器
- 利用ASP.NET SiteMap生成与Bootstrap"兼容"菜单
- 埃隆·马斯克强烈推荐的5本书,看完之后他开始改变世界
- 算法:AOE网(Activity On edge Network)与关键路径简介
- ASP.NET Core的配置(4):多样性的配置来源[中篇]
- ASP.NET MVC的Razor引擎:RazorViewEngine
- 算法:求解AOE网的关键路径
- 编程小技巧:多态原理
- ASP.NET Core的配置(4):多样性的配置来源[下篇]
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- phpstudy2018升级MySQL5.5为5.7教程(图文)
- laravel实现简单用户权限的示例代码
- tp5(thinkPHP5框架)时间查询操作实例分析
- PHP使Laravel为JSON REST API返回自定义错误的问题
- 详解PHP PDO简单教程
- Python实现ElGamal加密算法的示例代码
- PHP实现基于状态的责任链审批模式详解
- django rest framework使用django-filter用法
- 通过实例解析python创建进程常用方法
- thinkPHP5框架实现多数据库连接,跨数据连接查询操作示例
- OpenCV 之按位运算举例解析
- Python实时监控网站浏览记录实现过程详解
- php中的buffer缓冲区用法分析
- Python虚拟环境的创建和包下载过程分析
- Django视图、传参和forms验证操作