读写锁

读写锁也称为多读者单写者锁。读写锁把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。同一时间只能有一个线程可以占有写模式的读写锁,但是可以有多个线程同时占有读模式的读写锁。读写锁适合于对数据结构的读次数比写次数多得多的情况,因为读模式锁定时可以共享,写模式锁定时意味着独占。

读写锁通常是基于互斥锁和条件变量实现的。一个线程可以对一个读写锁进行多次读写锁定,同样必须有对应次数的解锁。

读写锁的主要操作包括:调用pthread_rwlock_init()初始化一个读写锁,写线程调用pthread_rwlock_wrlock()对读写锁写锁定,读线程调用pthread_rwlock_rdlock()对读写锁读锁定,当不需要使用此读写锁时调用pthread_rwlock_destroy()销毁读写锁。

读写锁控制块

每个读写锁对应一个读写锁控制块,包括对读写锁进行操作的一些信息。pthread_rwlock_t是pthread_rwlock数据结构的重定义,定义在pthread.h头文件里。在创建一个读写锁之前需要先定义一个pthread_rwlock_t类型的数据结构。

  1. struct pthread_rwlock
  2. {
  3. pthread_rwlockattr_t attr; /* 读写锁属性 */
  4. pthread_mutex_t rw_mutex; /* 互斥锁 */
  5. pthread_cond_t rw_condreaders; /* 条件变量,供读者线程使用 */
  6. pthread_cond_t rw_condwriters; /* 条件变量,供写者线程使用 */
  7. int rw_nwaitreaders; /* 读者线程等待计数 */
  8. int rw_nwaitwriters; /* 写者线程等待计数 */
  9. /* 读写锁值,值为0:未上锁,值为-1:被写者线程锁定,大于0值:被读者线程锁定数量 */
  10. int rw_refcount;
  11. };
  12. typedef struct pthread_rwlock pthread_rwlock_t; /* 类型重定义 */

读写锁初始化

函数原型

  1. int pthread_rwlock_init (pthread_rwlock_t *rwlock,
  2. const pthread_rwlockattr_t *attr);

  1. 参数 描述

  1. rwlock 读写锁句柄,不能为NULL
  2.  
  3. attr 指向读写锁属性的指针,RT-Thread不使用此变量

函数返回

初始化成功返回0,参数无效返回EINVAL。

此函数会初始化一个rwlock读写锁。此函数使用默认值初始化读写锁控制块的信号量和条件变量,相关计数参数初始为0值。初始化后的读写锁处于未上锁状态。

还可以使用宏PTHREAD_RWLOCK_INITIALIZER来静态初始化读写锁,方法: pthread_rwlock_t mutex = PTHREAD_RWLOCK_INITIALIZER(结构体常量),等同于调用 pthread_rwlock_init()时attr指定为NULL。

attr一般设置NULL使用默认值即可,具体会在线程高级编程一章介绍。

销毁读写锁

函数原型

  1. int pthread_rwlock_destroy (pthread_rwlock_t *rwlock);

  1. 参数 描述

  1. rwlock 读写锁句柄,不能为NULL

函数返回

销毁成功返回0,参数无效返回EINVAL,读写锁目前正在被使用或者有线程等待该读写锁返回EBUSY,发生死锁返回EDEADLK。

此函数会销毁一个rwlock读写锁,对应的会销毁读写锁里的互斥锁和条件变量。销毁之后读写锁的属性及控制块参数将不在有效,但可以调用pthread_rwlock_init()或者静态方式重新初始化读写锁。

读写锁读锁定

阻塞方式对读写锁读锁定

函数原型

  1. int pthread_rwlock_rdlock (pthread_rwlock_t *rwlock);

  1. 参数 描述

  1. rwlock 读写锁句柄,不能为NULL

函数返回

锁定成功返回0,参数无效返回EINVAL,发生死锁返回EDEADLK。

