• 线程同步之条件变量


    1 基本概念

    条件变量本身不是锁!但它也可以造成线程阻塞。通常与互斥锁配合使用。给多线程提供一个会合的场所。

    条件变量是用来等待线程而不是上锁的,条件变量通常和互斥锁一起使用。条件变量之所以要和互斥锁一起使用,主要是因为互斥锁的一个明显的特点就是它只有两种状态:锁定和非锁定,而条件变量可以通过允许线程阻塞和等待另一个线程发送信号来弥补互斥锁的不足,所以互斥锁和条件变量通常一起使用。

           当条件满足的时候,线程通常解锁并等待该条件发生变化,一旦另一个线程修改了环境变量,就会通知相应的环境变量唤醒一个或者多个被这个条件变量阻塞的线程。这些被唤醒的线程将重新上锁,并测试条件是否满足。一般来说条件变量被用于线程间的同步;当条件不满足的时候,允许其中的一个执行流挂起和等待

    参考:条件变量详细解说_清风徐来Groot的博客-CSDN博客_条件变量

    2 为什么使用条件变量

    线程抢占互斥锁时,线程A抢到了互斥锁,但是条件不满足,线程A就会让出互斥锁让给其他线程,然后等待其他线程唤醒他;一旦条件满足,线程就可以被唤醒,并且拿互斥锁去访问共享区。经过这中设计能让进程运行更稳定。

    3 函数使用

    3.1 pthread_cond_init函数

    作用:初始化一个条件变量

    1. int pthread_cond_init(pthread_cond_t *restrict cond,
    2. const pthread_condattr_t *restrict attr);
    3. // 参 2:attr 表条件变量属性,通常为默认值,传 NULL 即可

    静态初始化和动态初始化

    1. // 1 静态初始化
    2. pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
    3. // 2 动态初始化
    4. int pthread_cond_init(pthread_cond_t *restrict cond,
    5. const pthread_condattr_t *restrict attr);

    3.2 pthread_cond_destroy函数

    作用:销毁一个条件变量

    int pthread_cond_destroy(pthread_cond_t *cond);

    3.3 pthread_cond_wait函数(重点)

    作用:(非常重要 三点)

    1 阻塞等待条件变量 cond(参 1)满足


    2 释放已掌握的互斥锁(解锁互斥量)相当于 pthread_mutex_unlock(&mutex);


    3 当被唤醒,pthread_cond_wait 函数返回时,解除阻塞并重新申请获取互斥锁
    pthread_mutex_lock(&mutex);

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

    1.2.两步为一个原子操作。

    3.4 pthread_cond_timedwait 函数

    作用:限时等待一个条件变量

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

    3.5 pthread_cond_signal 函数

    作用:唤醒至少一个阻塞在条件变量上的线程

    int pthread_cond_signal(pthread_cond_t *cond);

    3.6 pthread_cond_broadcast 函数

    作用:唤醒全部阻塞在条件变量上的线程

    int pthread_cond_broadcast(pthread_cond_t *cond);

    4 生产者消费者模型

    步骤:

    生产者:

    (1)生产数据;

    (2)加锁pthread_mutex_lock(&mutex);

    (3)将数据放置到公共区域;

    (4)解锁pthread_mutex_unlock(&mutex);

    (5)通知阻塞在条件变量上的线程pthread_cond_signal()、pthread_cond_brocadcast();

    (6)循环产生后序数据。

    消费者:

    (1)创建锁pthread_mutex_t mutex;

    (2)初始化锁pthread_mutex_init(mutex, NULL);

    (3)加锁pthread_mutex_lock(&mutex);

    (4)等待条件满足

            pthread_cond_wait(&cond, &mutex);

            阻塞等待条件变量;

            解锁;

            ----10s;

            加锁;

    (5)访问共享数据;

    (6)解锁、释放条件变量,释放锁。

    代码

    1. #include <stdio.h>
    2. #include <stdlib.h>
    3. #include <string.h>
    4. #include <unistd.h>
    5. #include <pthread.h>
    6. // 借助条件变量模拟[生产者-消费者]问题
    7. // 链表作为共享数据,需要被互斥量保护
    8. struct Msg
    9. {
    10. int val;
    11. struct Msg *next;
    12. };
    13. // 头节点
    14. struct Msg *head;
    15. // 静态初始化互斥量和条件变量
    16. pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
    17. pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
    18. // 生产者线程
    19. void *producer(void *arg)
    20. {
    21. struct Msg *mp;
    22. while (1)
    23. {
    24. // 申请空间
    25. mp = malloc(sizeof(struct Msg));
    26. // 模拟生产一个产品
    27. mp->val = rand() % 1000 + 1;
    28. printf("---produce----------------------:%d\n", mp->val);
    29. // 加锁
    30. int res = pthread_mutex_lock(&lock);
    31. if (res != 0)
    32. {
    33. fprintf(stderr, "pthread_mutex_lock producer error:%s\n", strerror(res));
    34. exit(1);
    35. }
    36. // 头插法
    37. mp->next = head;
    38. head = mp;
    39. // 解锁
    40. res = pthread_mutex_unlock(&lock);
    41. if (res != 0)
    42. {
    43. fprintf(stderr, "pthread_mutex_unlock producer error:%s\n", strerror(res));
    44. exit(1);
    45. }
    46. // 将等待在条件变量上的一个线程唤醒
    47. res = pthread_cond_signal(&has_product);
    48. if (res != 0)
    49. {
    50. fprintf(stderr, "pthread_cond_signal producer error:%s\n", strerror(res));
    51. exit(1);
    52. }
    53. sleep(rand() % 5);
    54. }
    55. }
    56. // 消费者线程
    57. void *consumer(void *arg)
    58. {
    59. struct Msg *mp;
    60. while (1)
    61. {
    62. // 加锁
    63. int res = pthread_mutex_lock(&lock);
    64. if (res != 0)
    65. {
    66. fprintf(stderr, "pthread_mutex_lock consumer error:%s\n", strerror(res));
    67. exit(1);
    68. }
    69. // 头节点为空,说明没有节点
    70. while (head == NULL)
    71. {
    72. // 消费者在阻塞
    73. res = pthread_cond_wait(&has_product, &lock);
    74. if (res != 0)
    75. {
    76. fprintf(stderr, "pthread_cond_wait consumer error:%s\n", strerror(res));
    77. exit(1);
    78. }
    79. }
    80. // 模拟消费掉一个产品
    81. mp = head;
    82. head = head->next;
    83. // 解锁
    84. res = pthread_mutex_unlock(&lock);
    85. if (res != 0)
    86. {
    87. fprintf(stderr, "pthread_mutex_unlock consumer error:%s\n", strerror(res));
    88. exit(1);
    89. }
    90. printf("==Consumer:%lu====%d\n", pthread_self(), mp->val);
    91. // 释放
    92. free(mp);
    93. sleep(rand() % 5);
    94. }
    95. }
    96. int main(int argc, char **argv)
    97. {
    98. // 创建生产者线程和消费者线程
    99. pthread_t pid, cid;
    100. srand(time(NULL));
    101. // 创建生产者线程
    102. int res = pthread_create(&pid, NULL, producer, NULL);
    103. if (res != 0)
    104. {
    105. fprintf(stderr, "pthread_create producer error:%s\n", strerror(res));
    106. exit(1);
    107. }
    108. // 创建消费者线程
    109. res = pthread_create(&cid, NULL, consumer, NULL);
    110. if (res != 0)
    111. {
    112. fprintf(stderr, "pthread_create consumer error:%s\n", strerror(res));
    113. exit(1);
    114. }
    115. // 回收生产者线程
    116. res = pthread_join(pid, NULL);
    117. if (res != 0)
    118. {
    119. fprintf(stderr, "pthread_join producer error:%s\n", strerror(res));
    120. exit(1);
    121. }
    122. // 回收消费者线程
    123. res = pthread_join(cid, NULL);
    124. if (res != 0)
    125. {
    126. fprintf(stderr, "pthread_join consumer error:%s\n", strerror(res));
    127. exit(1);
    128. }
    129. return 0;
    130. }

     执行

    5 多个消费者

    生产者休息时间短一些,其他都一样

    1. #include <stdio.h>
    2. #include <stdlib.h>
    3. #include <string.h>
    4. #include <unistd.h>
    5. #include <pthread.h>
    6. // 借助条件变量模拟[生产者-消费者]问题
    7. // 链表作为共享数据,需要被互斥量保护
    8. struct Msg
    9. {
    10. int val;
    11. struct Msg *next;
    12. };
    13. // 头节点
    14. struct Msg *head;
    15. // 静态初始化互斥量和条件变量
    16. pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
    17. pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
    18. // 生产者线程
    19. void *producer(void *arg)
    20. {
    21. struct Msg *mp;
    22. while (1)
    23. {
    24. // 申请空间
    25. mp = malloc(sizeof(struct Msg));
    26. // 模拟生产一个产品
    27. mp->val = rand() % 1000 + 1;
    28. printf("---produce----------------------:%d\n", mp->val);
    29. // 加锁
    30. int res = pthread_mutex_lock(&lock);
    31. if (res != 0)
    32. {
    33. fprintf(stderr, "pthread_mutex_lock producer error:%s\n", strerror(res));
    34. exit(1);
    35. }
    36. // 头插法
    37. mp->next = head;
    38. head = mp;
    39. // 解锁
    40. res = pthread_mutex_unlock(&lock);
    41. if (res != 0)
    42. {
    43. fprintf(stderr, "pthread_mutex_unlock producer error:%s\n", strerror(res));
    44. exit(1);
    45. }
    46. // 将等待在条件变量上的一个线程唤醒
    47. res = pthread_cond_signal(&has_product);
    48. if (res != 0)
    49. {
    50. fprintf(stderr, "pthread_cond_signal producer error:%s\n", strerror(res));
    51. exit(1);
    52. }
    53. sleep(rand() % 3);
    54. }
    55. }
    56. // 消费者线程
    57. void *consumer(void *arg)
    58. {
    59. struct Msg *mp;
    60. while (1)
    61. {
    62. // 加锁
    63. int res = pthread_mutex_lock(&lock);
    64. if (res != 0)
    65. {
    66. fprintf(stderr, "pthread_mutex_lock consumer error:%s\n", strerror(res));
    67. exit(1);
    68. }
    69. // 头节点为空,说明没有节点
    70. while (head == NULL)
    71. {
    72. // 消费者在阻塞
    73. res = pthread_cond_wait(&has_product, &lock);
    74. if (res != 0)
    75. {
    76. fprintf(stderr, "pthread_cond_wait consumer error:%s\n", strerror(res));
    77. exit(1);
    78. }
    79. }
    80. // 模拟消费掉一个产品
    81. mp = head;
    82. head = head->next;
    83. // 解锁
    84. res = pthread_mutex_unlock(&lock);
    85. if (res != 0)
    86. {
    87. fprintf(stderr, "pthread_mutex_unlock consumer error:%s\n", strerror(res));
    88. exit(1);
    89. }
    90. printf("==Consumer:%lu====%d\n", pthread_self(), mp->val);
    91. // 释放
    92. free(mp);
    93. sleep(rand() % 5);
    94. }
    95. }
    96. int main(int argc, char **argv)
    97. {
    98. // 创建生产者线程和消费者线程
    99. pthread_t pid, cid;
    100. srand(time(NULL));
    101. // 创建生产者线程
    102. int res = pthread_create(&pid, NULL, producer, NULL);
    103. if (res != 0)
    104. {
    105. fprintf(stderr, "pthread_create producer error:%s\n", strerror(res));
    106. exit(1);
    107. }
    108. /***********3个消费者****************/
    109. // 创建消费者线程
    110. res = pthread_create(&cid, NULL, consumer, NULL);
    111. if (res != 0)
    112. {
    113. fprintf(stderr, "pthread_create consumer error:%s\n", strerror(res));
    114. exit(1);
    115. }
    116. // 创建消费者线程
    117. res = pthread_create(&cid, NULL, consumer, NULL);
    118. if (res != 0)
    119. {
    120. fprintf(stderr, "pthread_create consumer error:%s\n", strerror(res));
    121. exit(1);
    122. }
    123. // 创建消费者线程
    124. res = pthread_create(&cid, NULL, consumer, NULL);
    125. if (res != 0)
    126. {
    127. fprintf(stderr, "pthread_create consumer error:%s\n", strerror(res));
    128. exit(1);
    129. }
    130. // 回收生产者线程
    131. res = pthread_join(pid, NULL);
    132. if (res != 0)
    133. {
    134. fprintf(stderr, "pthread_join producer error:%s\n", strerror(res));
    135. exit(1);
    136. }
    137. // 回收消费者线程
    138. res = pthread_join(cid, NULL);
    139. if (res != 0)
    140. {
    141. fprintf(stderr, "pthread_join consumer error:%s\n", strerror(res));
    142. exit(1);
    143. }
    144. return 0;
    145. }

    执行

    6 条件变量的优点

    相较于 mutex 而言,条件变量可以减少竞争。
    如直接使用 mutex,除了生产者、消费者之间要竞争互斥量以外,消费者之间也需要竞争互斥量,但如果汇聚(链表)中没有数据,消费者之间竞争互斥锁是无意义的。有了条件变量机制以后,只有生产者完成生产,才会引起消费者之间的竞争。提高了程序效率。

  • 相关阅读:
    Android 虚拟机
    【REGEXP】【正则的使用】【正则的使用】
    (论文阅读32/100)Flowing convnets for human pose estimation in videos
    EasyX图形库的下载安装与Dev-C++配置
    分布式数据库系统期末复习
    Oracle对临时表空间管理
    前后端交互实例(javaweb05)
    重装系统后电脑图片显示不出来怎么办
    循环神经网络
    JetBrains PyCharm Pro 2024 for Mac:智能Python开发的首选工具
  • 原文地址:https://blog.csdn.net/Zhouzi_heng/article/details/125456801