信号量 04.11

原语(原子语言,不可分割,不可中断)是操作系统或计算机网络用语范畴。是由若干条指令组成的,用于完成一定功能的一个过程。primitive or atomic action 是由若干个机器指令构成的完成某种特定功能的一段程序,具有不可分割性。即原语的执行必须是连续的,在执行过程中不允许被中断。

PV原语通过操作信号量来处理进程间的同步与互斥的问题。其核心就是一段不可分割不可中断的程序。

P原语
P是荷兰语Passeren(通过)的首字母。为阻塞原语,负责把当前进程由运行状态转换为阻塞状态,直到另外一个进程唤醒它。操作为:申请一个空闲资源(把信号量减1),若成功,则退出;若失败,则该进程被阻塞;

V原语
V是荷兰语Verhogen(增加)的首字母。为唤醒原语,负责把一个被阻塞的进程唤醒,它有一个参数表,存放着等待被唤醒的进程信息。操作为:释放一个被占用的资源(把信号量加1),如果发现有被阻塞的进程,则选择一个唤醒之。

解决同步问题:

互斥量—线程

信号量—进程

~#ipcs

~#ipcrm -a

小结

共享内存消息队列信号量
msggetshmgetsemget
msgctlshmctlsemctl
msgsnd msgrcvmemcpysemop

头文件

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
semget()

功能:创建一个新的信号量或获取一个已经存在的信号量的键值。

返回值:成功返回信号量标识符,失败返回-1;

int semget(key_t key, int nsems, int semflg);
/*
1)key为整型值,用户可以自己设定,如1001
有两种情况:
1.键值是IPC_PRIVATE,该值通常为0,创建一个信号量,使用和无名管道类似,只用于有亲缘关系的进程间通信
用法与shmget()中的IPC_PRIVATE相同
2.键值有用户指定(例如1001);也可以使用ftok()函数来取得一个唯一的键值

2)nsems 表示创建并初始化信号量的个数,常为 1

3)semflg:信号量的创建方式或权限。有IPC_CREAT,IPC_EXCL
	IPC_CREAT如果信号量不存在,则创建一个信号量,否则获取已存在的信号量
	IPC_EXCL只有信号量不存在的时候,新的信号量才建立,否则就产生错误
*/
semctl()

功能:控制信号量的信息。

返回值:成功返回0,失败返回-1;

int semctl(int semid, int semnum, int cmd, ...);
//1)semid信号量的标志码(ID),也就是semget()函数的返回值;

//2)semnum操作信号在信号集中的编号。从0开始。

//3)cmd命令,表示要进行的操作,通常有如下两个操作:
//SETVAL:用来把信号量初始化为一个已知的值。p 这个值通过union semun中的val成员设置,其作用是在信号量第一次使用前对它进行设置。
//IPC_RMID:用于删除一个已经无需继续使用的信号量标识符。

//4)如果有第四个参数,它通常是一个union semum结构,定义如下:
union semun {
	int val;    /* Value for SETVAL */  //设置具体值
	struct semid_ds* buf;    /* Buffer for IPC_STAT, IPC_SET */
	unsigned short* array;  /* Array for GETALL, SETALL */
	struct seminfo* __buf;  /* Buffer for IPC_INFO
								(Linux-specific) */
};
semop()

功能:改变信号量的值

int semop(int semid, struct sembuf *sops, size_t nsops);

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

#include <iostream>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

using namespace std;

union semun {
	int              val;    /* Value for SETVAL */  //设置具体值
	struct semid_ds* buf;    /* Buffer for IPC_STAT, IPC_SET */
	unsigned short* array;  /* Array for GETALL, SETALL */
	struct seminfo* __buf;  /* Buffer for IPC_INFO
								(Linux-specific) */
};


int sem_create(key_t key, int sems)
{
	int res = semget(key, sems, IPC_CREAT | 0777);
	if (res < 0)
	{
		perror("semget error");
	}
	return res;
}

int sem_setVal(int semid, int semindex, int val)
{
	union semun arg;
	arg.val = val;
	int res = semctl(semid, semindex, SETVAL, arg);
	if (res < 0)
	{
		perror("semctl error");
	}
	return res;
}

int sem_p(int semid, int semindex)
{
	struct sembuf buf = { semindex,-1,SEM_UNDO };
	int res = semop(semid, &buf,1);
	if (res < 0)
	{
		perror("semop_P error");
	}
	return res;
}

int sem_v(int semid, int semindex)
{
	struct sembuf buf = { semindex,1,SEM_UNDO };
	int res = semop(semid, &buf, 1);
	if (res < 0)
	{
		perror("semop_V error");
	}
	return res;
}

//以上函数由project1和project2使用

//-----project 1------
int main()
{
	int semid = sem_create((key_t)1001, 1);
	sem_setVal(semid, 0, 1);//将信号量数组下标为 0 的数据设置为 1
	sem_p(semid, 0);
	int i = 10;
	while (i--)
	{
		cout << "----project 1 i=" << i<<endl;
		sleep(1);
	}
	sem_v(semid, 0);
	return 0;
}

//-----project 2------
int main()
{
	//如果1001信号量存在,则访问,不存在则创建
	int semid = sem_create((key_t)1001, 1);
	//sem_setVal(semid, 0, 1);//将信号量数组下标为 0 的数据设置为 1
	sem_p(semid, 0);
	int i = 10;
	while (i--)
	{
		cout << "----project 2 i=" << i << endl;
		sleep(1);
	}
	sem_v(semid, 0);
	return 0;
}

在这里插入图片描述

高并发瓶颈:磁盘IO操作

多进程要操作同一个内存

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

共享内存的设计:
建立一个索引数组,初始值为0
1.当前值服务器发送数据时,信号量加锁
2.判断当期任务索引区什么下标为多少假设为0
3.将索引区下标0改为1数据区下标写入数据
4.信号量解锁
5.发送消息队列,告诉后置服务器做完任务了
后置服务器
1.收消息队列
2.信号量加锁
3.判断索引区什么下标为1
4.读取下标为1的数据区修改改下标为0
5.数据读取进行相关操作
6.信号量解锁
数据的读取: memcpy(shmaddr+sizef(arr) + sizeof(data)*下标)表示在共享内存中偏移到要读取的数据的位置
后置服务器传递数据给前置服务器,可以再创建一个共享内存
或者把索弓|区的索引|设置一个不同的值

共享内存+消息队列+信号量,作业完成基础版

作业一:

1.根据上课讲解的信号量的使用做进程锁

2.创建一个工程做前置服务器

3.创建第二个工程做后置服务器

4.前置服务器创建一个共享内存,一个消息队列,一个信号量

5.后置服务器访问相同key的共享内存、消息队列、信号量

6.前置服务器进程加锁往共享内存写入学生结构体信息

7.前置服务器操作完共享内存发送消息队列

8.前置服务器操作进程解锁

9.后置服务器读到消息队列的消息进程加锁读取共享内存内的学生结构体,并打印显示

10.共享内存采用索引区+数据区的设计方式

在这里插入图片描述

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