读者线程可以调用此函数来对rwlock读写锁进行读锁定。如果读写锁没有被写锁定并且没有写者线程阻塞在该读写锁上,读写线程将成功获取该读写锁。如果读写锁已经被写锁定,读者线程将会阻塞,直到写锁定该读写锁的线程解锁。

非阻塞方式对读写锁读锁定

函数原型

  1. int pthread_rwlock_tryrdlock (pthread_rwlock_t *rwlock);

  1. 参数 描述

  1. rwlock 读写锁句柄,不能为NULL

函数返回

锁定成功返回0,参数无效返回EINVAL,发生死锁返回EDEADLK,读写锁目前被写锁定或者有写着线程阻塞在该读写锁上返回EBUSY。

此函数和pthread_rwlock_rdlock()函数的不同在于,如果读写锁已经被写锁定,读者线程不会被阻塞,而是返回一个错误码EBUSY。

指定阻塞时间对读写锁读锁定

函数原型

  1. int pthread_rwlock_timedrdlock (pthread_rwlock_t *rwlock,
  2. const struct timespec *abstime);

  1. 参数 描述

  1. rwlock 读写锁句柄,不能为NULL
  2.  
  3. abstime 指定的等待时间,单位是操作系统时钟节拍(OS Tick

函数返回

锁定成功返回0,参数无效返回EINVAL,发生死锁返回EDEADLK,超时返回ETIMEDOUT。

此函数和pthread_rwlock_rdlock()函数的不同在于,如果读写锁已经被写锁定,读者线程将会阻塞指定的abstime时长,超时后函数将返回错误码ETIMEDOUT,线程将会被唤醒进入就绪态。

读写锁写锁定

阻塞方式对读写锁写锁定

函数原型

  1. int pthread_rwlock_wrlock (pthread_rwlock_t *rwlock);

  1. 参数 描述

  1. rwlock 读写锁句柄,不能为NULL

函数返回

锁定成功返回0,参数无效返回EINVAL,发生死锁返回EDEADLK。

写者线程调用此函数对rwlock读写锁进行写锁定。写锁定读写锁类似互斥量,同一时刻只能有一个线程写锁定读写锁。如果没有线程锁定该读写锁,即读写锁值为0,调用此函数的写者线程将会写锁定读写锁,其他线程此时都不能获取读写锁,如果已经有线程锁定该读写锁,即读写锁值不为0,则写线程将被阻塞,直到读写锁解锁。

非阻塞方式写锁定读写锁

函数原型

  1. int pthread_rwlock_trywrlock (pthread_rwlock_t *rwlock);

  1. 参数 描述

  1. rwlock 读写锁句柄,不能为NULL

函数返回

锁定成功返回0,参数无效返回EINVAL,发生死锁返回EDEADLK,读写锁目前被写锁定或者有写着线程阻塞在该读写锁上返回EBUSY。

此函数和pthread_rwlock_wrlock()函数唯一的不同在于,如果已经有线程锁定该读写锁,即读写锁值不为0,则调用该函数的写者线程会直接返回一个错误代码,线程不会被阻塞。

指定阻塞时长写锁定读写锁

函数原型

  1. int pthread_rwlock_timedwrlock (pthread_rwlock_t *rwlock,
  2. const struct timespec *abstime);

  1. 参数 描述

  1. rwlock 读写锁句柄,不能为NULL
  2. abstime 指定的等待时间,单位是操作系统时钟节拍(OS Tick

函数返回

锁定成功返回0,参数无效返回EINVAL,发生死锁返回EDEADLK,超时返回ETIMEDOUT。

此函数和pthread_rwlock_wrlock()函数唯一的不同在于,如果已经有线程锁定该读写锁,即读写锁值不为0,调用线程阻塞指定的abstime时长,超时后函数将返回错误码ETIMEDOUT,线程将会被唤醒进入就绪态。

读写锁解锁

函数原型

  1. int pthread_rwlock_unlock (pthread_rwlock_t *rwlock);

  1. 参数 描述

  1. rwlock 读写锁句柄,不能为NULL

函数返回

解锁成功返回0,参数无效返回EINVAL,死锁返回EDEADLK。

此函数可以对rwlock读写锁解锁。线程对同一个读写锁加锁多次,必须有同样次数的解锁,若解锁后有多个线程等待对读写锁进行锁定,系统将按照先进先出的规则激活等待的线程。

读写锁示例代码

这个程序有2个读者线程,一个写着线程。2个读者线程先对读写锁读锁定,之后休眠2秒,这是其他的读者线程还是可以对该读写锁读锁定,然后读取共享数据。

  1. #include <pthread.h>
  2. #include <sched.h>
  3. #include <stdio.h>
  4.  
  5. /* 线程控制块 */
  6. static pthread_t reader1;
  7. static pthread_t reader2;
  8. static pthread_t writer1;
  9. /* 共享数据book */
  10. static int book = 0;
  11. /* 读写锁 */
  12. static pthread_rwlock_t rwlock;
  13. /* 函数结果检查 */
  14. static void check_result(char* str,int result)
  15. {
  16. if (0 == result)
  17. {
  18. printf("%s successfully!\n",str);
  19. }
  20. else
  21. {
  22. printf("%s failed! error code is %d\n",str,result);
  23. }
  24. }
  25. /*线程入口*/
  26. static void* reader1_entry(void* parameter)
  27. {
  28. while (1)
  29. {
  30.  
  31. pthread_rwlock_rdlock(&rwlock); /* 尝试读锁定该读写锁 */
  32.  
  33. printf("reader1 read book value is %d\n",book);
  34. sleep(2); /* 线程休眠2秒,切换到其他线程运行 */
  35.  
  36. pthread_rwlock_unlock(&rwlock); /* 线程运行后对读写锁解锁 */
  37. }
  38. }
  39. static void* reader2_entry(void* parameter)
  40. {
  41. while (1)
  42. {
  43. pthread_rwlock_rdlock(&rwlock); /* 尝试读锁定该读写锁 */
  44.  
  45. printf("reader2 read book value is %d\n",book);
  46. sleep(2); /* 线程休眠2秒,切换到其他线程运行 */
  47.  
  48. pthread_rwlock_unlock(&rwlock); /* 线程运行后对读写锁解锁 */
  49. }
  50. }
  51. static void* writer1_entry(void* parameter)
  52. {
  53. while (1)
  54. {
  55. pthread_rwlock_wrlock(&rwlock); /* 尝试写锁定该读写锁 */
  56.  
  57. book++;
  58. printf("writer1 write book value is %d\n",book);
  59.  
  60. pthread_rwlock_unlock(&rwlock); /* 对读写锁解锁 */
  61.  
  62. sleep(2); /* 线程休眠2秒,切换到其他线程运行 */
  63. }
  64. }
  65. /* 用户应用入口 */
  66. int rt_application_init()
  67. {
  68. int result;
  69. /* 默认属性初始化读写锁 */
  70. pthread_rwlock_init(&rwlock,NULL);
  71.  
  72. /*创建reader1线程,线程入口是reader1_entry, 线程属性为默认值,入口参数为NULL*/
  73. result = pthread_create(&reader1,NULL,reader1_entry,NULL);
  74. check_result("reader1 created",result);
  75.  
  76. /*创建reader2线程,线程入口是reader2_entry, 线程属性为默认值,入口参数为NULL*/
  77. result = pthread_create(&reader2,NULL,reader2_entry,NULL);
  78. check_result("reader2 created",result);
  79.  
  80. /*创建writer1线程,线程入口是writer1_entry, 线程属性为,入口参数为NULL*/
  81. result = pthread_create(&writer1,NULL,writer1_entry,NULL);
  82. check_result("writer1 created",result);
  83.  
  84. return 0;
  85. }