条件变量

条件变量其实就是一个信号量,用于线程间同步。条件变量用来阻塞一个线程,当条件满足时向阻塞的线程发送一个条件,阻塞线程就被唤醒,条件变量需要和互斥锁配合使用,互斥锁用来保护共享数据。

条件变量可以用来通知共享数据状态。比如一个处理共享资源队列的线程发现队列为空,则此线程只能等待,直到有一个节点被添加到队列中,添加后在发一个条件变量信号激活等待线程。

条件变量的主要操作包括:调用pthread_cond_init()对条件变量初始化,调用 pthread_cond_destroy()销毁一个条件变量,调用pthread_cond_wait()等待一个条件变量,调用pthread_cond_signal()发送一个条件变量。

条件变量控制块

每个条件变量对应一个条件变量控制块,包括对条件变量进行操作的一些信息。初始化一个条件变量前需要先定义一个pthread_cond_t条件变量控制块。pthread_cond_t是pthread_cond结构体类型的重定义,定义在pthread.h头文件里。

  1. struct pthread_cond
  2. {
  3. pthread_condattr_t attr; /* 条件变量属性 */
  4. struct rt_semaphore sem; /* RT-Thread信号量控制块 */
  5. };
  6. typedef struct pthread_cond pthread_cond_t;
  7.  
  8. rt_semaphoreRT-Thread内核里定义的一个数据结构,是信号量控制块,
  9. 定义在rtdef.h头文件里
  10.  
  11. struct rt_semaphore
  12. {
  13. struct rt_ipc_object parent;/*继承自ipc_object类*/
  14. rt_uint16_t value; /* 信号量的值 */
  15. };

初始化条件变量

函数原型

  1. int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);

  1. 参数 描述

  1. cond 条件变量句柄,不能为NULL
  2.  
  3. attr 指向条件变量属性的指针,若为NULL则使用默认属性值

函数返回

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

此函数会初始化cond条件变量,并根据attr指向的条件变量属性设置其属性,此函数是对rt_sem_init()函数的一个封装,基于信号量实现。初始化成功后条件变量处于不可用状态。

还可以用宏PTHREAD_COND_INITIALIZER静态初始化一个条件变量,方法: pthread_cond_t cond = PTHREAD_COND_INITIALIZER(结构体常量),等同于调用 pthread_cond_init()时attr指定为NULL。

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

销毁条件变量

函数原型

  1. int pthread_cond_destroy(pthread_cond_t *cond);

  1. 参数 描述

  1. cond 条件变量句柄,不能为NULL

函数返回

销毁成功返回0,参数无效返回EINVAL,条件变量正在被使用返回EBUSY。

此函数会销毁cond条件变量,销毁后cond处于未初始化状态。销毁之后条件变量的属性及控制块参数将不在有效,但可以调用pthread_cond_init()或者静态方式重新初始化。

销毁条件变量前需要确定没有线程被阻塞在该条件变量上,也不会等待获取、发信号或者广播。

阻塞方式获取条件变量

函数原型

  1. int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

  1. 参数 描述

  1. cond 条件变量句柄,不能为NULL
  2.  
  3. mutex 指向互斥锁控制块的指针,不能为NULL

函数返回

成功获取条件变量返回0,参数无效返回EINVAL。

此函数会以阻塞方式获取cond条件变量。线程等待条件变量前需要先将mutex互斥锁锁住,此函数首先判断条件变量是否可用,如果不可用则初始化一个条件变量,之后解锁mutex互斥锁,然后尝试获取一个信号量,当信号量值大于零时,表明信号量可用,线程将获得信号量,也就获得该条件变量,相应的信号量值会减1。如果信号量的值等于零,表明信号量不可用,线程将阻塞直到信号量可用,之后将对mutex互斥锁再次上锁。

指定阻塞时间获取条件变量

