UNP(卷2:进程间通信)—— 第7、8、9章:互斥锁、条件变量、读写锁、记录上锁

来源:互联网 发布:linux命令强制关机 编辑:程序博客网 时间:2024/05/01 00:30

互斥锁

相互排斥(mutual exclusion),用于保护临界区。

Posix互斥锁被声明为具有pthread_mutex_t数据类型的变量。

如果互斥锁的变量是静态分配的,那么我们可以把它初始化成常值PTHREAD_MUTEX_INITIALIZER

若是动态分配的,必须在运行之时,通过调用pthread_mutex_init函数来初始化它。

#include <pthread.h>int pthread_mutex_lock(pthread_mutex_t *mutex);int pthread_mutex_trylock(pthread_mutex_t *mutex);int pthread_mutex_unlock(pthread_mutex_t *mutex);                                                         // 返回:成功为0,出错则为正的Exxx值
若给已经锁住的锁上锁,pthread_mutex_lock会阻塞到解锁为止pthread_mutex_trylock是非阻塞版本,则会返回EBUSY错误

互斥锁实际保护的是在临界区中被操纵的数据。(多进程或多线程之间的共享数据)


条件变量

互斥锁用于上锁,条件变量则用于等待。条件变量是类型为pthread_cond_t 的变量。

#include <pthread.h>int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);int pthread_cond_broadcast(pthread_cond_t *cond);int pthread_cond_signal(pthread_cond_t *cond);                                                   // 返回:若成功则为0,出错为Exxx值。
每个条件变量总有一个互斥锁与之关联。我们调用pthread_cond_wait等待某个条件为真时,还会指定其条件变量的地址和所关联锁的地址。

pthread_cond_signal只能唤醒等待在相应条件变量上的一个线程pthread_cond_broadcast唤醒阻塞在相应条件变量的所有线程

默认初始化:PTHREAD_MUTEX_INITIALIZER  、PTHREAD_COND_INITIALIZER

#include <pthread.h>int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);int pthread_mutex_destroy(pthread_mutex_t *mutex);pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);int pthread_cond_destroy(pthread_cond_t *cond);pthread_cond_t cond = PTHREAD_COND_INITIALIZER;                                                   // 返回:若成功则为0,出错为Exxx值。

互斥锁属性,条件变量属性

#include <pthread.h>int pthread_mutexattr_init(pthread_mutexattr_t *attr);int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);int pthread_condattr_init(pthread_condattr_t *attr);int pthread_condattr_destroy(pthread_condattr_t *attr);                                                   // 返回:若成功则为0,出错为Exxx值。


获取属性、设置属性

#include <pthread.h>int pthread_mutexattr_getpshared(const pthread_mutexattr_t *restrict attr, int *restrict pshared);int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared);int pthread_condattr_getpshared(const pthread_condattr_t *restrict attr, int *restrict pshared);int pthread_condattr_setpshared(pthread_condattr_t *attr, int pshared);                                                  // 返回:若成功则为0,出错为Exxx值。
pshared可以是 PTHREAD_PROCESS_PRIVATE 、 PTHREAD_PROCESS_SHARED 

进程终止时内核总是自动清理的唯一同步锁类型是fcntl记录锁

读写锁

数据类型pthread_rwlock_t,若静态分配,可以用PTHREAD_RWLOCK_INITIALIZER来初始化。

#include <pthread.h>int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);                                                     // 返回:若成功则为0,出错为Exxx值。


读写锁属性:

#include <pthread.h>int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);                                                     // 返回:若成功则为0,出错为Exxx值。
设置属性:

#include <pthread.h>int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *restrict attr, int *restrict pshared);int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared);// 返回:若成功则为0,出错为Exxx值。
pshared值: PTHREAD_PROCESS_PRIVATE 或 PTHREAD_PROCESS_SHARED (不同进程间共享,不仅仅是单进程中线程之间的共享)

一个线程可以被同一进程内的任何其他线程所取消。

