1. 读写锁函数
读写锁是互斥锁的升级版, 在做读操作的时候可以提高程序的执行效率 如果所有的线程都是做读操作, 那么读是并行的,但使用互斥锁,读操作是串行的。
读写锁是一把锁,锁的类型为pthread_rwlock_t
,有了类型之后就可以创建一把互斥锁了:
pthread_rwlock_t rwlock;
之所以称其为读写锁,是因为这把锁既可以锁定读操作,也可以锁定写操作。 为了方便理解,可以大致认为在这把锁中记录了这些信息:
- 锁的状态: 锁定/打开
- 锁定的是什么操作: 读操作/写操作,使用读写锁锁定了读操作,需要先解锁才能去锁定写操作,反之亦然。
- 哪个线程将这把锁锁上了
读写锁的使用方式与互斥锁的使用方式是完全相同的: 找共享资源, 确定临界区,在临界区的开始位置加锁(读锁/写锁),临界区的结束位置解锁。
因为通过一把读写锁可以锁定读或者写操作,下面介绍一下关于读写锁的特点:
- 使用读写锁的
读锁
锁定了临界区,线程对临界区的访问是并行的,读锁是共享的。 - 使用读写锁的
写锁
锁定了临界区,线程对临界区的访问是串行的,写锁是独占的。 - 使用读写锁分别对两个临界区加了读锁和写锁,两个线程要同时访问者两个临界区,访问写锁临界区的线程继续运行,访问读锁临界区的线程阻塞,因为
写锁比读锁的优先级高
。
如果程序中所有的线程都对共享资源做写操作,使用读写锁没有优势,和互斥锁是一样的 如果程序中所有的线程都对共享资源有写有读操作,且对共享资源
读的操作越多,读写锁更有优势。
#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);
- 返回值 函数调用成功返回0,失败返回对应的错误号
- 参数 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);
2. 读写锁的使用
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; }