【Linux】进程间通信(信号量 Semaphore)

前言:进程同步篇

前一节学习了管道完成进程间通信(IPC),其主要机制就是使用虚拟文件系统(VFS)的管道文件的半双工的通信。

为什么需要进行进程同步?进程异步不是效率更高吗?
因为牵扯到两个概念

两个概念:

  • 临界资源:同一时刻,只允许一个进程或者线程访问的资源(打印机等)
  • 临界区:访问临界资源的代码段(p和v操作之间的代码,尽量减少代码量,代码量过多会导致p操作阻塞,影响程序执行效率)
  • 原子操作:不能被终止的,一旦开始操作,必须等待操作完成(申请临界资源的操作)

举个栗子(一定要看!!!)

一个试衣间,是不是在某个时刻只能被一个人使用呢?答案是肯定的,此时的一个试衣间就算是临界资源;那你在试衣间干什么?那废话,肯定试衣服了,此时的试衣服的过程就算是临界区,那为什么在试衣间换衣服的过程就是临界区呢?折磨说吧,你(进程a)进入试衣间正在换衣服,秃然之间,有个猥琐大汉(进程b)闯进了你的试衣间,那是不是完蛋了。所以你在使用试衣间(临界资源)之前,肯定要反锁(P操作),当你换衣完成,出来的时候要开锁(V操作),在你换衣服的整个过程中就是访问临界资源的操作(代码操作临界资源)


(一)信号量

信号量是一个特殊的变量,一般取正数值。代表着允许访问资源的数目。

  • 获取资源时,需要对信号量原子减一,称为p操作(荷兰语passeren)。
  • 释放资源时,需要对信号量原子加一,称为v操作(荷兰语Verhoog)

信号量的内核对象是一个信号量集(信号量的数组)

(二)信号量的作用

作用:主要用于同步进程(进程同步工作)。

  • 二进制信号量:信号量的值如果只取0,1;
  • 计数信号量:信号量的值大于1;

注意:
当进程获取资源时,信号量为0时,进行p操作进程会阻塞;

(三)信号量的能进程通信的原理

请添加图片描述

(四)信号量的操作的函数

所需头文件:

  • sys/types.h
  • sys/ipc.h
  • sys/sem.h

(1)创建或者获取已存在的信号量集

int semget(key_t key, int nsems, int semflg)

  • key:两个进程使用相同的key值,就可以使用同一个信号量
  • nsems:内核维护的是一个信号量集,创建信号量集时,会指定信号量集的信号量个数(数组的大小),获取无所谓
  • semflg:可选IPC_CREAT没有key对应信号量就创建,存在则返回对应的信号量集的ID 、 IPC_EXCL(不管有没有该信号量都返回-1);IPC_CREAT | IP_EXCL没有则创建,有则返回-1
  • 返回值成功返回信号量集的ID,失败返回-1

注意:创建的信号量不手动销毁,系统内核就会一直帮我们维护下去,除非重启系统;

(2)操作信号量(初始化信号量的值、销毁信号量集)

int semctl(int semid, int semnum, int cmd, .../* union semun val*/ )

  • semid:信号量ID
  • semnum:信号量集的下标(操作第几个信号量)
  • cmd:SETVAL(创建) 、 IPC_RMID(销毁)
  • 返回值:成功0,失败-1

union semun联合体的结构体(需要自己声明)(都是4个字节):

union semun
{
     
     
	int val;				//设置的信号量的值 :临界资源的个数(一个打印机等)
	struct semid_ds *buf; 	//IPC_STAT, IPC_SET的缓冲区
	unsigned short *array;	//GETALL,SETALL的数组
	struct seminfo *__buff;	//IPC_INFO的缓冲区
};

(3) P V操作函数

int semop(int semid, struct sembuf *sops, size_t nsops);
-semid:信号量集ID

  • struct sembuff *sops:指定p/v操作的buff
  • size_t nsops :操作信号量的个数
    //假设我们定义了一个大小为10的信号量集,现在要对其中的下标5下标8信号量进行访问,进行PV操作,则需要修改struct sembuf中的元素;
  • 返回值:
    第二个参数类型:(该类型已经存在sys/sem.h头文件中)
struct sembuf
{
    
    
           unsigned short sem_num;  /* semaphore number */ 5 / 8
           short          sem_op;   /* semaphore operation */ -1是P操作 +1是V操作
           short          sem_flg;  /* operation flags */IPC_NOWAIT非阻塞、SEM_UNDO阻塞
}

(五)信号量的具体操作

