读写锁
读写锁允许:
- 多个读者同时读取数据(有读锁为了在读的过程中禁止写操作,从而保证读取到的数据是完整且一致的。)
- 只有一个写者写入数据
- 读者和写者互斥
#include <pthread.h>
pthread_rwlock_t rwlock; // 读写锁
读写锁核心函数
// 初始化
pthread_rwlock_init(&rwlock, NULL);
// 读锁 - 多个读者可以同时获取
pthread_rwlock_rdlock(&rwlock);
// 写锁 - 只有一个写者可以获取
pthread_rwlock_wrlock(&rwlock);
// 解锁(读锁写锁都用这个)
pthread_rwlock_unlock(&rwlock);
// 销毁
pthread_rwlock_destroy(&rwlock);
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
// 共享数据
int shared_data = 0; // 被读写锁保护的共享数据
int reader_count = 0; // 当前正在读取的读者数量,用于演示
pthread_rwlock_t rwlock;
// 读者线程
void* reader(void* arg) {
int id = *(int*)arg;
for (int i = 0; i < 5; i++) {
// 获取读锁(多个读者可以同时进入)
pthread_rwlock_rdlock(&rwlock);
// 读取数据
printf("读者 %d: 读取数据 = %d (同时有 %d 个读者)\n",
id, shared_data, ++reader_count);
sleep(1); // 模拟读取时间
reader_count--;
pthread_rwlock_unlock(&rwlock);
sleep(1); // 等待一段时间再读
}
return NULL;
}
// 写者线程
void* writer(void* arg) {
int id = *(int*)arg;
for (int i = 0; i < 3; i++) {
// 获取写锁(独占访问)
pthread_rwlock_wrlock(&rwlock);
// 修改数据
shared_data++;
printf("写者 %d: 写入数据 = %d\n", id, shared_data);
sleep(2); // 模拟写入时间
pthread_rwlock_unlock(&rwlock);
sleep(3); // 等待一段时间再写
}
return NULL;
}
int main() {
pthread_rwlock_init(&rwlock, NULL);
pthread_t readers[3], writers[2];
int reader_ids[3] = {1, 2, 3};
int writer_ids[2] = {1, 2};
printf("=== 读写锁演示 ===\n");
printf("初始数据: %d\n\n", shared_data);
// 创建读者和写者
for (int i = 0; i < 3; i++) {
pthread_create(&readers[i], NULL, reader, &reader_ids[i]);
}
for (int i = 0; i < 2; i++) {
pthread_create(&writers[i], NULL, writer, &writer_ids[i]);
}
// 等待所有线程完成
for (int i = 0; i < 3; i++) pthread_join(readers[i], NULL);
for (int i = 0; i < 2; i++) pthread_join(writers[i], NULL);
pthread_rwlock_destroy(&rwlock);
printf("\n最终数据: %d\n", shared_data);
return 0;
}
读者 1: 读取数据 = 0 (同时有 1 个读者)
读者 2: 读取数据 = 0 (同时有 2 个读者) ← 多个读者同时读!
读者 3: 读取数据 = 0 (同时有 3 个读者)
写者 1: 写入数据 = 1 ← 写者独占访问
读者 1: 读取数据 = 1 (同时有 1 个读者)
读者 2: 读取数据 = 1 (同时有 2 个读者)
sleep不是必要的!!!只是模拟读写场景
读写锁跟互斥锁的区别
并发访问能力
- 互斥锁:对读操作和写操作都一视同仁,只要有线程持有锁,其他线程就无法获取锁,无论它们是想读还是写。这意味着即使多个线程只是想读取共享资源,也必须一个一个地进行,大大限制了并发读的能力,在大量读操作场景下,性能较低。
- 读写锁:在只有读操作时,允许多个线程同时获取读锁,极大地提高了读操作的并发性能;只有在写操作时才会独占资源,阻塞其他读线程和写线程,相比互斥锁,在读写操作频繁且读多写少的场景下,读写锁能显著提升程序的并发效率。
适用场景
- 互斥锁:适用于对共享资源的访问中,读写操作频率相当,或者写操作相对频繁的场景。例如,在多线程对一个简单计数器进行递增或递减操作时,使用互斥锁就比较合适,因为每次操作都需要独占访问计数器,不存在大量并发读的情况。
- 读写锁:更适合读多写少的场景,如多线程读取一个数据库缓存中的数据,而偶尔会有线程对缓存进行更新操作。此时使用读写锁可以让多个读线程并发读取数据,只在写操作时才阻塞所有线程,从而提高系统的整体性能 。
锁的类型与特性
- 互斥锁:只有一种类型,即独占锁,它的加锁和解锁操作简单直接,不存在锁类型转换的问题。
- 读写锁:有读锁(共享锁)和写锁(独占锁)两种类型,并且存在锁类型转换的情况,比如从读锁升级为写锁(需要先释放读锁,再获取写锁,且过程中要处理好并发问题),或者从写锁降级为读锁,这增加了使用的复杂性,但也提供了更灵活的并发控制能力。
性能开销
- 互斥锁:实现相对简单,加锁和解锁的操作开销较小,但由于其独占特性,在高并发读场景下会导致线程频繁等待,从而增加了整体的运行时间开销。
- 读写锁:虽然读写锁在实现上更为复杂,需要处理读锁和写锁之间的并发关系,但在适合的读多写少场景中,能有效减少线程等待时间,总体性能开销反而可能更低。不过在写操作频繁的情况下,读写锁的开销会增大,因为写操作会阻塞所有其他操作。