Linux下各种编程锁的比较 - 文件锁

1 概述

  含有多个线程/进程的系统在运行过程中,往往会出现多个线程/进程在同一时间去读取或修改同一内存地址的数据,为了保证数据的完整性,最常用的方式是使用锁机制。编程锁有很多种,常见的有文件锁、互斥锁、读写锁、信号量、原子锁等等,虽然它们都起着保证数据完整性的作用,但是它们的作用范围、适用场景、时间开销各不相同。本篇将通过实验对各锁的适用场景、时间开销等方面做一个简单的比较和阐述,给今后工作过程中为使用哪种锁更合适而纠结时,提供一个参考依据。

2 文件锁

  在Linux系统中,文件锁的接口有3种方式:flock()、fcntl()、lockf()。以下将对这两种接口进行分别的说明。

2.1 flock()函数

表1 flock()函数说明

说明项 描述
头文件 #include <sys/file.h>
函数原型 int flock(int fd, int operation)
函数说明 1、此函数是从BSD中衍生出来的,但目前在大多数UNIX系统上都能找到。

2、此函数只能处理单个主机上的文件,不能处理NFS上的文件。

3、此函数按照operation所指定的方式对fd锁指向的文件进行加锁和解锁等相关操作。

4、此函数只能操作整个文件,不能操作此文件的部分区域。

5、此函数加的是建议性锁,当进程结束时,系统会自动释放该锁。

参数说明 参数operation有下列四种情况:

  LOCK_SH:建立共享锁定。多个进程可同时对同一个文件作共享锁定。

  LOCK_EX:建立互斥锁定。一个文件同时只有一个互斥锁定。

  LOCK_UN:解除文件锁定状态。

  LOCK_NB:无法建立锁定时,此操作不会被阻塞,将立即返回操作结果。

    1、此选项与LOCK_SH或LOCK_EX 做OR(|)组合。

      如:fblock(fd, LOCK_NB | LOCK_EX) 或 fblock(fd, LOCK_NB | LOCK_SH)

    2、单一文件无法同时建立共享锁定和互斥锁定,而当使用dup()或fork()时文件描述词不会继承此种锁定。

    3、返回值  返回0表示成功,若有错误则返回-1,错误代码存于errno。

  其使用的参考代码如下:

图1 flock()参考代码

2.2 fcntl()函数

表2 fcntl()函数说明

说明项 描述
头文件 #include <fcntl.h>
函数原型 int fcntl(int fd, int cmd, ... /* arg */)
函数说明 在此只说明与锁相关的功能:

1、此函数符合POSIX标准的文件锁实现,所以是可移植的。

2、此函数不仅能处理本地文件,也能处理NFS上的文件。

  fcntl()请求会被递交给叫rpc.lockd的守护进程,然后由它负责和主机端lockd的对话。

3、此函数不仅能操作整个文件,也能操作此文件的部分区域。

4、此函数不仅可以加建议性锁,但也支持加强制性锁。

参数说明 1)当CMD为F_SETLKW时,阻塞加读锁或加写锁或解锁。

2)当CMD为F_SETLK时,非阻塞加读锁或加写锁或解锁。

3)当CMD为以上2值之一时,该函数第三个参数为:

  struct flock

  {

  short l_type;        /* 操作类型:F_RDLCK(读锁)、F_WRLCK(写锁)、F_UNLCK(解锁) */

  off_t l_start;         /* 起始偏移 */

  short l_whence; /* 相对位置:SEEK_SET(文件开始处)、CURR_SET(当前位置处)、END_SET(文件结束处) */

  off_t l_len;           /* 操作长度:0表示从start至文件结束处 */

  pid_t l_pid;          /* 进程ID:不必设置。当CMD为F_GETLK时,将返回该值 */

  }

  使用fcntl()实现的锁操作接口如下:

图2 fcntl()非阻塞锁操作

图3
fcntl()阻塞锁操作

以上2个函数虽然支持读锁、写锁、解锁等操作,但是参数过多,不太好使用。为了简化接口的使用,可以使用如下宏替代:(因考虑到有线程的读写锁,而fcntl()只支持进程级的锁,因此其命名以proc_开头)

图4 锁的宏定义

