八、关于锁
锁主要分为互斥锁pthread_mutex/读写锁pthread_rwlock、信号量sem、文件锁flock、记录锁(文件锁的升级版)fcntl控制
还有条件变量pthread_cond,虽然它不是锁,但经常跟互斥锁配合使用
互斥锁、信号量可用于进程间和线程间通信
读写锁用于线程间通信
文件锁、记录锁用于进程间通信
锁主要分为互斥锁pthread_mutex/读写锁pthread_rwlock、信号量sem、文件锁flock、记录锁(文件锁的升级版)fcntl控制
还有条件变量pthread_cond,虽然它不是锁,但经常跟互斥锁配合使用
互斥锁、信号量可用于进程间和线程间通信
读写锁用于线程间通信
文件锁、记录锁用于进程间通信
1 互斥锁/互斥量
可用于线程和进程间通信时加锁,通常用于线程
用于进程间通信时需要在pthread_mutex_init初始化之前,修改其属性为进程间共享
pthread_mutex_init函数
初始化一个互斥锁
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
参1:传出参数
restrict关键字:只用于限制指针,告诉编译器,所有修改该指针指向内存中内容的操作,只能通过本指针完成。不能通过除本指针以外的其他变量或指针修改
参2:互斥量属性。是一个传入参数,通常传NULL,选用默认属性(线程间共享)。 参APUE.12.4同步属性
静态初始化:如果互斥锁 mutex 是静态分配的(定义在全局,或加了static关键字修饰),可以直接使用宏进行初始化。
e.g. pthead_mutex_t muetx = PTHREAD_MUTEX_INITIALIZER;
动态初始化:局部变量应采用动态初始化。e.g. pthread_mutex_init(&mutex, NULL)
设置属性
pthread_mutexattr_t mattr 类型: 用于定义mutex锁的属性
pthread_mutexattr_init函数: 初始化mutex属性
int pthread_mutexattr_init(pthread_mutexattr_t *attr);
pthread_mutexattr_destroy函数: 销毁mutex属性
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
pthread_mutexattr_setpshared函数: 设置用于进程或线程
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared);
参2:pshared取值:
线程锁:PTHREAD_PROCESS_PRIVATE (mutex的默认属性即为线程锁,进程间私有)
进程锁:PTHREAD_PROCESS_SHARED
用于进程间通信时需要在pthread_mutex_init初始化之前,修改其属性为进程间共享
pthread_mutex_init函数
初始化一个互斥锁
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
参1:传出参数
restrict关键字:只用于限制指针,告诉编译器,所有修改该指针指向内存中内容的操作,只能通过本指针完成。不能通过除本指针以外的其他变量或指针修改
参2:互斥量属性。是一个传入参数,通常传NULL,选用默认属性(线程间共享)。 参APUE.12.4同步属性
静态初始化:如果互斥锁 mutex 是静态分配的(定义在全局,或加了static关键字修饰),可以直接使用宏进行初始化。
e.g. pthead_mutex_t muetx = PTHREAD_MUTEX_INITIALIZER;
动态初始化:局部变量应采用动态初始化。e.g. pthread_mutex_init(&mutex, NULL)
设置属性
pthread_mutexattr_t mattr 类型: 用于定义mutex锁的属性
pthread_mutexattr_init函数: 初始化mutex属性
int pthread_mutexattr_init(pthread_mutexattr_t *attr);
pthread_mutexattr_destroy函数: 销毁mutex属性
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
pthread_mutexattr_setpshared函数: 设置用于进程或线程
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared);
参2:pshared取值:
线程锁:PTHREAD_PROCESS_PRIVATE (mutex的默认属性即为线程锁,进程间私有)
进程锁:PTHREAD_PROCESS_SHARED
pthread_mutex_destroy函数
销毁一个互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
pthread_mutex_lock函数
加锁。
int pthread_mutex_lock(pthread_mutex_t *mutex);
pthread_mutex_unlock函数
解锁。
int pthread_mutex_unlock(pthread_mutex_t *mutex);
pthread_mutex_trylock函数
尝试加锁
int pthread_mutex_trylock(pthread_mutex_t *mutex);
2 信号量
由于互斥锁的粒度比较大,如果我们希望在多个线程间对某一对象的部分数据进行共享,使用互斥锁是没有办法实现的,只能将整个数据对象锁住。
这样虽然达到了多线程操作共享数据时保证数据正确性的目的,却无形中导致线程的并发性下降。
线程从并行执行,变成了串行执行。与直接使用单进程无异。
信号量,是相对折中的一种处理方式,既能保证同步,数据不混乱,又能提高线程并发。
信号量的返回值都是成功返回0, 失败返回-1,同时设置errno
信号量的初值,决定了占用信号量的线程的个数。
信号量基本操作:
sem_wait: 1. 信号量大于0,则信号量-- (类比pthread_mutex_lock)
| 2. 信号量等于0,造成线程阻塞
对应
|
sem_post: 将信号量++,同时唤醒阻塞在信号量上的线程 (类比pthread_mutex_unlock)
但,由于sem_t的实现对用户隐藏,所以所谓的++、--操作只能通过函数来实现,而不能直接++、--符号。
信号量的初值,决定了占用信号量的线程的个数。
由于互斥锁的粒度比较大,如果我们希望在多个线程间对某一对象的部分数据进行共享,使用互斥锁是没有办法实现的,只能将整个数据对象锁住。
这样虽然达到了多线程操作共享数据时保证数据正确性的目的,却无形中导致线程的并发性下降。
线程从并行执行,变成了串行执行。与直接使用单进程无异。
信号量,是相对折中的一种处理方式,既能保证同步,数据不混乱,又能提高线程并发。
信号量的返回值都是成功返回0, 失败返回-1,同时设置errno
信号量的初值,决定了占用信号量的线程的个数。
信号量基本操作:
sem_wait: 1. 信号量大于0,则信号量-- (类比pthread_mutex_lock)
| 2. 信号量等于0,造成线程阻塞
对应
|
sem_post: 将信号量++,同时唤醒阻塞在信号量上的线程 (类比pthread_mutex_unlock)
但,由于sem_t的实现对用户隐藏,所以所谓的++、--操作只能通过函数来实现,而不能直接++、--符号。
信号量的初值,决定了占用信号量的线程的个数。
sem_init函数
初始化一个信号量
int sem_init(sem_t *sem, int pshared, unsigned int value);
参1:sem信号量
参2:pshared取0用于线程间;取非0(一般为1)用于进程间
参3:value指定信号量初值
sem_destroy函数
销毁一个信号量
int sem_destroy(sem_t *sem);
sem_wait函数
给信号量加锁 --
int sem_wait(sem_t *sem);
sem_post函数
给信号量解锁 ++
int sem_post(sem_t *sem);
sem_trywait函数
尝试对信号量加锁 -- (与sem_wait的区别类比lock和trylock)
int sem_trywait(sem_t *sem);
sem_timedwait函数
限时尝试对信号量加锁 --
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
参2:abs_timeout采用的是绝对时间。
定时1秒:
time_t cur = time(NULL); 获取当前时间。
struct timespec t; 定义timespec 结构体变量t
t.tv_sec = cur+1; 定时1秒
t.tv_nsec = t.tv_sec +100;
sem_timedwait(&sem, &t); 传参
给信号量解锁 ++
int sem_post(sem_t *sem);
sem_trywait函数
尝试对信号量加锁 -- (与sem_wait的区别类比lock和trylock)
int sem_trywait(sem_t *sem);
sem_timedwait函数
限时尝试对信号量加锁 --
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
参2:abs_timeout采用的是绝对时间。
定时1秒:
time_t cur = time(NULL); 获取当前时间。
struct timespec t; 定义timespec 结构体变量t
t.tv_sec = cur+1; 定时1秒
t.tv_nsec = t.tv_sec +100;
sem_timedwait(&sem, &t); 传参
3 读写锁
特性:读共享,写独占,写优先级高
返回值,成功返回0,失败返回错误号
pthread_rwlock_init函数
初始化一把读写锁
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
参2:attr表读写锁属性,通常使用默认属性,传NULL即可。
pthread_rwlock_destroy函数
销毁一把读写锁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
pthread_rwlock_rdlock函数
以读方式请求读写锁。(常简称为:请求读锁)
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
pthread_rwlock_wrlock函数
以写方式请求读写锁。(常简称为:请求写锁)
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
pthread_rwlock_unlock函数
解锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
pthread_rwlock_tryrdlock函数
非阻塞以读方式请求读写锁(非阻塞请求读锁)
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
pthread_rwlock_trywrlock函数
非阻塞以写方式请求读写锁(非阻塞请求写锁)
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
4 文件锁flock
特性:读共享,写独占,写优先级高
返回值,成功返回0,失败返回错误号
pthread_rwlock_init函数
初始化一把读写锁
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
参2:attr表读写锁属性,通常使用默认属性,传NULL即可。
pthread_rwlock_destroy函数
销毁一把读写锁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
pthread_rwlock_rdlock函数
以读方式请求读写锁。(常简称为:请求读锁)
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
pthread_rwlock_wrlock函数
以写方式请求读写锁。(常简称为:请求写锁)
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
pthread_rwlock_unlock函数
解锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
pthread_rwlock_tryrdlock函数
非阻塞以读方式请求读写锁(非阻塞请求读锁)
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
pthread_rwlock_trywrlock函数
非阻塞以写方式请求读写锁(非阻塞请求写锁)
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
4 文件锁flock
int flock(int fd, int operation);
成功返回0,失败返回-1,同时设置errno
参数1为文件描述符
参数2位锁的方式:
LOCK_SH,共享锁,多个进程共享同一把锁,常被用作读锁;
LOCK_EX,排他锁,只允许一个进程使用,常被用作写锁;
LOCK_UN,解锁
LOCK_NB, 设置非阻塞,若是设置了该属性,尝试加锁时发现锁被其他进程占用
则返回错误,并设置errno = EWOULDBLOCK
5 记录锁(文件锁的升级版)
成功返回0,失败返回-1,同时设置errno
参数1为文件描述符
参数2位锁的方式:
LOCK_SH,共享锁,多个进程共享同一把锁,常被用作读锁;
LOCK_EX,排他锁,只允许一个进程使用,常被用作写锁;
LOCK_UN,解锁
LOCK_NB, 设置非阻塞,若是设置了该属性,尝试加锁时发现锁被其他进程占用
则返回错误,并设置errno = EWOULDBLOCK
5 记录锁(文件锁的升级版)
利用fcntl来加锁。
操作文件的进程没有获得锁时,可以打开文件,但无法read、write。
这种方式可以实现对文件的某一部分加锁
int fcntl(int fd, int cmd, ... /* arg */ );
参数1为文件描述符
参2:
F_SETLK (struct flock *) 设置记录锁,非阻塞
F_SETLKW (struct flock *) 设置记录锁,阻塞
F_GETLK (struct flock *) 获取记录锁
参3:结构体地址
struct flock {
...
short l_type; 锁类型: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; 锁的长度:0表示整个文件加锁/* Number of bytes to lock */
pid_t l_pid; 持有该锁的进程ID:(F_GETLK only)
...
};
用法:
struct flock l;
l.l_type = F_WRLCK;
l.l_whence = SEEK_SET;
l.l_start = 0;
l.l_len = 128;
int ret = fcntl(fd, F_SETLKW, &l);
6 条件变量
条件变量不是锁,但他也会造成线程阻塞,通常与互斥锁搭配使用
返回值,成功返回0,失败返回错误号
如果没有线程在等待条件,此时唤醒函数pthread_cond_signal不会唤醒任何的线程,也不会记录。
如果有多个线程在执行pthread_cond_wait,
而此时有一个线程调用pthread_cond_signal,那么只会唤醒其中一个线程。
如果想唤醒所有线程,那么调用pthread_cond_broadcast,该函数可以唤醒等待该条件的所有线程。
操作文件的进程没有获得锁时,可以打开文件,但无法read、write。
这种方式可以实现对文件的某一部分加锁
int fcntl(int fd, int cmd, ... /* arg */ );
参数1为文件描述符
参2:
F_SETLK (struct flock *) 设置记录锁,非阻塞
F_SETLKW (struct flock *) 设置记录锁,阻塞
F_GETLK (struct flock *) 获取记录锁
参3:结构体地址
struct flock {
...
short l_type; 锁类型: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; 锁的长度:0表示整个文件加锁/* Number of bytes to lock */
pid_t l_pid; 持有该锁的进程ID:(F_GETLK only)
...
};
用法:
struct flock l;
l.l_type = F_WRLCK;
l.l_whence = SEEK_SET;
l.l_start = 0;
l.l_len = 128;
int ret = fcntl(fd, F_SETLKW, &l);
6 条件变量
条件变量不是锁,但他也会造成线程阻塞,通常与互斥锁搭配使用
返回值,成功返回0,失败返回错误号
如果没有线程在等待条件,此时唤醒函数pthread_cond_signal不会唤醒任何的线程,也不会记录。
如果有多个线程在执行pthread_cond_wait,
而此时有一个线程调用pthread_cond_signal,那么只会唤醒其中一个线程。
如果想唤醒所有线程,那么调用pthread_cond_broadcast,该函数可以唤醒等待该条件的所有线程。
pthread_cond_init函数
初始化一个条件变量
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
参2:attr表条件变量属性,通常为默认值,传NULL即可
也可以使用静态初始化的方法,初始化条件变量:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_cond_destroy函数
销毁一个条件变量
int pthread_cond_destroy(pthread_cond_t *cond);
pthread_cond_wait函数
阻塞等待一个条件变量
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
流程:
第一,获得互斥锁
第二,
阻塞等待条件cond(参1)满足,并解锁获得的互斥锁,这一步为一个原子操作。
第三,当条件满足(被信号唤醒),pthread_cond_wait函数返回,解除阻塞并重新申请加锁;
第四,解锁
其中第一和第四步需要手动使用pthread_mutex实现代码
pthread_cond_timedwait函数
限时等待一个条件变量
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
前面两个参数与pthread_cond_wait相同
第三个参数是绝对时间,为相对于unix时间戳的时间
struct timespec {
time_t tv_sec; /* seconds */ 秒
long tv_nsec; /* nanosecondes*/ 纳秒
}
正确用法:
time_t cur = time(NULL); 获取当前时间。
struct timespec t; 定义timespec 结构体变量t
t.tv_sec = cur+1; 定时1秒
对比该结构体的相对时间
struct timeval {
time_t tv_sec; /* seconds */ 秒
suseconds_t tv_usec; /* microseconds */ 微秒
};
pthread_cond_signal函数
唤醒至少一个阻塞在条件变量上的线程,通常就是唤醒一个
int pthread_cond_signal(pthread_cond_t *cond);
pthread_cond_broadcast函数
唤醒全部阻塞在条件变量上的线程
int pthread_cond_broadcast(pthread_cond_t *cond);
初始化一个条件变量
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
参2:attr表条件变量属性,通常为默认值,传NULL即可
也可以使用静态初始化的方法,初始化条件变量:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_cond_destroy函数
销毁一个条件变量
int pthread_cond_destroy(pthread_cond_t *cond);
pthread_cond_wait函数
阻塞等待一个条件变量
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
流程:
第一,获得互斥锁
第二,
阻塞等待条件cond(参1)满足,并解锁获得的互斥锁,这一步为一个原子操作。
第三,当条件满足(被信号唤醒),pthread_cond_wait函数返回,解除阻塞并重新申请加锁;
第四,解锁
其中第一和第四步需要手动使用pthread_mutex实现代码
pthread_cond_timedwait函数
限时等待一个条件变量
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
前面两个参数与pthread_cond_wait相同
第三个参数是绝对时间,为相对于unix时间戳的时间
struct timespec {
time_t tv_sec; /* seconds */ 秒
long tv_nsec; /* nanosecondes*/ 纳秒
}
正确用法:
time_t cur = time(NULL); 获取当前时间。
struct timespec t; 定义timespec 结构体变量t
t.tv_sec = cur+1; 定时1秒
对比该结构体的相对时间
struct timeval {
time_t tv_sec; /* seconds */ 秒
suseconds_t tv_usec; /* microseconds */ 微秒
};
pthread_cond_signal函数
唤醒至少一个阻塞在条件变量上的线程,通常就是唤醒一个
int pthread_cond_signal(pthread_cond_t *cond);
pthread_cond_broadcast函数
唤醒全部阻塞在条件变量上的线程
int pthread_cond_broadcast(pthread_cond_t *cond);