本地进程间通信(四)--信号量

概念

信号量本质上是一个计数器(不设置全局变量是因为进程间是相互独立的,而这不一定能保证所有进程都能看到,看到也不能保证++引用计数为原子操作),用于多进程对共享数据对象的读取,它和管道有所不同,它不以传送数据为主要目的,它主要是用来保护共享资源(信号量也属于临界资源),使得资源在一个时刻只有一个进程独享。

同步:处理竞争就是同步,安排进程执行的先后顺序就是同步,每个进程都有一定的个先后执行顺序。

互斥:互斥访问不可共享的临界资源,同时会引发两个新的控制问题(互斥可以说是特殊的同步)。

竞争:当并发进程竞争使用同一个资源的时候,我们就称为竞争进程。

共享资源通常分为两类:

  • 互斥共享资源,即任一时刻只允许一个进程访问该资源;
  • 同步共享资源,即同一时刻允许多个进程访问该资源;信号量是解决互斥共享资源的同步问题而引入的机制。

作用

信号量用于进程间同步,若要在进程间传递数据需要结合共享内存

信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。

每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。

最简单的信号量是只能取 0 和 1 的变量,这也是信号量最常见的一种形式,叫做二值信号量(Binary Semaphore)。而可以取多个正整数的信号量被称为通用信号量。二值信号量(Binary Semaphore)是最简单的一种锁(互斥锁),它只用两种状态:占用与非占用。所以它的引用计数为1。

步骤

当有进程要求使用共享资源时,需要执行以下操作:

1.系统首先要检测该资源的信号量;

2.若该资源的信号量值大于0,则进程可以使用该资源,此时,进程将该资源的信号量值减1;

3.若该资源的信号量值为0,则进程进入休眠状态,直到信号量值大于0时进程被唤醒,访问该资源;

当进程不再使用由一个信号量控制的共享资源时,该信号量值增加1,如果此时有进程处于休眠状态等待此信号量,则该进程会被唤醒。

*注:信号量通过同步与互斥保证访问资源的一致性。*

函数操作

1.创建或打开信号量

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>


创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
int semget(key_t key, int num_sems, int sem_flags);
  • (1)第一个参数key是长整型(唯一非零),系统建立IPC通讯 ( 消息队列、 信号量和 共享内存) 时必须指定一个ID值。通常情况下,该id值通过ftok函数得到,由内核变成标识符,要想让两个进程看到同一个信号集,只需设置key值不变就可以。
  • (2)第二个参数nsem指定信号量集中需要的信号量数目,它的值几乎总是1。
  • (3)第三个参数flag是一组标志,当想要当信号量不存在时创建一个新的信号量,可以将flag设置为IPC_CREAT与文件权限做按位或操作。设置了IPC_CREAT标志后,即使给出的key是一个已有信号量的key,也不会产生错误。而IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误。一般我们会还或上一个文件权限
  • (4)返回:成功返回信号灯集描述字semid,否则返回-1。

2.对信号量进行PV操作

对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
int semop(int semid, struct sembuf *sops, size_t nops);

sembuf 结构图
struct sembuf{  
    short sem_num;   //除非使用一组信号量,否则它为0  
    short sem_op;   //信号量在一次操作中需要改变的数据,通常是两个数,                                         
                    //一个是-1,即P(等待)操作,  
                    //一个是+1,即V(发送信号)操作。  
    short sem_flg; //通常为SEM_UNDO,使操作系统跟踪信号量,  
                  //并在进程没有释放该信号量而终止时,操作系统释放信号量  
};  

semnum: 当前需要操作的信号量在信号集中编号,从0开始

sem_flg: IPC_NOWAIT或SEM_UNDO,如果设置了SEM_UNDO标志,那么在进程结束时,相应的操作将被取消,这是比较重要的一个标志位。