函数原型

  1. int pthread_cond_timedwait(pthread_cond_t *cond,
  2. pthread_mutex_t *mutex,
  3. const struct timespec *abstime);

  1. 参数 描述

  1. cond 条件变量句柄,不能为NULL
  2.  
  3. mutex 指向互斥锁控制块的指针,不能为NULL
  4.  
  5. abstime 指定的等待时间,单位是操作系统时钟节拍(OS Tick

函数返回

成功获取条件变量返回0,参数无效返回EINVAL,超时返回ETIMEDOUT。

此函数和pthread_cond_wait()函数唯一的差别在于,如果条件变量不可用,线程将被阻塞abstime时长,超时后函数将直接返回ETIMEDOUT错误码,线程将会被唤醒进入就绪态。

发送满足条件信号量

函数原型

  1. int pthread_cond_signal(pthread_cond_t *cond);

  1. 参数 描述

  1. cond 条件变量句柄,不能为NULL

函数返回

只返回0,总是成功。

此函数会发送一个信号且只唤醒一个等待cond条件变量的线程,是对rt_sem_release()函数的封装,也就是发送一个信号量。当信号量的值等于零,并且有线程等待这个信号量时,将唤醒等待在该信号量线程队列中的第一个线程,由它获取信号量。否则将把信号量的值加1。

广播

函数原型

  1. int pthread_cond_broadcast(pthread_cond_t *cond);

  1. 参数 描述

  1. cond 条件变量句柄,不能为NULL

函数返回

广播成功返回0,参数无效返回EINVAL。

调用此函数将唤醒所有等待cond条件变量的线程。

条件变量示例代码

这个程序是一个生产者消费者模型,有一个生产者线程,一个消费者线程,它们拥有相同的优先级。生产者每隔2秒会生产一个数字,放到head指向的链表里面,之后调用pthread_cond_signal()给消费者线程发信号,通知消费者线程链表里面有数据。消费者线程会调用pthread_cond_wait()等待生产者线程发送信号。

  1. #include <pthread.h>
  2. #include <unistd.h>
  3. #include <stdio.h>
  4. #include <stdlib.h>
  5.  
  6. /* 静态方式初始化一个互斥锁和一个条件变量 */
  7. static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
  8. static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
  9.  
  10. /* 指向线程控制块的指针 */
  11. static pthread_t tid1;
  12. static pthread_t tid2;
  13.  
  14. /* 函数返回值检查 */
  15. static void check_result(char* str,int result)
  16. {
  17. if (0 == result)
  18. {
  19. printf("%s successfully!\n",str);
  20. }
  21. else
  22. {
  23. printf("%s failed! error code is %d\n",str,result);
  24. }
  25. }
  26.  
  27. /* 生产者生产的结构体数据,存放在链表里 */
  28. struct node
  29. {
  30. int n_number;
  31. struct node* n_next;
  32. };
  33. struct node* head = NULL; /* 链表头,是共享资源 */
  34.  
  35. /* 消费者线程入口函数 */
  36. static void* consumer(void* parameter)
  37. {
  38. struct node* p_node = NULL;
  39.  
  40. pthread_mutex_lock(&mutex); /* 对互斥锁上锁 */
  41.  
  42. while (1)
  43. {
  44. while (head == NULL) /* 判断链表里是否有元素 */
  45. {
  46. pthread_cond_wait(&cond,&mutex); /* 尝试获取条件变量 */
  47. }
  48. /*
  49. pthread_cond_wait()会先对mutex解锁,
  50. 然后阻塞在等待队列,直到获取条件变量被唤醒,
  51. 被唤醒后,该线程会再次对mutex上锁,成功进入临界区。
  52. */
  53.  
  54. p_node = head; /* 拿到资源 */
  55. head = head->n_next; /* 头指针指向下一个资源 */
  56. /* 打印输出 */
  57. printf("consume %d\n",p_node->n_number);
  58.  
  59. free(p_node); /* 拿到资源后释放节点占用的内存 */
  60. }
  61. pthread_mutex_unlock(&mutex); /* 释放互斥锁 */
  62. return 0;
  63. }
  64. /* 生产者线程入口函数 */
  65. static void* product(void* patameter)
  66. {
  67. int count = 0;
  68. struct node *p_node;
  69.  
  70. while(1)
  71. {
  72. /* 动态分配一块结构体内存 */
  73. p_node = (struct node*)malloc(sizeof(struct node));
  74. if (p_node != NULL)
  75. {
  76. p_node->n_number = count++;
  77. pthread_mutex_lock(&mutex); /* 需要操作head这个临界资源,先加锁 */
  78.  
  79. p_node->n_next = head;
  80. head = p_node; /* 往链表头插入数据 */
  81.  
  82. pthread_mutex_unlock(&mutex); /* 解锁 */
  83. printf("produce %d\n",p_node->n_number);
  84.  
  85. pthread_cond_signal(&cond); /* 发信号唤醒一个线程 */
  86.  
  87. sleep(2); /* 休眠2秒 */
  88. }
  89. else
  90. {
  91. printf("product malloc node failed!\n");
  92. break;
  93. }
  94. }
  95. }
  96.  
  97. int rt_application_init()
  98. {
  99. int result;
  100.  
  101. /* 创建生产者线程,属性为默认值,入口函数是product,入口函数参数为NULL*/
  102. result = pthread_create(&tid1,NULL,product,NULL);
  103. check_result("product thread created ",result);
  104.  
  105. /* 创建消费者线程,属性为默认值,入口函数是consumer,入口函数参数是NULL */
  106. result = pthread_create(&tid2,NULL,consumer,NULL);
  107. check_result("consumer thread created ",result);
  108.  
  109. return 0;
  110. }