一、读写锁

1.介绍

读写锁(ReadWrite Locks,也称为共享-互斥锁)是一个用于同步访问的机制,允许多个读取者同时访问同一资源,但在任何时候只允许一个写入者。这通常用于数据结构(如列表、数组或散列表)的并发访问,其中读取操作比写入操作频繁得多。

在 POSIX 线程(Pthreads)库中,你可以使用 pthread_rwlock_t 类型的变量表示读写锁,使用 pthread_rwlock_init 来初始化或者PTHREAD_RWLOCK_INITIALIZER初始化静态的锁,pthread_rwlock_destroy 来销毁。以下是一些基本的操作:

  • pthread_rwlock_rdlock:获取读锁。如果有其他线程持有写锁,或者有其他线程正在等待获取写锁,那么这个函数会阻塞,直到可以安全地获取读锁为止。

  • pthread_rwlock_wrlock:获取写锁。如果有其他线程持有读锁或写锁,那么这个函数会阻塞,直到可以安全地获取写锁为止。

  • pthread_rwlock_unlock:释放读锁或写锁。

#include ​pthread_rwlock_t lock;​void read_data() {pthread_rwlock_rdlock(&lock);// 读取数据的代码pthread_rwlock_unlock(&lock);}​void write_data() {pthread_rwlock_wrlock(&lock);// 写入数据的代码pthread_rwlock_unlock(&lock);}

在这个例子中,read_data 函数获取读锁,然后读取数据,最后释放锁。write_data 函数获取写锁,写入数据,然后释放锁。使用读写锁可以使多个读取者并行访问数据,但在写入数据时,仍然只有一个写入者可以访问数据,确保数据的一致性。

2.函数原型

创建读写锁

int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);

参数:

  • pthread_rwlock_t *restrict rwlock:指向将被初始化的读写锁的指针。

  • const pthread_rwlockattr_t *restrict attr:指向读写锁属性对象的指针,该对象可以用于设置读写锁的属性。如果传入 NULL,则使用默认的属性。

返回值:如果成功,返回 0;否则,返回一个错误码。

销毁读写锁

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

参数:

  • pthread_rwlock_t *rwlock:指向需要销毁的读写锁的指针。

返回值:如果成功,返回 0;否则,返回一个错误码。

使用读写锁

获取读锁:

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

参数:

  • pthread_rwlock_t *rwlock:指向需要获取读锁的读写锁的指针。

返回值:如果成功,返回 0;否则,返回一个错误码。

获取写锁:

int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

参数:

  • pthread_rwlock_t *rwlock:指向需要获取写锁的读写锁的指针。

返回值:如果成功,返回 0;否则,返回一个错误码。

解锁:

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

参数:

  • pthread_rwlock_t *rwlock:指向需要解锁的读写锁的指针。

返回值:如果成功,返回 0;否则,返回一个错误码。

需要注意的是,任何成功地调用了 pthread_rwlock_rdlockpthread_rwlock_wrlock 的线程都必须在不需要锁的时候调用 pthread_rwlock_unlock。如果同一个线程多次调用 pthread_rwlock_rdlockpthread_rwlock_wrlock,就可能会导致死锁。此外,试图解锁一个已经被其他线程持有的锁,或者未被任何线程持有的锁,都是未定义的行为。

3.例子

#include #include ​#define NUM_READER_THREADS 5​pthread_rwlock_t rwlock;int counter = 0; // 共享资源​void *reader(void *arg) {pthread_rwlock_rdlock(&rwlock);printf("Reader: counter = %d\n", counter);pthread_rwlock_unlock(&rwlock);return NULL;}​void *writer(void *arg) {pthread_rwlock_wrlock(&rwlock);counter++;printf("Writer: counter = %d\n", counter);pthread_rwlock_unlock(&rwlock);return NULL;}​int main() {pthread_t reader_threads[NUM_READER_THREADS], writer_thread;​pthread_rwlock_init(&rwlock, NULL);​pthread_create(&writer_thread, NULL, writer, NULL);for (int i = 0; i < NUM_READER_THREADS; i++) {pthread_create(&reader_threads[i], NULL, reader, NULL); }​pthread_join(writer_thread, NULL);for (int i = 0; i < NUM_READER_THREADS; i++) {pthread_join(reader_threads[i], NULL); }​pthread_rwlock_destroy(&rwlock);​return 0;}

需要注意对于线程相关代码的编译需要链接上posix线程库:gcc xxx.c -lpthread -l是链接选项,pthread是库名称,默认不添加空格也可以添加空格gcc xxx.c -l pthread

二、带超时机制的读写锁

1.介绍

POSIX 线程库提供了两个函数,用于带有超时机制的读写锁操作:

  • pthread_rwlock_timedrdlock:尝试获取读锁,如果在指定的时间内无法获取,则返回超时错误。

  • pthread_rwlock_timedwrlock:尝试获取写锁,如果在指定的时间内无法获取,则返回超时错误。

这两个函数的函数原型如下:

int pthread_rwlock_timedrdlock(pthread_rwlock_t *restrict rwlock,const struct timespec *restrict abs_timeout);

参数:

  • pthread_rwlock_t *restrict rwlock:指向要获取读锁的读写锁的指针。

  • const struct timespec *restrict abs_timeout:指向 timespec 结构的指针,该结构定义了一个绝对时间,用于指定超时的时间点。如果在这个时间点之前还不能获取读

  • 锁,函数会返回一个超时错误。

返回值:如果成功,返回 0;否则,返回一个错误码。如果超时,返回 ETIMEDOUT

int pthread_rwlock_timedwrlock(pthread_rwlock_t *restrict rwlock,const struct timespec *restrict abs_timeout);

参数:

  • pthread_rwlock_t *restrict rwlock:指向要获取写锁的读写锁的指针。

  • const struct timespec *restrict abs_timeout:同上,用于指定超时的时间点。

返回值:同上。

这两个函数都使用了 timespec 结构来表示超时的时间点,该结构有两个成员:tv_sectv_nsec,分别表示秒和纳秒。这个时间表示的是一个绝对时间,即从某个固定的时间点(如 Unix 纪元,1970 年 1 月 1 日)开始的时间。

需要注意的是,pthread_rwlock_timedrdlockpthread_rwlock_timedwrlock 这两个函数在 POSIX 标准中是可选的,因此,一些系统可能不支持这两个函数。你应该检查你的系统文档,看看这些函数是否可用。