• 线程同步之信号量


    1 基本概念

    • 简单的说就是进化版的互斥锁(1~N)计数器
    • 记录当前可利用的资源数,当资源数量<=0时会阻塞,当资源数量>时才开始进行操作,另外信号量的操作均为原子操作。

    由于互斥锁的粒度比较大,如果我们希望在多个线程间对某一对象的部分数据进行共享,使用互斥锁是没有办法实现的,只能将整个数据对象锁住。这样虽然达到了多线程操作共享数据时保证数据正确性的目的,却无形中导致线程的并发性下降。线程从并行执行,变成了串行执行。与直接使用单进程无异。信号量,是相对折中的一种处理方式,既能保证同步,数据不混乱,又能提高线程并发。

    2 函数使用

    2.1 sem_init函数

    作用:初始化一个信号量

    1. int sem_init(sem_t *sem, int pshared, unsigned int value);
    2. // 参 1:sem 信号量
    3. // 参 2:pshared 取 0 用于线程间;取非 0(一般为 1)用于进程间
    4. // 参 3:value 指定信号量初值

    信号量的初值,决定了占用信号量的线程(进程)的个数。

    2.2 sem_destroy函数

    作用:销毁一个信号量

    int sem_destroy (sem_t *sem);

    2.3 sem_wait函数

    作用:给信号量加锁--

    int sem_wait(sem_t *sem);

    2.4 sem_post 函数

    作用:给信号量解锁 ++

    int sem_post(sem_t *sem);

    2.5 sem_trywait 函数

    作用:尝试对信号量加锁 – (与 sem_wait 的区别类比 lock 和 trylock)

    int sem_trywait(sem_t *sem);

    2.6 sem_timedwait 函数

    作用:限时尝试对信号量加锁 --

    1. int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
    2. // 参 2:abs_timeout 采用的是绝对时间。

    3 信号量实现的生产者和消费者

    根据注释,代码还是比较好理解的。两个信号量来控制,一个初始为0,表示一开始生产的数量为0.另外一个初始的数量是可以存放商品的空格数,初始化就是格子数N。
    互斥和条件变量的方式是我消费 你不能生产。 而两个信号量是你生产 我可以消费。毕竟环形的不影响,最多你生产了,我不知道。就怕多个生产者 ,你格子放入了还没移到下一个格子我又重复放入了。

    1. #include <stdio.h>
    2. #include <stdlib.h>
    3. #include <string.h>
    4. #include <unistd.h>
    5. #include <pthread.h>
    6. #include <semaphore.h>
    7. #define NUM 5
    8. int queue[NUM]; // 全局数组模拟实现环形队列
    9. sem_t black_number, product_number; // 空格子信号量。产品信号量
    10. // 生产者回调函数
    11. void* producer(void* arg) {
    12. int i = 0;
    13. int res = 0;
    14. while (1) {
    15. // 空格子信号量加锁,将空格数减一,为0则阻塞等待
    16. res = sem_wait(&black_number);
    17. if (res != 0) {
    18. fprintf(stderr, "sem_wait black_number error:%s\n", strerror(res));
    19. exit(1);
    20. }
    21. // 模拟生产一个产品
    22. queue[i] = rand() % 1000 + 1;
    23. printf("------Produce---%d\n", queue[i]);
    24. // 产品信号量解锁,将产品++
    25. res = sem_post(&product_number);
    26. if (res != 0) {
    27. fprintf(stderr, "sem_post product_number error:%s\n", strerror(res));
    28. exit(1);
    29. }
    30. i = (i + 1) % NUM;
    31. sleep(rand() % 2);
    32. }
    33. }
    34. // 消费者回调函数
    35. void* consumer(void* arg) {
    36. int i = 0;
    37. int res = 0;
    38. while (1) {
    39. // 消费者将产品数量--,为0则阻塞等待
    40. res = sem_wait(&product_number);
    41. if (res != 0) {
    42. fprintf(stderr, "sem_wait product_number error:%s\n", strerror(res));
    43. exit(1);
    44. }
    45. printf("==============================Consumer=========%d\n", queue[i]);
    46. // 消费一个产品
    47. queue[i] = 0;
    48. // 消费后将空格数++
    49. res = sem_post(&black_number);
    50. if (res != 0) {
    51. fprintf(stderr, "sem_post black_number error:%s\n", strerror(res));
    52. exit(1);
    53. }
    54. // 模拟环形队列
    55. i = (i + 1) % NUM;
    56. sleep(rand() % 5);
    57. }
    58. }
    59. int main(int argc, char** argv) {
    60. // 生产者线程和消费者线程
    61. pthread_t pid, cid;
    62. // 初始化空格子信号量,信号量为5,线程间共享----0
    63. int res = sem_init(&black_number, 0, NUM);
    64. if (res != 0) {
    65. fprintf(stderr, "sem_init black_number error:%s\n", strerror(res));
    66. exit(1);
    67. }
    68. // 初始化产品信号量,产品数量是0
    69. res = sem_init(&product_number, 0, 0);
    70. if (res != 0) {
    71. fprintf(stderr, "sem_init product_number error:%s\n", strerror(res));
    72. exit(1);
    73. }
    74. // 创建生产者线程
    75. res = pthread_create(&pid, NULL, producer, NULL);
    76. if (res != 0) {
    77. fprintf(stderr, "pthread_create producer error:%s\n", strerror(res));
    78. exit(1);
    79. }
    80. // 创建消费者线程
    81. res = pthread_create(&cid, NULL, consumer, NULL);
    82. if (res != 0) {
    83. fprintf(stderr, "pthread_create consumer error:%s\n", strerror(res));
    84. exit(1);
    85. }
    86. // 回收线程
    87. res = pthread_join(pid, NULL);
    88. if (res != 0) {
    89. fprintf(stderr, "pthread_join producer error:%s\n", strerror(res));
    90. exit(1);
    91. }
    92. res = pthread_join(cid, NULL);
    93. if (res != 0) {
    94. fprintf(stderr, "pthread_join consumer error:%s\n", strerror(res));
    95. exit(1);
    96. }
    97. // 销毁信号量
    98. res = sem_destroy(&black_number);
    99. if (res != 0) {
    100. fprintf(stderr, "sem_destroy black_number error:%s\n", strerror(res));
    101. exit(1);
    102. }
    103. res = sem_destroy(&product_number);
    104. if (res != 0) {
    105. fprintf(stderr, "sem_destroy product_number error:%s\n", strerror(res));
    106. exit(1);
    107. }
    108. return 0;
    109. }

    执行

  • 相关阅读:
    IntelliJ IDEA 如何设置类注释和方法注释
    Excel - 使用VBA通过ADO数据库连接来操作一个Excel数据源
    使用kubeadm快速部署一个k8s集群
    JavaScript 验证 API
    【源码解析】Spring源码解读-bean的加载
    关于2022年深圳市福田区支持高端服务业发展项目的申报通知
    java数据结构与算法刷题-----LeetCode20:有效的括号
    Spark - Task 与 Partition 一一对应与参数详解
    云表|低代码开发崛起:重新定义企业级应用开发
    linux部署tomcat项目详细教程(安装linux到部署tomcat)
  • 原文地址:https://blog.csdn.net/Zhouzi_heng/article/details/125468073