(1)void sem_init();
函数功能:创建/获取信号量集,初始化信号量的值
(2)void sem_p();
函数功能:实现对临界资源的P(-1)操作
(3)void sem_v();
函数功能:实现对临界资源的V(+1)操作
(4)void sem_destroy();
函数功能:销毁创建的信号量集(只销毁一次)

(六)一个进程同步的栗子

(1)未同步之前

jiege的代码:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
    
    
	for(int i = 0; i < 3; i++)
	{
    
    
		//用j表示jiege在换衣服
		write(1, "j", 1);
		int num = rand() % 3;
		sleep(num);
		write(1, "j", 1);
		num = rand() % 3;
		sleep(num);
	}
	return 0;
}

awei的代码:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
    
    
	for(int i = 0; i < 3; i++)
	{
    
    
		//用a表示阿伟在换衣服
		write(1, "a", 1);
		int num = rand() % 3;
		sleep(num);
		
		write(1, "a", 1);
		num = rand() % 3;
		sleep(num);
	}
	return 0;
}

结果:可以看到杰哥和阿伟有时候共用一个试衣间。。。显然需要进程同步。
在这里插入图片描述

(2)同步之后

显然,同步后的结果应该是jj 或者aa成对出现,也就是当杰哥或者阿伟用完试衣间后,另一个人再进去换衣服;

  • sem.h文件
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/sem.h>
//union semun共用体
union semun
{
    
    
	int val;	//信号量的值
};

//创建(获取)信号量集并初始化信号集中的信号量的值
void sem_init();

//p操作
void sem_p();

//v操作
void sem_v();

//销毁信号量集
void sem_destroy();
  • sem.c文件
#include "sem.h"
//信号量集的ID
static int semid = -1;

//创建(获取)信号量集并初始化信号集中的信号量的值
void sem_init()
{
    
    
	//创建信号集
	semid = semget((key_t)8888, 1, IPC_CREAT | IPC_EXCL | 0600);
	//若创建失败则说明存在该信号量集
	if(semid == -1)
	{
    
    
		//此时第二、三个参数起到占位作用
		//该信号量集存在,获取即可
		semid = semget((key_t)8888, 1, IPC_CREAT);
		if(semid == -1)
		{
    
    
			perror("semget create err");
			return;
		}
	}
	else
	{
    
    
		//初始化信号量的val值
		//定义union semun的变量保存信号量的值(一种临界资源的个数)
		union semun tmp;
		tmp.val = 1;
		//第二参数是信号量集数组下标
		if(semctl(semid, 0, SETVAL, tmp) == -1)
		{
    
    
			perror("semctl初始化失败");
			return;
		}
	}
}

//p操作
void sem_p()
{
    
    
	struct sembuf buf;
	buf.sem_num = 0;	//下标
	buf.sem_op = -1;		//P操作
	buf.sem_flg = SEM_UNDO;	//阻塞
	//第三个参数信号量的个数
	if(semop(semid, &buf, 1) == -1)
	{
    
    
		perror("semop p err");
		return;
	}
}

//v操作
void sem_v()
{
    
    
	struct sembuf buf;
	buf.sem_num = 0;	//下标
	buf.sem_op = 1;		//v操作
	buf.sem_flg = SEM_UNDO;	//阻塞
	if(semop(semid, &buf, 1) == -1)
	{
    
    
		perror("semop v err");
		return;
	}
}

//销毁信号量集
void sem_destroy()
{
    
    
	//IPC_RMID是删除信号量集的ID
	//第二个参数起到占位作用
	if(semctl(semid, 0, IPC_RMID) == -1)
	{
    
    
		perror("destroy err");
	}
}
  • jiege.c文件
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include "sem.h"
int main()
{
    
    
	//新建信号量集
	sem_init();

	for(int i = 0; i < 3; i++)
	{
    
    
		//p操作
		sem_p();
		
		//用j表示jiege在换衣服
		write(1, "j", 1);
		int num = rand() % 3;
		sleep(num);
		write(1, "j", 1);

		//v操作
		sem_v();
		num = rand() % 3;
		sleep(num);

	}
	//销毁信号量集
	sleep(2); //确保jiege后退出销毁一次即可
	sem_destroy();

	return 0;
}
  • awei.c文件
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include "sem.h"
int main()
{
    
    
	sem_init();
	for(int i = 0; i < 3; i++)
	{
    
    
		sem_p();
		//用a表示阿伟在换衣服
		write(1, "a", 1);
		int num = rand() % 3;
		sleep(num);
		write(1, "a", 1);

		sem_v();
		num = rand() % 3;
		sleep(num);
	}
	return 0;
}

使用信号量进行同步的结果:正确输出是jj或aa, 解决了jaja交叉出现的结果

  • 结果:
    在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/xiaoxiaoguailou/article/details/121513022
今日推荐