跳到主要内容

读写锁

1. 基本概念

读写锁是一种用于多线程环境中对共享资源进行访问控制的锁。它允许多个线程同时对共享资源进行读取,但是在写入共享资源时,只能有一个线程进行写入,并且在写入操作进行时,所有其他线程都不能访问该资源。这样做的好处是可以大大提高程序的并发性能。

例如,假设有一个数据库表,其中包含用户的姓名和年龄。如果有多个线程同时读取这个表,那么就可以使用读写锁来保证在多个线程同时读取该表的情况下,表中的数据不会被修改。但是,如果有一个线程想要修改表中的数据,那么它必须获得写入锁,这样才能确保在修改表中的数据时,其他线程不会访问该表。

对于读写锁,一般来说,操作步骤如下:

  1. 初始化读写锁,通常使用 pthread_rwlock_init() 函数来完成初始化。
  2. 在需要对共享资源进行读取时,调用 pthread_rwlock_rdlock() 函数来获取读取锁。
  3. 在需要对共享资源进行写入时,调用 pthread_rwlock_wrlock() 函数来获取写入锁。
  4. 在读取或写入完成后,调用 pthread_rwlock_unlock() 函数来释放读写锁。
  5. 在不再使用读写锁时,调用 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;
}