• 线程同步之信号量


    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. }

    执行

  • 相关阅读:
    在 LangChain 尝试了 N 种可能后,我发现了分块的奥义!
    MySQL数据库:2、MySQL的下载与安装、基本使用、系统服务制作
    阿里面试官终于把多年总结的Java八股文PDF版分享出来了,帮我金九银十拿下4个offer
    服务器前后端学习理解
    HTTP 及三次握手,四次挥手
    【软考】-- 计算机组成体系结构(上)【我的1024】
    如何使用Python进行Web开发,如Flask或Django?
    使用阿里云加速器 配置 Docker 镜像加速器
    93. 复原 IP 地址
    ffmpeg批量合并截取音频文件
  • 原文地址:https://blog.csdn.net/Zhouzi_heng/article/details/125468073