读写锁
读写锁也称为多读者单写者锁。读写锁把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。同一时间只能有一个线程可以占有写模式的读写锁,但是可以有多个线程同时占有读模式的读写锁。读写锁适合于对数据结构的读次数比写次数多得多的情况,因为读模式锁定时可以共享,写模式锁定时意味着独占。
读写锁通常是基于互斥锁和条件变量实现的。一个线程可以对一个读写锁进行多次读写锁定,同样必须有对应次数的解锁。
读写锁的主要操作包括:调用pthread_rwlock_init()初始化一个读写锁,写线程调用pthread_rwlock_wrlock()对读写锁写锁定,读线程调用pthread_rwlock_rdlock()对读写锁读锁定,当不需要使用此读写锁时调用pthread_rwlock_destroy()销毁读写锁。
读写锁控制块
每个读写锁对应一个读写锁控制块,包括对读写锁进行操作的一些信息。pthread_rwlock_t是pthread_rwlock数据结构的重定义,定义在pthread.h头文件里。在创建一个读写锁之前需要先定义一个pthread_rwlock_t类型的数据结构。
- struct pthread_rwlock
- {
- pthread_rwlockattr_t attr; /* 读写锁属性 */
- pthread_mutex_t rw_mutex; /* 互斥锁 */
- pthread_cond_t rw_condreaders; /* 条件变量,供读者线程使用 */
- pthread_cond_t rw_condwriters; /* 条件变量,供写者线程使用 */
- int rw_nwaitreaders; /* 读者线程等待计数 */
- int rw_nwaitwriters; /* 写者线程等待计数 */
- /* 读写锁值,值为0:未上锁,值为-1:被写者线程锁定,大于0值:被读者线程锁定数量 */
- int rw_refcount;
- };
- typedef struct pthread_rwlock pthread_rwlock_t; /* 类型重定义 */
读写锁初始化
函数原型
- int pthread_rwlock_init (pthread_rwlock_t *rwlock,
- const pthread_rwlockattr_t *attr);
- 参数 描述
- rwlock 读写锁句柄,不能为NULL
- attr 指向读写锁属性的指针,RT-Thread不使用此变量
函数返回
初始化成功返回0,参数无效返回EINVAL。
此函数会初始化一个rwlock读写锁。此函数使用默认值初始化读写锁控制块的信号量和条件变量,相关计数参数初始为0值。初始化后的读写锁处于未上锁状态。
还可以使用宏PTHREAD_RWLOCK_INITIALIZER来静态初始化读写锁,方法: pthread_rwlock_t mutex = PTHREAD_RWLOCK_INITIALIZER(结构体常量),等同于调用 pthread_rwlock_init()时attr指定为NULL。
attr一般设置NULL使用默认值即可,具体会在线程高级编程一章介绍。
销毁读写锁
函数原型
- int pthread_rwlock_destroy (pthread_rwlock_t *rwlock);
- 参数 描述
- rwlock 读写锁句柄,不能为NULL
函数返回
销毁成功返回0,参数无效返回EINVAL,读写锁目前正在被使用或者有线程等待该读写锁返回EBUSY,发生死锁返回EDEADLK。
此函数会销毁一个rwlock读写锁,对应的会销毁读写锁里的互斥锁和条件变量。销毁之后读写锁的属性及控制块参数将不在有效,但可以调用pthread_rwlock_init()或者静态方式重新初始化读写锁。
读写锁读锁定
阻塞方式对读写锁读锁定
函数原型
- int pthread_rwlock_rdlock (pthread_rwlock_t *rwlock);
- 参数 描述
- rwlock 读写锁句柄,不能为NULL
函数返回
锁定成功返回0,参数无效返回EINVAL,发生死锁返回EDEADLK。
读者线程可以调用此函数来对rwlock读写锁进行读锁定。如果读写锁没有被写锁定并且没有写者线程阻塞在该读写锁上,读写线程将成功获取该读写锁。如果读写锁已经被写锁定,读者线程将会阻塞,直到写锁定该读写锁的线程解锁。
非阻塞方式对读写锁读锁定
函数原型
- int pthread_rwlock_tryrdlock (pthread_rwlock_t *rwlock);
- 参数 描述
- rwlock 读写锁句柄,不能为NULL
函数返回
锁定成功返回0,参数无效返回EINVAL,发生死锁返回EDEADLK,读写锁目前被写锁定或者有写着线程阻塞在该读写锁上返回EBUSY。
此函数和pthread_rwlock_rdlock()函数的不同在于,如果读写锁已经被写锁定,读者线程不会被阻塞,而是返回一个错误码EBUSY。
指定阻塞时间对读写锁读锁定
函数原型
- int pthread_rwlock_timedrdlock (pthread_rwlock_t *rwlock,
- const struct timespec *abstime);
- 参数 描述
- rwlock 读写锁句柄,不能为NULL
- abstime 指定的等待时间,单位是操作系统时钟节拍(OS Tick)
函数返回
锁定成功返回0,参数无效返回EINVAL,发生死锁返回EDEADLK,超时返回ETIMEDOUT。
此函数和pthread_rwlock_rdlock()函数的不同在于,如果读写锁已经被写锁定,读者线程将会阻塞指定的abstime时长,超时后函数将返回错误码ETIMEDOUT,线程将会被唤醒进入就绪态。
读写锁写锁定
阻塞方式对读写锁写锁定
函数原型
- int pthread_rwlock_wrlock (pthread_rwlock_t *rwlock);
- 参数 描述
- rwlock 读写锁句柄,不能为NULL
函数返回
锁定成功返回0,参数无效返回EINVAL,发生死锁返回EDEADLK。
写者线程调用此函数对rwlock读写锁进行写锁定。写锁定读写锁类似互斥量,同一时刻只能有一个线程写锁定读写锁。如果没有线程锁定该读写锁,即读写锁值为0,调用此函数的写者线程将会写锁定读写锁,其他线程此时都不能获取读写锁,如果已经有线程锁定该读写锁,即读写锁值不为0,则写线程将被阻塞,直到读写锁解锁。
非阻塞方式写锁定读写锁
函数原型
- int pthread_rwlock_trywrlock (pthread_rwlock_t *rwlock);
- 参数 描述
- rwlock 读写锁句柄,不能为NULL
函数返回
锁定成功返回0,参数无效返回EINVAL,发生死锁返回EDEADLK,读写锁目前被写锁定或者有写着线程阻塞在该读写锁上返回EBUSY。
此函数和pthread_rwlock_wrlock()函数唯一的不同在于,如果已经有线程锁定该读写锁,即读写锁值不为0,则调用该函数的写者线程会直接返回一个错误代码,线程不会被阻塞。
指定阻塞时长写锁定读写锁
函数原型
- int pthread_rwlock_timedwrlock (pthread_rwlock_t *rwlock,
- const struct timespec *abstime);
- 参数 描述
- rwlock 读写锁句柄,不能为NULL
- abstime 指定的等待时间,单位是操作系统时钟节拍(OS Tick)
函数返回
锁定成功返回0,参数无效返回EINVAL,发生死锁返回EDEADLK,超时返回ETIMEDOUT。
此函数和pthread_rwlock_wrlock()函数唯一的不同在于,如果已经有线程锁定该读写锁,即读写锁值不为0,调用线程阻塞指定的abstime时长,超时后函数将返回错误码ETIMEDOUT,线程将会被唤醒进入就绪态。
读写锁解锁
函数原型
- int pthread_rwlock_unlock (pthread_rwlock_t *rwlock);
- 参数 描述
- rwlock 读写锁句柄,不能为NULL
函数返回
解锁成功返回0,参数无效返回EINVAL,死锁返回EDEADLK。
此函数可以对rwlock读写锁解锁。线程对同一个读写锁加锁多次,必须有同样次数的解锁,若解锁后有多个线程等待对读写锁进行锁定,系统将按照先进先出的规则激活等待的线程。
读写锁示例代码
这个程序有2个读者线程,一个写着线程。2个读者线程先对读写锁读锁定,之后休眠2秒,这是其他的读者线程还是可以对该读写锁读锁定,然后读取共享数据。
- #include <pthread.h>
- #include <sched.h>
- #include <stdio.h>
- /* 线程控制块 */
- static pthread_t reader1;
- static pthread_t reader2;
- static pthread_t writer1;
- /* 共享数据book */
- static int book = 0;
- /* 读写锁 */
- static pthread_rwlock_t rwlock;
- /* 函数结果检查 */
- static void check_result(char* str,int result)
- {
- if (0 == result)
- {
- printf("%s successfully!\n",str);
- }
- else
- {
- printf("%s failed! error code is %d\n",str,result);
- }
- }
- /*线程入口*/
- static void* reader1_entry(void* parameter)
- {
- while (1)
- {
- pthread_rwlock_rdlock(&rwlock); /* 尝试读锁定该读写锁 */
- printf("reader1 read book value is %d\n",book);
- sleep(2); /* 线程休眠2秒,切换到其他线程运行 */
- pthread_rwlock_unlock(&rwlock); /* 线程运行后对读写锁解锁 */
- }
- }
- static void* reader2_entry(void* parameter)
- {
- while (1)
- {
- pthread_rwlock_rdlock(&rwlock); /* 尝试读锁定该读写锁 */
- printf("reader2 read book value is %d\n",book);
- sleep(2); /* 线程休眠2秒,切换到其他线程运行 */
- pthread_rwlock_unlock(&rwlock); /* 线程运行后对读写锁解锁 */
- }
- }
- static void* writer1_entry(void* parameter)
- {
- while (1)
- {
- pthread_rwlock_wrlock(&rwlock); /* 尝试写锁定该读写锁 */
- book++;
- printf("writer1 write book value is %d\n",book);
- pthread_rwlock_unlock(&rwlock); /* 对读写锁解锁 */
- sleep(2); /* 线程休眠2秒,切换到其他线程运行 */
- }
- }
- /* 用户应用入口 */
- int rt_application_init()
- {
- int result;
- /* 默认属性初始化读写锁 */
- pthread_rwlock_init(&rwlock,NULL);
- /*创建reader1线程,线程入口是reader1_entry, 线程属性为默认值,入口参数为NULL*/
- result = pthread_create(&reader1,NULL,reader1_entry,NULL);
- check_result("reader1 created",result);
- /*创建reader2线程,线程入口是reader2_entry, 线程属性为默认值,入口参数为NULL*/
- result = pthread_create(&reader2,NULL,reader2_entry,NULL);
- check_result("reader2 created",result);
- /*创建writer1线程,线程入口是writer1_entry, 线程属性为,入口参数为NULL*/
- result = pthread_create(&writer1,NULL,writer1_entry,NULL);
- check_result("writer1 created",result);
- return 0;
- }