首先,祝大家冬至快乐!今天,介绍下信号量,也属于线程同步的一种方式。文字比较多,不好理解,但是一看代码你就懂了。


看懂这次的代码还需要之前的知识~ 补课链接:

信号量 Semaphore

定义:

有时被称为信号灯,是在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用。

目的:

类似计数器,常用在多线程同步任务上,信号量可以在当前线程某个任务完成后,通知别的线程,再进行别的任务。

分类:

  • 二值信号量: 信号量的值只有 0 和 1,这和互斥量很类似,若资源被锁住,信号量的值为 0,若资源可用,则信号量的值为 1;
  • 计数信号量: 信号量的值在 0 到一个大于 1 的限制值之间,该计数表示可用的资源的个数。

信号量在创建时需要设置一个初始值,表示同时可以有几个任务可以访问该信号量保护的共享资源,初始值为 1 就变成互斥锁 Mutex,即同时只能有一个任务可以访问信号量保护的共享资源

函数使用:

首先需要 include 这个库,没啥好说的,除非你自己实现内部函数。和互斥锁一样,也是四大金刚。

sem_init

简述:创建信号量

第一个参数:指向的信号对象

第二个参数:控制信号量的类型,如果其值为 0,就表示信号量是当前进程的局部信号量,否则信号量就可以在多个进程间共享

第三个参数:信号量 sem 的初始值

返回值:success 为 0,failure 为 - 1

  1. int sem_init(sem_t *sem, int pshared, unsigned int value);

sem_post

简述:信号量的值加 1

第一个参数:信号量对象

返回值:success 为 0,failure 为 - 1

  1. int sem_post(sem_t *sem);

sem_wait

简述:信号量的值加 - 1

第一个参数:信号量对象

返回值:success 为 0,failure 为 - 1

  1. int sem_wait(sem_t *sem);

sem_destroy

简述:用完记得销毁哦~

第一个参数:信号量对象

返回值:success 为 0,failure 为 - 1

  1. int sem_destroy(sem_t *sem);

举例

说明:你可以进行三个下载任务,但是最多选择同时执行二个(创建两个线程)。直接看 main 函数即可,信号量的逻辑都在里面,在实际代码中最好,所有的线程和信号量的创建、释放都要进行校验,这里为了方便阅读,减少代码行数,就不进行校验了。

  1. #include <stdio.h>
  2. #include <pthread.h>
  3. #include <semaphore.h>
  4. #include <windows.h>
  5. #define MAXNUM 2
  6. sem_t semDownload;
  7. pthread_t a_thread, b_thread, c_thread;
  8. int g_phreadNum = 1;
  9. void InputInfo(void)
  10. {
  11. printf("****************************************\n");
  12. printf("*** which task you want to download? ***\n");
  13. printf("*** you can enter [1-3],[0] is done ***\n");
  14. printf("****************************************\n");
  15. }
  16. void *func1(void *arg)
  17. {
  18. //等待信号量的值>0
  19. sem_wait(&semDownload);
  20. printf("============== Downloading Task 1 ============== \n");
  21. Sleep(5000);
  22. printf("============== Finished Task 1 ============== \n");
  23. g_phreadNum--;
  24. //等待线程结束
  25. pthread_join(a_thread, NULL);
  26. }
  27. void *func2(void *arg)
  28. {
  29. sem_wait(&semDownload);
  30. printf("============== Downloading Task 2 ============== \n");
  31. Sleep(3000);
  32. printf("============== Finished Task 2 ============== \n");
  33. g_phreadNum--;
  34. pthread_join(b_thread, NULL);
  35. }
  36. void *func3(void *arg)
  37. {
  38. sem_wait(&semDownload);
  39. printf("============== Downloading Task 3 ============== \n");
  40. Sleep(1000);
  41. printf("============== Finished Task 3 ============== \n");
  42. g_phreadNum--;
  43. pthread_join(c_thread, NULL);
  44. }
  45. int main()
  46. {
  47. int taskNum;
  48. InputInfo();
  49. while (scanf("%d", &taskNum) != EOF) {
  50. //输入0,判断是否正常退出
  51. if (taskNum == 0 && g_phreadNum <= 1) {
  52. break;
  53. }
  54. if (taskNum == 0){
  55. printf("Can not quit, casue count of threads is [%d]\n", g_phreadNum - 1);
  56. }
  57. //初始化信号量
  58. sem_init(&semDownload, 0, 0);
  59. printf("your choose Downloading Task [%d]\n", taskNum);
  60. //线程数超过2个则不下载
  61. if (g_phreadNum > MAXNUM) {
  62. printf("!!! You've reached a limit on the number of threads !!!\n");
  63. continue;
  64. }
  65. //用户选择下载Task
  66. switch (taskNum)
  67. {
  68. case 1:
  69. //创建线程1
  70. pthread_create(&a_thread, NULL, func1, NULL);
  71. //信号量+1,进而触发fun1的任务
  72. sem_post(&semDownload);
  73. //总线程数+1
  74. g_phreadNum++;
  75. break;
  76. case 2:
  77. pthread_create(&b_thread, NULL, func2, NULL);
  78. sem_post(&semDownload);
  79. g_phreadNum++;
  80. break;
  81. case 3:
  82. pthread_create(&c_thread, NULL, func3, NULL);
  83. sem_post(&semDownload);
  84. g_phreadNum++;
  85. break;
  86. default:
  87. printf("!!! eroor task [%d] !!!\n", taskNum);
  88. break;
  89. }
  90. }
  91. //销毁信号量
  92. sem_destroy(&semDownload);
  93. return 0;
  94. }

输出:

红色箭头指的是你的输入

C语言多线程编程(三)——信号量 - 知乎 - 图1

如果你是初学者,你可能会有这样的

疑问: 为啥不直接把 “下载任务” 放在 switch-case 内,而要搞个信号量这么麻烦呢?

我给你的答案是: 你自己实现一次,就把函数里的内容剪切到 case 里,然后,运行下代码就知道为啥了。
https://zhuanlan.zhihu.com/p/98717838