读写锁介绍
读写锁是互斥锁的升级版,如果加了读锁,多个线程作读操作,那么读是并发的或者并行的,所以可以提高读操作的时候程序的执行效率。但是在写操作的时候,和互斥锁一样,还是串行的。
读写锁在 Linux 中类型为 pthread_rwlock_t
,有了类型就可以创建读写锁了:
pthread_rwlock_t rwlock;
读写锁记录了这些信息:
- 锁定状态:锁定 / 打开
- 锁定的是什么操作:读操作/写操作
- 哪个线程上的锁
读写锁的使用方式和互斥锁的使用方式一样:找共享资源,确定临界区,在临界区的开始位置加锁(读锁 / 写锁),临界区结束的位置解锁。
读写锁的特点:
- 读锁是共享的,使用读写锁的读锁锁定了临界区,线程对临界区的访问是并发或是并行的
- 写锁是独占的,使用读写锁的写锁锁定了临界区,线程对临界区的访问是串行的
- 写锁比读锁的优先级高,使用读写锁分别对两个临界区加了读锁和写锁,两个线程要同时访问两个临界区,访问写锁临界区的线程继续运行,访问读锁临界区的线程阻塞
- 读的时候不能写,写的时候不能读。
什么时候用读写锁
基于读写锁的特点,如果程序中的所有线程都对共享资源做写操作,使用读写锁没有优势,因为写操作都是串行执行的,和用互斥锁没有区别。如果说程序中所有线程都对共享资源有写操作也有读操作,并且对共享资源读的操作越多,读写锁更有优势
读写锁函数
Linux 提供的读写锁操作函数原型如下,如果调用成功返回 0,失败返回对应的错误号:
#include <pthread.h>
pthread_rwlock_t rwlock;
// 初始化读写锁
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
const pthread_rwlockattr_t *restrict attr);
// 释放读写锁占用的系统资源
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
参数:
- rwlock:读写锁的地址,传出参数
- attr:读写锁属性,一般使用默认属性,指定为NULL
// 在程序中对读写锁加读锁, 锁定的是读操作
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);
读写锁的使用示例
8个线程操作同一个全局变量,3个线程不定时写同一全局资源,5个线程不定时读同一全局资源。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
// 全局变量
int number = 0;
// 定义读写锁
pthread_rwlock_t rwlock;
// 写的线程的处理函数
void* writeNum(void* arg)
{
while(1)
{
pthread_rwlock_wrlock(&rwlock);
int cur = number;
cur ++;
number = cur;
printf("++写操作完毕, number : %d, tid = %ld\n", number, pthread_self());
pthread_rwlock_unlock(&rwlock);
// 添加sleep目的是要看到多个线程交替工作
usleep(rand() % 100);
}
return NULL;
}
// 读线程的处理函数
// 多个线程可以如果处理动作相同, 可以使用相同的处理函数
// 每个线程中的栈资源是独享
void* readNum(void* arg)
{
while(1)
{
pthread_rwlock_rdlock(&rwlock);
printf("--全局变量number = %d, tid = %ld\n", number, pthread_self());
pthread_rwlock_unlock(&rwlock);
usleep(rand() % 100);
}
return NULL;
}
int main()
{
// 初始化读写锁
pthread_rwlock_init(&rwlock, NULL);
// 3个写线程, 5个读的线程
pthread_t wtid[3];
pthread_t rtid[5];
for(int i=0; i<3; ++i)
{
pthread_create(&wtid[i], NULL, writeNum, NULL);
}
for(int i=0; i<5; ++i)
{
pthread_create(&rtid[i], NULL, readNum, NULL);
}
// 释放资源
for(int i=0; i<3; ++i)
{
pthread_join(wtid[i], NULL);
}
for(int i=0; i<5; ++i)
{
pthread_join(rtid[i], NULL);
}
// 销毁读写锁
pthread_rwlock_destroy(&rwlock);
return 0;
}