一、记录锁
1、概念
我们首先来看记录锁,记录锁的功能是当一个进程正在读或者修改文件的某一个部分时,它可以阻止其他进程修改同一文件区。它其实是“字节范围锁”,因为它锁定的是文件中的一个区域,当然,也可能是整个文件。如下图:
记录锁其实是不同进程间进行同步的一种锁,它主要针对的是两个不同的进程,而信号量和互斥量的着眼点在于多线程(可以是同一进程的,也可以是不同进程的)。
2、fcntl记录锁:
提到记录锁,第一反应就要想到fcntl函数,通过fcntl函数实现记录锁的功能,fcntl在APUE的文件IO中首次提及,这里我们回顾一下它的功能:
int fcntl(int fd, int cmd, ... /* int arg*/); 返回值:若成功,依赖于cmd;若出错,返回-1
fcntl的五种功能:
(1).复制一个已有的描述符
(2).获取/设置文件描述符标志
(3).获取/设置文件状态标志
(4).获取/设置异步IO所有权
(5).获取/设置记录锁
其他功能暂且不提,这里我们刚好用到第五个功能。在cmd参数中,分别为F_GETLK、F_SETLK、F_SETLKW三种,F_SETLKW是F_SETLK的阻塞版本。
记录锁中,涉及到共享读锁、独占性写锁,这里的概念与多线程同步原语的读写锁情况几乎相同,可以对比记忆。
注:用F_GETLK测试能否建立一把锁,然后用F_SETLK或F_SETLKW企图建立那把锁,这两者不是一个原子操作。因此不能保证在这两次fcntl调用之间不会有另一个进程插入并建立一把相同的锁。
3、锁的隐含继承和释放规则
(1).锁与进程和文件两者相关联
即 ① 当一个进程终止时,它建立的锁全部释放。
② 当一个描述符关闭时,则这一描述符引用的文件上的任何一把锁都会释放。如果执行下面四步:
fd1 = open(pathname, ...); read_lock(fd1,...); fd2 = dup(fd1); close(fd2);
则在close(fd2)后,在fd1上设置的锁被释放,如果将dup换成open,以打开另一描述符上的同一文件,其效果也是一样的。
(2).由fork产生的子进程不继承父进程所设置的锁。
(3).在执行exec后,新程序可以继承原执行程序的锁。
4.建议性锁和强制性锁
①.建议性锁是这样规定的:每个使用上锁文件的进程都要检查是否有锁存在,当然还得尊重已有的锁。内核和系统总体上都坚持不使用建议性锁,它们依靠程序员遵守这个规定。(Linux默认是采用建议性锁)
②.强制性锁是由内核执行的。当文件被上锁来进行写入操作时,在锁定该文件的进程释放该锁之前,内核会阻止任何对该文件的读或写访问,每次读或写访问都得检查锁是否存在。
二、信号量和互斥量的区别
上面我们提到了信号量和互斥量的着眼点都为多线程,那么他们的区别是什么?
1.信号量:是多线程同步用的,一个线程完成了某一动作就通过信号告诉别的线程,别的线程在进行某些动作。是Unix进程间通信的方式之一。
2.互斥量:是多线程互斥用的,比如说,一个线程占用了某一资源,那么别的线程就无法访问,直到这个线程离开,其他线程才开始可以利用这个资源。是同一进程下的多线程的五种同步方式之一。
3.区别(来自别人的总结)
①.互斥量用于线程的互斥,信号量用于线程的同步,这是根本区别。也就是互斥和同步的区别。
互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源
②.互斥量值只能为0/1,信号量值可以为非负整数。
即一个互斥量只能用于一个资源的互斥访问,它不能实现多个资源的多线程互斥问题。信号量可以实现多个同类资源的多线程互斥和同步。当信号量为单值信号量是,也可以完成一个资源的互斥访问。 因此,我们也可以把互斥量看成是信号量的一种特殊方式。
③. 互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到。
●此外,再简单提一下信号,之前在IPC中已经有所记录:
信号量又叫信号灯,也是一种IPC,负责协调各个线程,以保证它们正确合理的使用公共资源。可分为“二进制信号量”、“整形信号量”、“记录型信号量”,其中,“二进制信号量”就相当于互斥量,而“记录型信号量”可以看成是一个计数器(之前IPC中所学)。这里,信号量被抽象为5个操作:创建create、等待wait、释放post、试图等待TryWait、销毁destroy
●再提一下条件变量:
条件变量常与互斥锁同时使用,达到线程同步的目的。条件变量通过允许线程阻塞和等待另一个线程发送信号的方法,弥补了互斥锁的不足。所以,条件变量+互斥量 可以实现信号量的功能,但是,信号量有计数值,每次信号量post操作都会被记录,而条件变量在发送信号时,如果没有线程在等待该条件变量,那么信号将丢失。此外,信号量比互斥量实现更加复杂,且开销大,应该谨慎使用。
三、记录锁、信号量、互斥量时间性能比较
如果在多个进程间共享一个资源,则可使用这3种技术中的一种来协调访问。我们可以使用映射到两个进程地址空间中的信号量、记录锁或者互斥量。对这三种技术两两之间在时间上的差别进行比较。
若使用信号量,则先创建一个包含一个成员的信号量集合,然后将该信号量值初始化为1。为了分配资源,以sem_op为-1调用semop。为了释放资源,以sem_op为+1调用semop。对每个操作指定SEM_UNDO,以处理在未释放资源条件下进程终止的情况。
若使用记录锁,则先创建一个空文件,并且用该文件的第一个字节(无需存在)作为锁字节。为了分配资源,先对该字节获得一个写锁。释放该资源时,则对该字节解锁。记录锁的性质确保了当一个锁的持有者进程终止时,内核会自动释放该锁。
若使用互斥量,需要所有的进程将相同的文件映射到它们的地址空间中,并且使用pthread_process_shared开启互斥量的进程共享属性。为了分配资源,我们对互斥量加锁。为了释放锁,我们解锁互斥量。如果一个进程没有释放互斥量而终止,恢复将是非常困难的,除非我们使用鲁棒互斥量(pthread_mutex_consistent)。
下图显示了在Linux上,使用这3种不同技术进行锁操作所需的时间。在每一种情况下,资源都被分配、释放1000000次。这同时由3个不同的进程执行。
在Linux上,记录锁比信号量快,但是共享存储中的互斥量的性能比信号量和记录锁都要优越。如果我们能单一资源加锁,并且不需要信号量的其他功能,则记录锁比信号量要好。因为他使用起来更快,当进程终止时系统会管理遗留下来的锁。尽管对于Linux来说,共享存储中使用互斥量是个最快的选择,但是我们仍然应该使用记录锁,除非要特别考虑性能。原因是在多个进程间共享的内存中使用互斥量来恢复一个终止的进程更难,其次,进程共享的互斥量属性并没有得到所有平台的支持。