#include <pthread.h>int pthread_cancel(pthread_t thread);                                            // Compile and link with -pthread.                                            // 返回:若成功则为0,出错为Exxx值。
为处理被取消的可能情况,任何线程可以安装(压入)和删除(弹出)清理处理程序
#include <pthread.h>void pthread_cleanup_push(void (*routine)(void *), void *arg);void pthread_cleanup_pop(int execute);                                            // 返回:若成功则为0,出错为Exxx值。
这些处理程序就是发生以下情况时被调用的函数。

  • 调用线程被取消(pthread_cancel)
  • 调用线程自愿终止(pthread_exit)

记录上锁

POSIX记录上锁定义了一个特殊的字节范围以指定整个文件。待上锁或解锁的部分:字节范围。

Posix fcntl 记录上锁:

#include <unistd.h>#include <fcntl.h>int fcntl(int fd, int cmd, ... /* struct flock *arg */ );                                              // 返回:若成功则取决于cmd,出错为-1struct flock {    ...    short l_type;    /* Type of lock: F_RDLCK, F_WRLCK, F_UNLCK */    short l_whence;  /* How to interpret l_start: SEEK_SET, SEEK_CUR, SEEK_END */    off_t l_start;   /* Starting offset for lock */    off_t l_len;     /* Number of bytes to lock */    pid_t l_pid;     /* PID of process blocking our lock (set by F_GETLK and F_OFD_GETLK) */    ...};
cmd参数有三个命令:

F_SETLK:获取(l_type为F_RDLCK或F_WRLCK)或释放(l_type为F_UNLCK)由arg指向的flock结构所描述的锁。

F_SETLKW:与F_SETLK类似,若无法请求到锁,调用线程将阻塞到该锁能够授予为止。(W意思是wait)

F_GETLK:检查由arg指向的锁以确定是否有某个已存在的锁会妨碍新锁授予调用进程。如果当前没有这样的锁存在,由arg指向的flock结构的l_type被置为F_UNLCK。否则,关于这个已存在锁的信息将在由arg指向的flock结构中返回(该结构的内容由fcntl函数覆写),其中包含持有该锁的进程ID。

应该清楚发出F_GETLK命令后紧接着发出F_SETLK命令不是一个原子操作。也就是说,如果我们发出F_GETLK命令,并且执行该命令的fcntl函数返回时设置l_type的值为F_UNLCK,那么跟着立即发出F_SETLK命令不能保证其fcntl函数会成功返回。因为这两次命令调用期间可能有另外一个进程运行并获取了我们想要的锁。    

其实提供F_GETLK命令的原因在于:当执行F_SETLK命令的fcntl函数返回一个错误时,导致该错误的某个锁的信息可由F_GETLK命令返回,从而允许我们确定是哪个进程锁住了我们所要请求的文件区。但是即使是这样的情形,F_GETLK命令也可能返回该文件区已解锁的信息,因为在F_SETLK和F_GETLK命令之间,该文件区可能被解锁。

flock结构描述锁的类型以及待锁住的字节范围。

l_whence成员有三个值:

SEEK_SET:l_start相对于文件的开头解释;

SEEK_CUR:l_start相对与文件的当前字节偏移解释;

SEEK_END:l_start相对于文件的末尾解释。

l_len成员指定从该偏移开始的连续字节数。长度为0表示锁住整个文件,一般锁整个文件如下使用:指定l_whence成员为SEEK_SET,l_start为0,l_len为0。

Posix记录上锁称为劝告性上锁(advisory locking)。一个进程能够无视一个劝告性锁而写一个读锁定文件,或者读一个写锁定文件。

有些系统提供了强制性上锁(mandatory locking)。使用强制性锁后,内核将检查每个read和write请求,以验证其操作不会干扰由某个进程持有的某个锁。对于通常的阻塞式描述字,与某个强制性锁冲突的read或write将把调用进程投入睡眠,直到该锁释放为止。对于非阻塞式描述字,与某个强制性锁冲突的read或write将导致它们返回一个EAGAIN错误。

对某个特定文件施行强制性上锁,应满足:

  • 组成员执行位必须关闭;
  • SGID位必须打开。
强制性锁不需要新的系统调用。

虽然强制性上锁有一定作用,但多个进程在更新同一个文件时,仍然会导致混乱。进程之间还是需要某种上锁形式的协作。

当一个文件区被锁住时,待处理的读出者和写入者的优先级是不可知的。































阅读全文
0 0
原创粉丝点击