2.3 lockf()函数

表2 lockf()函数说明

说明项 描述
头文件 #include <unistd.h>
函数原型 int lockf(int fd, int cmd, off_t len)
函数说明 1、此函数符合POSIX标准的文件锁实现,所以是可移植的。

2、在Linux操作系统中,此函数是基于fcntl()的封装,在其他操作系统中,很多也是这么实现的。但是

在POSIX.1-2001中未指明lockf()和fcntl()之间的关系。

3、一个可移植的程序,应该避免将lockf()和fcntl()混搭使用。

参数说明 1、参数cmd:命令类型

 1)当CMD为F_LOCK时,给文件的指定段加互斥锁。(阻塞)

1. 如果加锁的段的全部或部分已经被其他进程(而不是线程)加锁,此进程将会一直阻塞直到该段被释放;

2. 如果加锁的端的与本进程之前加锁端有重叠,加锁段将进行合并;

3. 当文件描述符被关闭或或进程退出后,锁将会自动释放;

4. 孩子进程将不会继承父进程的锁;

 2)当CMD为F_TLOCK时,给文件的指定段加互斥锁。(非阻塞)

该选项与F_LOCK一致,但是如果加锁段的全部或部分已经被其他进程占用时,将不会阻塞,而是立即返回错误。

 3)当CMD为F_ULOCK时,给文件的指定段解锁。

1. 该选项可能导致加锁段被分割为2段加锁段。

2. 比如:文件的第2~9 字节被加锁,可以通过此函数给第4~7字节先解锁,这样就造成第2~3和第8~9两端

还处于加锁状态。

 4)当CMD为F_TEST时,测试文件的指定段是否已加锁。(非阻塞)

1. 当测试段没有被其他进程加锁,或者被本加锁时,返回0;

2. 当测试段被其他进程加锁,将errno设置为EAGAIN(有些系统设置为EACCESS),同时返回-1.

2、参数len: 操作长度

 1)当LEN的值为正数时,操作的范围是:pos ~ pos+len-1(注:pos为文件当前偏移量)

 2)当LEN的值为负数时,操作的范围是:pos-len ~ pos-1

 3)当LEN的值为零时,操作的范围是:pos ~ 文件末尾(注:文件末尾指的是当前和未来的文件末尾)

  使用lockf()实现的锁操作接口如下:

图5 lockf()示例代码

  以上3个函数lockf()、fcntl()、flock()实现的互斥锁、多写锁的作用域是进程级的,这种锁不能用来保证多线程中数据的安全性和一致性。

  假设:有进程3个进程A、B和C同时抢一把进程级的互斥锁L(进程A有多个线程A1、A2、...)。如果进程A抢到了锁L,那么在进程A释放锁之前,进程B和C是无法再加锁成功的。如果A进程抢锁成功是通过线程A1,那么当线程A2、A3、...再执行抢锁L的操作时,依然会返回加锁成功;如果是由线程An抢到的锁L,可以由线程Ax来释放锁L。

  使用以上3个函数实现的读写锁的效果和互斥锁的效果,在多线程中是一致的,在此不再赘述。

  总之:以上3个函数lockf()、fcntl()、flock()实现的互斥锁、多写锁的作用域是进程级的,这种锁不能用来保证多线程中数据的安全性和一致性。

时间: 2024-11-06 23:57:03

Linux下各种编程锁的比较 - 文件锁的相关文章

linux下多线程编程

最近研究mysql源码,各种锁,各种互斥,好在我去年认真学了<unix环境高级编程>, 虽然已经忘得差不多了,但是学过始终是学过,拿起来也快.写这篇文章的目的就是总结linux 下多线程编程,作为日后的参考资料. 本文将介绍linux系统下多线程编程中,线程同步的各种方法.包括: 互斥量(mutex) 读写锁 条件变量 信号量 文件互斥 在介绍不同的线程同步的方法之前,先简单的介绍一下进程和线程的概念, 它们的优缺点,线程相关的API,读者——写者问题和哲学家就餐问题. 基础知识 1. 进程和

Linux下socket编程,附带tcp例子

