首先,祝大家冬至快乐!今天,介绍下信号量,也属于线程同步的一种方式。文字比较多,不好理解,但是一看代码你就懂了。
看懂这次的代码还需要之前的知识~ 补课链接:
信号量 Semaphore
定义:
有时被称为信号灯,是在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用。
目的:
类似计数器,常用在多线程同步任务上,信号量可以在当前线程某个任务完成后,通知别的线程,再进行别的任务。
分类:
- 二值信号量: 信号量的值只有 0 和 1,这和互斥量很类似,若资源被锁住,信号量的值为 0,若资源可用,则信号量的值为 1;
- 计数信号量: 信号量的值在 0 到一个大于 1 的限制值之间,该计数表示可用的资源的个数。
信号量在创建时需要设置一个初始值,表示同时可以有几个任务可以访问该信号量保护的共享资源,初始值为 1 就变成互斥锁 Mutex,即同时只能有一个任务可以访问信号量保护的共享资源
函数使用:
首先需要 include
sem_init
简述:创建信号量
第一个参数:指向的信号对象
第二个参数:控制信号量的类型,如果其值为 0,就表示信号量是当前进程的局部信号量,否则信号量就可以在多个进程间共享
第三个参数:信号量 sem 的初始值
返回值:success 为 0,failure 为 - 1
int sem_init(sem_t *sem, int pshared, unsigned int value);
sem_post
简述:信号量的值加 1
第一个参数:信号量对象
返回值:success 为 0,failure 为 - 1
int sem_post(sem_t *sem);
sem_wait
简述:信号量的值加 - 1
第一个参数:信号量对象
返回值:success 为 0,failure 为 - 1
int sem_wait(sem_t *sem);
sem_destroy
简述:用完记得销毁哦~
第一个参数:信号量对象
返回值:success 为 0,failure 为 - 1
int sem_destroy(sem_t *sem);
举例
说明:你可以进行三个下载任务,但是最多选择同时执行二个(创建两个线程)。直接看 main 函数即可,信号量的逻辑都在里面,在实际代码中最好,所有的线程和信号量的创建、释放都要进行校验,这里为了方便阅读,减少代码行数,就不进行校验了。
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <windows.h>
#define MAXNUM 2
sem_t semDownload;
pthread_t a_thread, b_thread, c_thread;
int g_phreadNum = 1;
void InputInfo(void)
{
printf("****************************************\n");
printf("*** which task you want to download? ***\n");
printf("*** you can enter [1-3],[0] is done ***\n");
printf("****************************************\n");
}
void *func1(void *arg)
{
//等待信号量的值>0
sem_wait(&semDownload);
printf("============== Downloading Task 1 ============== \n");
Sleep(5000);
printf("============== Finished Task 1 ============== \n");
g_phreadNum--;
//等待线程结束
pthread_join(a_thread, NULL);
}
void *func2(void *arg)
{
sem_wait(&semDownload);
printf("============== Downloading Task 2 ============== \n");
Sleep(3000);
printf("============== Finished Task 2 ============== \n");
g_phreadNum--;
pthread_join(b_thread, NULL);
}
void *func3(void *arg)
{
sem_wait(&semDownload);
printf("============== Downloading Task 3 ============== \n");
Sleep(1000);
printf("============== Finished Task 3 ============== \n");
g_phreadNum--;
pthread_join(c_thread, NULL);
}
int main()
{
int taskNum;
InputInfo();
while (scanf("%d", &taskNum) != EOF) {
//输入0,判断是否正常退出
if (taskNum == 0 && g_phreadNum <= 1) {
break;
}
if (taskNum == 0){
printf("Can not quit, casue count of threads is [%d]\n", g_phreadNum - 1);
}
//初始化信号量
sem_init(&semDownload, 0, 0);
printf("your choose Downloading Task [%d]\n", taskNum);
//线程数超过2个则不下载
if (g_phreadNum > MAXNUM) {
printf("!!! You've reached a limit on the number of threads !!!\n");
continue;
}
//用户选择下载Task
switch (taskNum)
{
case 1:
//创建线程1
pthread_create(&a_thread, NULL, func1, NULL);
//信号量+1,进而触发fun1的任务
sem_post(&semDownload);
//总线程数+1
g_phreadNum++;
break;
case 2:
pthread_create(&b_thread, NULL, func2, NULL);
sem_post(&semDownload);
g_phreadNum++;
break;
case 3:
pthread_create(&c_thread, NULL, func3, NULL);
sem_post(&semDownload);
g_phreadNum++;
break;
default:
printf("!!! eroor task [%d] !!!\n", taskNum);
break;
}
}
//销毁信号量
sem_destroy(&semDownload);
return 0;
}
输出:
红色箭头指的是你的输入
如果你是初学者,你可能会有这样的
疑问: 为啥不直接把 “下载任务” 放在 switch-case 内,而要搞个信号量这么麻烦呢?
我给你的答案是: 你自己实现一次,就把函数里的内容剪切到 case 里,然后,运行下代码就知道为啥了。
https://zhuanlan.zhihu.com/p/98717838