sem_op: PV操作,其值为正,加到现有的信号内含值。通常用于释放所控资源的使用权;值为负,而其绝对值又大于信号的现值,操作将会阻塞,直到信号值大于或等于sem_op的绝对值。通常用于获取资源的使用权;如果sem_op的值为0,则操作将暂时阻塞,直到信号的值变为0。
blog.csdn.net/qq_44715649/article/details/88884685
  • semidsemget()返回的信号量描述符
  • 如果我们的信号量集只有一个信号量,此时,nsops=1,我们的sops就直接指向一个struct sembuf类型的指针。

2.对信号量其他控制操作

int semctl(int semid, int sem_num, int cmd, ...);


union semun {
	int val;			/* value for SETVAL */
	struct semid_ds __user *buf;	/* buffer for IPC_STAT & IPC_SET */
	unsigned short __user *array;	/* array for GETALL & SETALL */
	struct seminfo __user *__buf;	/* buffer for IPC_INFO */
	void __user *__pad;
};
  • sem_id是由semget返回的信号量标识符

  • semnum当前信号量集的哪一个信号量,就是需要控制的信号量在信号集中的编号,如果信号集只有一个元素,该值为0。cmd为控制类型,对于有些操作,需要第四个参数,即为一个union semun联合体,根据cmd不同,使用联合体中不同的字段:

  • cmd通常是下面两个值中的其中一个
    SETVAL:用来把信号量初始化为一个已知的值。p 这个值通过union semun中的val成员设置,其作用是在信号量第一次使用前对它进行设置。
    IPC_RMID:用于删除一个已经无需继续使用的信号量标识符,删除的话就不需要缺省参数,只需要三个参数即可。

举例

此列子是自己完成PV操作的封装

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
 
#include <sys/sem.h>//包含信号量定义的头文件
 
//联合类型semun定义
union semun{
	int val;
	struct semid_ds *buf;
	unsigned short *array;
};
 
//函数声明
//函数:设置信号量的值
static int set_semvalue(void);
//函数:删除信号量
static void del_semvalue(void);
//函数:信号量P操作
static int semaphore_p(void);
//函数:信号量V操作
static int semaphore_v(void);
 
static int sem_id;//信号量ID
 
 
int main(int argc,char *argv[])
{
	int i;
	int pause_time;
	char op_char = 'O';
 
	srand((unsigned int)getpid());
 
	//创建一个新的信号量或者是取得一个已有信号量的键
	sem_id = semget((key_t)1234,1,0666 | IPC_CREAT);
 
	//如果参数数量大于1,则这个程序负责创建信号和删除信号量
	if(argc > 1)
	{
	    if(!set_semvalue())
	    {
	    	fprintf(stderr,"failed to initialize semaphore\n");
		exit(EXIT_FAILURE);
	    }
 
	    op_char = 'X';//对进程进行标记
	    sleep(5);
	}
 
	//循环:访问临界区
	for(i = 0;i < 10;++i)
	{
	    //P操作,尝试进入缓冲区
	    if(!semaphore_p())
		exit(EXIT_FAILURE);
	    printf("%c",op_char);
	    fflush(stdout);//刷新标准输出缓冲区,把输出缓冲区里的东西打印到标准输出设备上
 
	    pause_time = rand() % 3;
	    sleep(pause_time);
 
	    printf("%c",op_char);
	    fflush(stdout);
 
	    //V操作,尝试离开缓冲区
	    if(!semaphore_v())
		exit(EXIT_FAILURE);
	    pause_time = rand() % 2;
	    sleep(pause_time);
	}
 
	printf("\n %d - finished \n",getpid());
 
	if(argc > 1)
	{
		sleep(10);
		del_semvalue();//删除信号量
	}
}
 
 
//函数:设置信号量的值
static int set_semvalue(void)
{
	union semun sem_union;
	sem_union.val = 1;
 
	if(semctl(sem_id,0,SETVAL,sem_union))
	    return 0;
 
	return 1;
}
 
//函数:删除信号量
static void del_semvalue(void)
{
	union semun sem_union;
 
	if(semctl(sem_id,0,IPC_RMID,sem_union))
		fprintf(stderr,"Failed to delete semaphore\n");
}
 
