一、信号量的概念
信号量广泛用于进程或线程间的同步和互斥,信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。
编程时可根据操作信号量值的结果判断是否对公共资源具有访问的权限,当信号量值大
于0时,则可以访问,否则将阻塞。
信号量又称之为PV操作,PV原意是对信号量的操作:
一次P操作使信号量(sem)减1
一次V操作使信号量(sem)加1
对于P操作,如果信号量的sem值为小于等于0,则P操作就会阻塞。
如果信号量的值大于0,才可以执行P操作进行减1。
注意:信号量可用于 互斥 和 同步。而互斥锁只能用于互斥。
二、信号量用途
信号量主要用于进程或线程间的同步和互斥这两种典型情况。
1、若用于互斥,几个进程(或线程)往往只设置一个信号量。
2、若用于同步操作,往往会设置多个信号量,并且安排不同的初始值,来实现它们之间的
执行顺序。
2.1、图解信号量互斥
2.2、图解信号量同步
三、信号量的操作
3.1信号量的初始化
头文件:
#include <semaphore.h>
函数:int sem_init(sem_t *sem, int pshared, unsigned int value);
功能:初始化一个信号量
参数:
sem:指定的信号量6 pshared:是否在线程间或者进程间共享
设置为0 : 线程间共享
设置为1 : 进程间共享
value:信号量的初始值
返回值:
成功:0
失败:‐1
3.2、信号量P操作
头文件
#include <semaphore.h>
函数int sem_wait(sem_t *sem);
功能:将信号量的值减1,若信号量的值小于等于0,此函数会引起调用者阻塞
参数:
sem:指定的信号量
返回值:
成功:0
失败:‐1
3.3、信号量V操作
头文件
#include <semaphore.h>
函数:int sem_post(sem_t *sem);
功能:执行V操作,执行一次,信号量的值加1
参数:
sem:指定的信号量
返回值:
成功:0
失败:‐1
3.4、获取信号量的计数值
头文件
#include <semaphore.h>
函数:int sem_getvalue(sem_t *sem, int *sval);
功能:
获取sem标识的信号量的值,保存在sval中。
参数:
sem:信号量地址。
sval:保存信号量值的地址。
返回值:
成功:0
失败:‐1。
3.5、信号量的销毁
头文件
#include <semaphore.h>
函数int sem_destroy(sem_t *sem);
功能:
删除sem标识的信号量。
参数:
sem:信号量地址。返回值:
成功:0
失败:‐1。
四、信号量的使用
4.1、信号量实现互斥功能(跟互斥锁同一个案例)
#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include <unistd.h>
#include <semaphore.h>
int money=10000; //卡一共有10000元
sem_t sem;
void *pthread_fun1(void *arg)
{
sem_wait(&sem);
int getmoney; //实际取的钱
int yu_er; //卡余额
int withdraw=4800; //预先想取的钱数
printf("张三使用主卡去取钱 \n");
sleep(1);
printf("张三正在取钱 \n");
if(money < withdraw)
{
getmoney=0;
yu_er =money;
}
else if(money >= withdraw)
{
getmoney=withdraw;
yu_er=money-withdraw;
}
money = yu_er;
sleep(1);
printf("张三预先想取%d元 实际取了%d元 卡里余额%d元\n",withdraw,getmoney,yu_er);
sem_post(&sem);
}
void *pthread_fun2(void *arg)
{
sem_wait(&sem);
int getmoney; //实际取的钱
int yu_er; //卡余额
int withdraw1=2800; //预先想取的钱数
printf("李四使用副卡去取钱 \n");
sleep(1);
printf("李四正在取钱 \n");
if(money < withdraw1)
{
getmoney=0;
yu_er =money;
}
else if(money >= withdraw1)
{
getmoney=withdraw1;
yu_er=money-withdraw1;
}
money = yu_er;
sleep(1);
printf("李四预先想取 %d元 实际取了%d元 卡里余额%d元\n",withdraw1,getmoney,yu_er);
sem_post(&sem);
}
int main()
{
int ret1;
int ret2;
pthread_t pthread1;
pthread_t pthread2;
sem_init(&sem,0,1);
if((ret1=pthread_create(&pthread1,NULL,pthread_fun1,NULL))!=0)
{
perror("fail to pthread_create1");
exit(1);
}
if((ret2=pthread_create(&pthread2,NULL,pthread_fun2,NULL))!=0)
{
perror("fail to pthread_create2");
exit(1);
}
if(pthread_join(pthread1,NULL)!=0)
{
perror("fail to pthread_join1");
exit(1);
}
if(pthread_join(pthread2,NULL)!=0)
{
perror("fail to pthread_join2");
exit(1);
}
sem_destroy(&sem);
return 0;
}
运行结果
张三使用主卡去取钱
张三正在取钱
张三预先想取4800元 实际取了4800元 卡里余额5200元
李四使用副卡去取钱
李四正在取钱
李四预先想取 2800元 实际取了2800元 卡里余额2400元
4.2、信号量实现同步功能
#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include <unistd.h>
#include <semaphore.h>
sem_t sem1;
sem_t sem2;
char ch ='a';
void *pthread_fun1(void *arg)
{
while(1)
{
sem_wait(&sem2);
ch++;
sleep(1);
sem_post(&sem1);
}
}
void *pthread_fun2(void *arg)
{
while(1)
{
sem_wait(&sem1);
printf("%c",ch);
fflush(stdout);
sem_post(&sem2);
}
}
int main()
{
int ret1;
int ret2;
pthread_t pthread1;
pthread_t pthread2;
sem_init(&sem1,0,1);
sem_init(&sem2,0,0);
if((ret1=pthread_create(&pthread1,NULL,pthread_fun1,NULL))!=0)
{
perror("fail to pthread_create1");
exit(1);
}
if((ret2=pthread_create(&pthread2,NULL,pthread_fun2,NULL))!=0)
{
perror("fail to pthread_create2");
exit(1);
}
if(pthread_join(pthread1,NULL)!=0)
{
perror("fail to pthread_join1");
exit(1);
}
if(pthread_join(pthread2,NULL)!=0)
{
perror("fail to pthread_join2");
exit(1);
}
sem_destroy(&sem1);
sem_destroy(&sem2);
return 0;
}
运行结果
abcdefghijklmno......
补充:fflush()函数
作用:是用来刷新缓冲区,fflush(stdin)刷新标准输入缓冲区,把输入缓冲区里的东西丢弃;
fflush(stdout)刷新标准输出缓冲区,把输出缓冲区里的东西强制打印到标准输出设备上。
fflush(stdout)在单进程程序中作用不大,但在多进程程序中很有用。程序的输出内容一般不会立即输出,而是在程序结束后再输出。
fflush(stdout)会强制每次printf()都立即显示在标准输出设备上。
一句话,程序不结束,输出语句不会立刻输出,而是等待程序结束,把所有输出的数据一次输出完。但本此代码使用while(1)死循环,不会结束,因此,需要使用fflush先打印到标准输出上。
你学会了???