第4章 读写锁和记录上锁
4.1 读写锁
读写锁用于读取数据比修改数据更频繁的场景,它的分配规则如下:
1. 没有线程持有写锁时,任意多的线程可以持有读锁。
2. 仅当没有线程持有读锁或写锁时,才能分配写锁。
当已有线程持有读锁时,另一线程申请写锁则会阻塞,若后续还有读锁的申请,此时有两种策略:
1. 对后续的读锁请求都通过,可能会造成因读锁不断被分配,写锁申请始终阻塞,“饿死”了写进程。
3. 后续读锁请求都阻塞,等当前持有的读锁都结束后优先分配写锁。
4.2 功能函数
读写锁的静态分配可用PTHREAD_RWLOCK_INITIALIZER,动态分配用pthread_rwlock_init初始化,它的属性也是PTHREAD_PROCESS_PRIVATE或PTHREAD_PROCESS_SHARED,也需要对应类型的属性对象。读写锁的init和destroy要结对使用,pthread_rwlockattr_t属性对象也同样。
pthread_rwlock_rdlock:阻塞模式申请读锁。
pthread_rwlock_wrlock:阻塞模式申请写锁。
pthread_rwlock_tryrdlock:非阻塞模式申请读锁。
pthread_rwlock_tryrdlock:非阻塞模式申请读锁。
pthread_rwlock_unlock:解锁。
4.3 记录上锁
记录上锁是读写锁的一种扩展类型,它可用于任意两个进程间共享某文件的读和写。执行上锁的函数是fcntl,锁由内核维护,其属主由进程ID标识。
Unix内核没有记录这一概念,对记录的解释是由读写文件的应用进行的。每个记录就是文件中的一个字节范围。
Posix记录上锁的粒度是单个字节,粒度越小,允许同时使用的用户数就越多。
4.4 fcntl
int fcntl(int fd, int cmd, .../* struct flock *arg*/);
fcntl函数的cmd参数为以下三个值时,执行记录上锁的相关操作:
1. F_SETLK 获取或释放指定的锁,若无法完成该操作则返回出错而不阻塞。
2. F_SETLKW 阻塞版本的F_SETLK。
3. F_GETLK 检查arg指向的锁是否与某个已存在的锁冲突。
F_GETLK后紧接着F_SETLK不是原子操作。
fcntl不能对只读打开的文件获取写锁,也不能对只写打开的文件获取读锁。
锁住整个文件的两个方式:
1. l_whence成员为SEEK_SET,l_start的成员为0,l_len成员为0。
2. 用lseek把读写指针放到文件头,然后令l_whence为SEEK_SET,l_start为0,l_len为0。
某个文件描述符被关闭时,与它关联的记录锁都被删除。记录锁不能通过fork子进程继承。
记录锁不应该同标准I/O函数一起使用,因为标准I/O库使用了缓冲。
NFS可以使用记录锁。