读写锁
1. 基本概念
读写锁是一种用于多线程环境中对共享资源进行访问控制的锁。它允许多个线程同时对共享资源进行读取,但是在写入共享资源时,只能有一个线程进行写入,并且在写入操作进行时,所有其他线程都不能访问该资源。这样做的好处是可以大大提高程序的并发性能。
例如,假设有一个数据库表,其中包含用户的姓名和年龄。如果有多个线程同时读取这个表,那么就可以使用读写锁来保证在多个线程同时读取该表的情况下,表中的数据不会被修改。但是,如果有一个线程想要修改表中的数据,那么它 必须获得写入锁,这样才能确保在修改表中的数据时,其他线程不会访问该表。
对于读写锁,一般来说,操作步骤如下:
- 初始化读写锁,通常使用
pthread_rwlock_init()
函数来完成初始化。 - 在需要对共享资源进行读取时,调用
pthread_rwlock_rdlock()
函数来获取读取锁。 - 在需要对共享资源进行写入时,调用
pthread_rwlock_wrlock()
函数来获取写入锁。 - 在读取或写入完成后,调用
pthread_rwlock_unlock()
函数来释放读写锁。 - 在不再使用读写锁时,调用
pthread_rwlock_destroy()
函数来销毁读写锁。
2. 读写锁API
2.1 定义读写锁变量并初始化
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER; // 定义并初始化读写锁
这种方法等价于使用 pthread_rwlock_init()
函数初始化读写锁,但是更加简洁。
注意:使用PTHREAD_RWLOCK_INITIALIZER宏初始化读写锁时,需要保证该读写锁变量是全局变量或者静态变量。
2.2 初始化读写锁
在 Linux 系统中,pthread_rwlock_init()
函数用于初始化读写锁。函数原型为:
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
该函数接受两个参数:
rwlock
:指向读写锁的指针。attr
:指向读写锁属性的指针。如果初始化成功,则该函数返回 0。否则,返回一个非 0 值。
2.3 销毁读写锁
在 Linux 系统中,pthread_rwlock_destroy()
函数用于销毁读写锁。函数原型为:
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
该函数接受一个参数:
rwlock
:指向读写锁的指针。如果销毁成功,则该函数返回 0。否则,返回一个非 0 值。
注意:在使用读写锁的过程中,通常需要在不再使用读写锁时,调用 pthread_rwlock_destroy()
函数来释放读写锁所占用的资源,以便提高系统的性能。
2.4 读锁
在 Linux 系统中,pthread_rwlock_rdlock()
函数用于获取读取锁。函 数原型为:
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
该函数接受一个参数:
rwlock
:指向读写锁的指针。如果成功获取读取锁,则该函数返回 0。否则,返回一个非 0 值。
注意:调用 pthread_rwlock_rdlock()
函数时,如果读写锁没有被其他线程占用,则该函数会立即返回,并获得读取锁。如果读写锁被其他线程占用,则该函数会阻塞,直到读 写锁被释放为止。
2.5 写锁
在 Linux 系统中,pthread_rwlock_wrlock()
函数用于获取写入锁。函数原型为:
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
该函数接受一个参数:
rwlock
:指向读写锁的指针。如果成功获取写入锁,则该函数返回 0。否则,返回一个非 0 值。
调用 pthread_rwlock_wrlock() 函数时,如果读写锁没有被其他线程占用,则该函数会立即返回,并获得写入锁。如果读写锁被其他线程占用,则该函数会阻塞,直到读写锁被释放为止。
注意,写入锁是独占锁,即在写入锁被获取时,其他线程既不能获取读取锁,也不能获取写入锁。因此,写入锁的获取一般需要放在比较短的代码段中,以避免阻塞其他线程的执行。
2.6 解除读写锁
在 Linux 系统中,pthread_rwlock_unlock()
函数用于释放读写锁。函数原型为:
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
该函数接受一个参数:
rwlock
:指向读写锁的指针。如果释放成功,则该函数返回 0。否则,返回一个非 0 值。
注意:在使用读写锁的过程中,调用 pthread_rwlock_unlock()
函数来释放读写锁是非常重要的。如果一个线程持有读写锁,但没有调用 pthread_rwlock_unlock()
函数来释放读写锁,那么其他线程将无法获取读写锁,这可能会造成程序的死锁。因此,在使用读写锁时,一定要记得在不再使用读写锁时调用 pthread_rwlock_unlock()
函数来释放读写锁。
3. 示例代码
下面的代码创建了 3 个读者线程和 2 个写者线程。每个线程都不断地尝试获取读写锁,然后执行读取或写入操作。这里可能会产生一些锁竞争,但读写锁能够确保在同一时间只有一个线程能够持有写入锁,而其他线程都能持有读取锁。这样就可以保证在写入操作进行时,其他线程可以继续进行读取操作,而不会被阻塞。
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
pthread_rwlock_t rwlock;
int shared_resource;
void *reader(void *arg)
{
int i = *((int *)arg);
while (1) {
// 获取读取锁
pthread_rwlock_rdlock(&rwlock);
printf("reader %d: read shared_resource = %d\n", i, shared_resource);
sleep(1);
// 释放读取锁
pthread_rwlock_unlock(&rwlock);
}
}
void *writer(void *arg)
{
int i = *((int *)arg);
while (1) {
// 获取写入锁
pthread_rwlock_wrlock(&rwlock);
shared_resource = i;
printf("writer %d: write shared_resource = %d\n", i, shared_resource);
sleep(1);
// 释放写入锁
pthread_rwlock_unlock(&rwlock);
}
}
int main(void)
{
pthread_t thread_id[5];
int i;
int ret;
// 初始化读写锁
pthread_rwlock_init(&rwlock, NULL);
// 创建读者线程
for (i = 0; i < 3; i++) {
ret = pthread_create(&thread_id[i], NULL, reader, &i);
if (ret != 0) {
printf("Create reader thread failed\n");
exit(EXIT_FAILURE);
}
}
// 创建写者线程
for (i = 0; i < 2; i++) {
ret = pthread_create(&thread_id[i + 3], NULL, writer, &i);
if (ret != 0) {
printf("Create writer thread failed\n");
exit(EXIT_FAILURE);
}
}
// 等待线程结束
for (i = 0; i < 5; i++) {
pthread_join(thread_id[i], NULL);
}
// 销毁读写锁
pthread_rwlock_destroy(&rwlock);
return 0;
}