//函数:信号量P操作:对信号量进行减一操作
static int semaphore_p(void)
{
	struct sembuf sem_b;
 
	sem_b.sem_num = 0;//信号量编号
	sem_b.sem_op = -1;//P操作	
	sem_b.sem_flg = SEM_UNDO;
 
	if(semop(sem_id,&sem_b,1) == -1)
	{
	    fprintf(stderr,"semaphore_p failed\n");
	    return 0;
	}
 
	return 1;
}
 
//函数:信号量V操作:对信号量进行加一操作
static int semaphore_v(void)
{
	struct sembuf sem_b;
 
	sem_b.sem_num = 0;//信号量编号
	sem_b.sem_op = 1;//V操作	
	sem_b.sem_flg = SEM_UNDO;
 
	if(semop(sem_id,&sem_b,1) == -1)
	{
	    fprintf(stderr,"semaphore_v failed\n");
	    return 0;
	}
 
	return 1;
 
}
————————————————
版权声明:本文为CSDN博主「Thanos Yan」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_44715649/article/details/88884685

Linux系统中已经有封装好的semaphore,无需关心PV操作

#include <stdio.h>
#include <stdlib.h>
#include <semaphore.h>
#include <errno.h>
#define total 20

sem_t remain, apple, pear, mutex;
static unsigned int vremain = 20, vapple = 0, vpear = 0;

void *father(void *);
void *mather(void *);
void *son(void *);
void *daughter(void *);
void print_sem();
int main()
{  
    pthread_t fa, ma, so, da;  
    sem_init(&remain, 0, total);//总数初始化为20
    sem_init(&apple, 0, 0);//盆子中苹果数, 开始为0  
    sem_init(&pear, 0, 0);//盆子中梨子数, 开始为0   
    sem_init(&mutex, 0, 1);//互斥锁, 初始为1
    pthread_create(&fa, NULL, &father, NULL);  
    pthread_create(&ma, NULL, &mather, NULL);  
    pthread_create(&so, NULL, &son, NULL);
    pthread_create(&da, NULL, &daughter, NULL);    
    for(;;);
}
void *father(void *arg)
{  
    while(1)
    {      
        sem_wait(&remain);     
        sem_wait(&mutex);      
        printf("父亲: 放苹果之前, 剩余空间=%u, 苹果数=%u\n", vremain--, vapple++);
        printf("父亲: 放苹果之后, 剩余空间=%u, 苹果数=%u\n", vremain, vapple);
        sem_post(&mutex);      
        sem_post(&apple);  
        sleep(1);  
    }
}
void *mather(void *arg)
{  
    while(1)
    {      
        sem_wait(&remain);     
        sem_wait(&mutex);      
        printf("母亲: 放梨子之前, 剩余空间=%u, 梨子数=%u\n", vremain--, vpear++);
        printf("母亲: 放梨子之后, 剩余空间=%u, 梨子数=%u\n", vremain, vpear);
        sem_post(&mutex);  
        sem_post(&pear);   
        sleep(2);  
    }
}
void *son(void *arg)
{  
    while(1)
    {      
        sem_wait(&pear);   
        sem_wait(&mutex);   
        printf("儿子: 吃梨子之前, 剩余空间=%u, 梨子数=%u\n", vremain++, vpear--);
        printf("儿子: 吃梨子之后, 剩余空间=%u, 梨子数=%u\n", vremain, vpear);
        sem_post(&mutex);  
        sem_post(&remain);     
        sleep(3);
    }
}

void *daughter(void *arg)
{  
    while(1)
    {  
        sem_wait(&apple);  
        sem_wait(&mutex);
        printf("女儿: 吃苹果之前, 剩余空间=%u, 苹果数=%u\n", vremain++, vapple--);
        printf("女儿: 吃苹果之前, 剩余空间=%u, 苹果数=%u\n", vremain, vapple);   
        sem_post(&mutex);  
        sem_post(&remain);
        sleep(3);  
    }
}

void print_sem()
{  
    int val1, val2, val3;
    sem_getvalue(&remain, &val1);  
    sem_getvalue(&apple, &val2);   
    sem_getvalue(&pear, &val3);
    printf("Semaphore: remain:%d, apple:%d, pear:%d\n", val1, val2, val3);
}

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值