读写锁(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
函数获取写锁,写入数据,然后释放锁。使用读写锁可以使多个读取者并行访问数据,但在写入数据时,仍然只有一个写入者可以访问数据,确保数据的一致性。
创建读写锁:
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_rdlock
或 pthread_rwlock_wrlock
的线程都必须在不需要锁的时候调用 pthread_rwlock_unlock
。如果同一个线程多次调用 pthread_rwlock_rdlock
或 pthread_rwlock_wrlock
,就可能会导致死锁。此外,试图解锁一个已经被其他线程持有的锁,或者未被任何线程持有的锁,都是未定义的行为。
#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
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_sec
和 tv_nsec
,分别表示秒和纳秒。这个时间表示的是一个绝对时间,即从某个固定的时间点(如 Unix 纪元,1970 年 1 月 1 日)开始的时间。
需要注意的是,pthread_rwlock_timedrdlock
和 pthread_rwlock_timedwrlock
这两个函数在 POSIX 标准中是可选的,因此,一些系统可能不支持这两个函数。你应该检查你的系统文档,看看这些函数是否可用。