读写锁

1. 读写锁函数

读写锁是互斥锁的升级版, 在做读操作的时候可以提高程序的执行效率 如果所有的线程都是做读操作, 那么读是并行的,但使用互斥锁,读操作是串行的。

读写锁是一把锁,锁的类型为pthread_rwlock_t,有了类型之后就可以创建一把互斥锁了:

pthread_rwlock_t rwlock;

之所以称其为读写锁,是因为这把锁既可以锁定读操作,也可以锁定写操作。 为了方便理解,可以大致认为在这把锁中记录了这些信息:

  • 锁的状态: 锁定/打开
  • 锁定的是什么操作: 读操作/写操作,使用读写锁锁定了读操作,需要先解锁才能去锁定写操作,反之亦然。
  • 哪个线程将这把锁锁上了

读写锁的使用方式与互斥锁的使用方式是完全相同的: 找共享资源, 确定临界区,在临界区的开始位置加锁(读锁/写锁),临界区的结束位置解锁。

因为通过一把读写锁可以锁定读或者写操作,下面介绍一下关于读写锁的特点:

  1. 使用读写锁的读锁锁定了临界区,线程对临界区的访问是并行的,读锁是共享的。
  2. 使用读写锁的写锁锁定了临界区,线程对临界区的访问是串行的,写锁是独占的。
  3. 使用读写锁分别对两个临界区加了读锁和写锁,两个线程要同时访问者两个临界区,访问写锁临界区的线程继续运行,访问读锁临界区的线程阻塞,因为写锁比读锁的优先级高

如果程序中所有的线程都对共享资源做写操作,使用读写锁没有优势,和互斥锁是一样的 如果程序中所有的线程都对共享资源有写有读操作,且对共享资源读的操作越多,读写锁更有优势。

#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; }