1.网络中进程之间如何通信? 本地的进程间通信(IPC)有很多种方式,但可以总结为下面4类: 消息传递(管道.FIFO.消息队列) 同步(互斥量.条件变量.读写锁.文件和写记录锁.信号量) 共享内存(匿名的和具名的) 远程过程调用(Solaris门和Sun RPC) 但这些都不是本文的主题!我们要讨论的是网络中进程之间如何通信?首要解决的问题是如何唯一标识一个进程,否则通信无从谈起!在本地可以通过进程PID来唯一标识一个进程,但是在网络中这是行不通的.其实TCP/IP协议族已经帮我们解决了这个问

根据《linux兵书》目录 在kali上操作 第8章 Linux下的编程

第8章  浑水摸鱼:Linux下的编程 175 8.1  Linux下常用的开发工具 176 8.1.1  GCC 176 8.1.2  CVS 176 8.1.3  Perl 176 8.1.4  Linux上的Delphi--Kylix 177 8.2  Linux下的Vi文本编辑器 177 8.2.1  Vi编辑器介绍 178 8.2.2  启动Vi编辑器 179 8.2.3  显示Vi中的行号 180 8.2.4  光标移动操作 181 8.2.5  屏幕命令 182 8.2.6  文本

Linux下Socket编程

http://blog.chinaunix.net/uid-20733992-id-3450058.html 原文地址:Linux下Socket编程 作者:yulianliu1218 Linux下Socket编程 什么是Socket Socket接口是TCP/IP网络的API,Socket接口定义了许多函数或例程,程序员可以用它们来开发TCP/IP网络上的应用程序.要学Internet上的TCP/IP网络编程,必须理解Socket接口. Socket接口设计者最先是将接口放在Unix操作系统里面

Linux下Socket编程的端口问题( Bind error: Address already in use )

Linux下Socket编程的端口问题( Bind error: Address already in use ) 在进行linux网络编程时,每次修改了源代码并再次编译运行时,常遇到下面的地使用错误: Bind error: Address already in use 虽然用Ctrl+C强制结束了进程,但错误依然存在,用netstat -an |grep 5120和ps aux |grep 5120都还能看到刚才用Ctrl+C“强制结束”了的进程,端口还是使用中,只好每次用kill结束进程,

Linux下Shell编程

Linux的shell编程 1.什么是shell? 当一个用户登录Linux系统之后,系统初始化程序init就为每一个用户运行一个称为shell(外壳)的程序. shell就是一个命令行解释器,它为用户提供了一个向Linux内核发送请求以便运行程序的界面系统级程序,用户可以用shell来启动.挂起.停止甚至是编写一些程序.一般的Linux系统都将bash作为默认的shell. 2.几种流行的shell 目前流行的shell有ash.bash.ksh.csh.zsh等,可以用下面的命令来查看she

linux 下 poll 编程

poll 与 select 很类似,都是对描述符进行遍历,查看是否有描述符就绪.如果有就返回就绪文件描述符的个数将.poll 函数如下: #include <poll.h> int poll(struct pollfd *fdarray, unsigned long nfds, int timeout) 第一个参数指向结构数组第一个元素的指针,每个数组都是一个 pollfd 结构iouyonghu制定额是某个给定描述符的条件. struct pollfd { int fd; short eve

Linux下shell编程实例

1. 判断一文件是不是块或字符设备文件,如果是将其拷贝到 /dev 目录下 read -p "input a file:" filename if [ -b $filename -o -c $filename ] then cp $filename /dev/ fi 2.编写一个脚本,进行简单的减法运算,要求提示输入变量 #!/bin/bash read -p "input a number:" num1 read -p "input another nu

linux下Bash编程组合测试及编写脚本(五)

linux下Bash编程组合测试及编写综合脚本(五) 1.Bash编程组合测试条件 -a: 与关系 -o: 或关系 !: 非关系 表示方法1:[ $# -gt 100 -a $# -le 500 ] 表示方法2:[ $# -gt 100 ] && [ $# -le 500 ] 2.编写一个任意添加与删除用户的脚本,要求如下: 2.1:如果脚本选项是--add:,将添加用户; 如果选项是--del,将删除用户,如果是--help显示帮助信息 2.2:脚本选项后面的参数可任意指定多个用户并且用