I\O进程线程(Day32)

一、学习内容

  1. 进程之间的通信(nterprocess communication)

    1. 信号通信

      1. 概念

        1> 信号通信中,多个进程只起到通知作用,没有数据传输的功能

        2> 所谓信号通信,就是软件模拟的硬件的中断请求

        3>原理图

      2. 信号处理方式
        1. 默认(SIG_DFL)
          停下正在执行的任务,去执行信号功能的任务,大多数默认处理是杀死进程
        2. 捕获
          需要给定一个函数名,表示停下正在执行的任务,去执行给定的函数
        3. 忽略(SIG_IGN)
          忽略当前信号,继续执行自己的任务
      3. 收到信号类型
        1. 内核发送过来的信号
          如管道破裂时,内核向用户空间发射SIGPIPE信号,指针操作失误时内核向用户空间发射SIGSEGV信号
        2. 用户发送过来的信号
          如用户键入ctrl+c,表示终端向该进程发送SIGINT信号;用户键入ctrl+z,表示终端向进程发送SIGSTOP信号;
        3. 一个进程向另一个进程发送信号
          需要使用相关函数完成,kill 函数,表示向某个进程发送信号
      4. signal(信号绑定函数)
        typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler);
        1. 返回值
          成功返回处理方式,失败返回SIG_ERR( void * -1 ),并置位错误码
        2. 参数

          参数1:要处理的信号号

          参数2:信号处理方式一共有三种

                 SIG_IGN:表示忽略该信号

                SIG_DFL:表示默认处理该信号

                 自定义函数名:返回值为void,参数为int

        3. 功能
          将发送给当前进程的信号与信号处理方式进行绑定
      5. alarm(闹钟)
        unsigned int alarm(unsigned int seconds);
        1. 返回值
          如果之前没有启动定时器,那么该函数返回0,如果之前启动了定时器,那么该函数返回上一个定时器剩余的秒数,并更新该定时器时间
        2. 参数
          要延迟的秒数,如果为0,表示取消该定时器
        3. 功能
          启动一个定时器,延迟seconds秒后,向该进程发送SIGALRM信号
      6. kill(发信号)
        int kill(pid_t pid, int sig);
        1. 返回值
          成功返回0,失败返回-1并置位错误码
        2. 参数

          参数1:进程ID号

               >0:表示向指定进程发送信号
               =0:表示向当前进程所在的进程组发送信号
               =-1:表示向所有进程发送信号
               <-1:表示向进程号为pid的绝对值那个进程组发送信号

          参数2:要发送的信号号

        3. 功能
          向指定的进程或进程组发信号
      7. raise(发信号)
        int raise(int sig);
        1. 返回值
          成功返回0,失败返回非0错误码
        2. 参数
          要发送的信号号
        3. 功能
          向当前进程发送信号(自己给自己发信号 kill(getpid(), sig))
    2. System V提供的IPC通信方式

      1. 概念
        system V提供的通信方式,是将通信容器独立于可执行程序而存在。即使相关程序已经退出,写入容器中的数据如果取出,依然存在
      2. 通信方式

        消息队列

        共享内存

        信号量集

        注意:

        使用该通信方式时,需要先创建一个 IPC 对象,后续只需对该对象进行操作,就可以实现进程间通信

      3. 有关System V 提供的IPC对象操作的指令

        1、ipcs:可以查看当前所有的消息队列、共享内存、信号量集的属性

        2、ipcs -q:只查看消息队列的相关信息

        3、ipcs -m:只查看共享内存的相关信息

        4、ipcs -s:只查看信号量集的相关信号

        5、ipcrm -q/-m/-s ID:表示删除某个消息队列、共享内存、信号量集

    3. 消息队列

      1. 原理图
      2. key(钥匙)值的创建
        key_t ftok(const char *pathname, int proj_id);
        1. 返回值
          成功返回当前ipc对象key值,失败返回-1并置位错误码
        2. 参数

          参数1:一个必须已经存在的文件路径,占key值的四分之三,其中inode号占2字节,设备号占1字节

          参数2:一个随机值,栈key值的1字节

        3. 功能
          创建IPC通信的key值
      3. API
        1. 创建消息队列
          int msgget(key_t key, int msgflg);
          1. 返回值
            成功返回该消息队列的id号,以便于后期使用该消息队列,失败返回-1并置位错误码
          2. 参数

            参数1:key值,可以由ftok创建出来,也可以使用IPC_PRIVATE(适用于亲缘进程间通信)

            参数2:消息队列创建的标识

                IPC_CREAT:表示创建一个消息队列,如果消息队列已经存在,则直接打开
                IPC_EXEC:表示确保创建一个新的消息队列,如果该消息队列存在,则报错
            注意:
                  如果是创建消息队列,那么该参数中也要包含该消息队列的权限

          3. 功能
            通过给定的key值创建一个消息队列对象,并返回该消息队列的id号
        2. 向消息队列中存放数据
          int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
          1. 返回值
            成功返回0,失败返回-1并置位错误码
          2. 参数

            参数1:消息队列id号

            参数2:消息的起始地址,一般是如下结构体类型,需要用户自己定义

            struct msgbuf {
                           long mtype;       /* 消息类型,必须是大于0的数字,必须放在结构体的最上面 */
                           char mtext[1];    /* 消息正文 */
                       };

            参数3:当前消息的正文的大小,不包含消息的类型大小
            参数4:是否阻塞

                0:表示阻塞
                IPC_NOWAIT:表示非阻塞

          3. 功能
            向msqid对应的消息队列容器中存放以msgp作为起始地址的msgsz大小的数据
        3. 从消息队列中读取消息
          ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
          1. 返回值
            成功返回读取数据正文的大小,失败返回-1并置位错误码
          2. 参数

            参数1:消息队列id

            参数2:取出数据存放的容器起始地址

            参数3:消息正文的大小

            参数4:要取出的消息类型

                >0:表示取得指定类型的消息的第一个

                =0:表示取得消息队列的第一个(不限制类型)

                <0: 从消息队列中取出类型为小于等于给定类型的绝对值的第一个消息

            参数5:是否阻塞

                0:表示阻塞

                IPC_NOWAIT:表示非阻塞

          3. 功能
            从消息队列中取出一条消息
        4. 消息队列的控制函数
          int msgctl(int msqid, int cmd, struct msqid_ds *buf);
          1. 返回值
            成功返回0,失败返回-1并置位错误码
          2. 参数

            参数1:消息队列id

            参数2: 该函数要执行的操作指令,该函数会因不同的指令执行不同的操作

                IPC_STAT: 获取当前消息队列的属性信息,并将该属性放入参数3中

                IPC_SET: 设置当前消息队列的属性信息,需要将新信息当做第三个参数传入

                IPC_RMID: 删除当前的消息队列,此时参数3可以省略填NULL即可

            参数3:根据参数2而定

          3. 功能
            用于控制消息队列的操作
  2. 脑图

二、作业

作业1:

使用消息队列实现两个进程的相互通信

 

 代码解答:

方法一:进程法
进程A:
#include <myhead.h>  
struct msgbuf
{
	long mtype;      // 消息类型,必须是长整型
	char mtext[128]; // 消息正文,最多包含128个字符
};

#define MSGSZ (sizeof(struct msgbuf)-sizeof(long))  // 定义消息正文的大小,不包含消息类型

int main(int argc, const char *argv[])
{
	key_t key = ftok("/", 'I');  // 使用ftok函数生成唯一的消息队列key
	if (key == -1)  // 错误检查
	{
		perror("ftok error");  // 打印ftok调用错误的详细信息
		return -1;  // 返回错误
	}
	printf("key=%#x\n", key);  // 打印生成的key

	pid_t pid = fork();  // 创建子进程
	if (pid > 0)  // 父进程执行此部分代码
	{
		int msqid = msgget(key, IPC_CREAT | 0664);  // 创建或获取消息队列
		if (msqid == -1)  // 错误检查
		{
			perror("msgget error");  // 打印msgget调用错误信息
			return -1;  // 返回错误
		}
		printf("msqid=%d\n", msqid);  // 打印消息队列ID
		
		struct msgbuf buf;  // 定义发送消息的缓冲区
		while (1)
		{
			printf("请输入消息类型>>>(类型只能有1)");
			scanf("%ld", &buf.mtype);  // 从用户输入中读取消息类型
			getchar();  // 清除输入缓冲区中残留的换行符		
			if (buf.mtype != 1)  // 如果消息类型不为1,提示重新输入
			{
				printf("输入的类型有误请重新输入\n");
				continue;
			}
			printf("请输入消息正文>>>");
			fgets(buf.mtext, MSGSZ, stdin);  // 从标准输入中获取消息正文
			buf.mtext[strlen(buf.mtext) - 1] = 0;  // 去除换行符

			msgsnd(msqid, &buf, MSGSZ, 0);  // 将消息发送到消息队列
			printf("发送成功\n");  // 成功发送提示

			if (strcmp(buf.mtext, "quit") == 0)  // 如果消息内容为"quit",退出循环
			{
				break;
			}
		}
	} 
	else if (pid == 0)  // 子进程执行此部分代码
	{
		int msqid = msgget(key, IPC_CREAT | 0664);  // 获取父进程中创建的消息队列
		if (msqid == -1)  // 错误检查
		{
			perror("msgget error");  // 打印msgget调用错误信息
			return -1;  // 返回错误
		}

		printf("msqid=%d\n", msqid);  // 打印消息队列ID

		struct msgbuf rbuf;  // 定义接收消息的缓冲区
		while (1)
		{
			msgrcv(msqid, &rbuf, MSGSZ, 2, 0);  // 从消息队列中读取类型为2的消息,阻塞方式接收

			printf("收到的消息为:%s\n", rbuf.mtext);  // 打印接收到的消息内容

			if (strcmp(rbuf.mtext, "quit") == 0)  // 如果收到的消息内容为"quit",退出循环
			{
				break;
			}
		}

		// 删除消息队列
		if (msgctl(msqid, IPC_RMID, NULL) == -1)  // 删除消息队列
		{
			perror("msgctl error");  // 打印删除消息队列时的错误信息
			return -1;  // 返回错误
		}

		exit(EXIT_SUCCESS);  // 子进程正常退出
	}
	else  // fork函数调用出错
	{
		perror("fork error");  // 打印fork调用错误信息
		return -1;  // 返回错误
	}

	waitpid(0, NULL, 0);  // 父进程等待子进程退出

	return 0;  // 程序正常结束
}
进程B:
#include <myhead.h>  
struct msgbuf
{
	long mtype;      // 消息类型,必须是长整型
	char mtext[128]; // 消息正文,最多包含128个字符
};

#define MSGSZ (sizeof(struct msgbuf)-sizeof(long))  // 定义消息正文的大小,不包含消息类型

int main(int argc, const char *argv[])
{
	key_t key = ftok("/", 'I');  // 使用ftok函数生成唯一的消息队列key
	if (key == -1)  // 错误检查
	{
		perror("ftok error");  // 打印ftok调用错误的详细信息
		return -1;  // 返回错误
	}
	printf("key=%#x\n", key);  // 打印生成的key

	pid_t pid = fork();  // 创建子进程
	if (pid == 0)  // 子进程执行此部分代码
	{
		int msqid = msgget(key, IPC_CREAT | 0664);  // 创建或获取消息队列
		if (msqid == -1)  // 错误检查
		{
			perror("msgget error");  // 打印msgget调用错误信息
			return -1;  // 返回错误
		}
		printf("msqid=%d\n", msqid);  // 打印消息队列ID
		
		struct msgbuf buf;  // 定义发送消息的缓冲区
		while (1)
		{
			printf("请输入消息类型>>>(类型只能有2)");
			scanf("%ld", &buf.mtype);  // 从用户输入中读取消息类型
			getchar();  // 清除输入缓冲区中残留的换行符		
			if (buf.mtype != 2)  // 如果消息类型不为2,提示重新输入
			{
				printf("输入的类型有误请重新输入\n");
				continue;
			}
			printf("请输入消息正文>>>");
			fgets(buf.mtext, MSGSZ, stdin);  // 从标准输入中获取消息正文
			buf.mtext[strlen(buf.mtext) - 1] = 0;  // 去除换行符

			msgsnd(msqid, &buf, MSGSZ, 0);  // 将消息发送到消息队列
			printf("发送成功\n");  // 成功发送提示

			if (strcmp(buf.mtext, "quit") == 0)  // 如果消息内容为"quit",退出循环
			{
				break;
			}
		}
	} 
	else if (pid > 0)  // 父进程执行此部分代码
	{
		int msqid = msgget(key, IPC_CREAT | 0664);  // 创建或获取消息队列
		if (msqid == -1)  // 错误检查
		{
			perror("msgget error");  // 打印msgget调用错误信息
			return -1;  // 返回错误
		}

		printf("msqid=%d\n", msqid);  // 打印消息队列ID

		struct msgbuf rbuf;  // 定义接收消息的缓冲区
		while (1)
		{
			msgrcv(msqid, &rbuf, MSGSZ, 1, 0);  // 从消息队列中读取类型为1的消息

			printf("收到的消息为:%s\n", rbuf.mtext);  // 打印接收到的消息内容

			if (strcmp(rbuf.mtext, "quit") == 0)  // 如果收到的消息内容为"quit",退出循环
			{
				break;
			}
		}

		// 删除消息队列
		if (msgctl(msqid, IPC_RMID, NULL) == -1)  // 删除消息队列
		{
			perror("msgctl error");  // 打印删除消息队列时的错误信息
			return -1;  // 返回错误
		}

		exit(EXIT_SUCCESS);  // 父进程正常退出
	} 
	else  // fork函数调用出错
	{
		perror("fork error");  // 打印fork调用错误信息
		return -1;  // 返回错误
	}

	waitpid(0, NULL, 0);  // 父进程等待子进程退出

	return 0;  // 程序正常结束
}
方法二:线程法
进程A:
#include <myhead.h>  
// 定义消息缓冲区结构体,包含消息类型和消息正文
struct msgbuf
{
	long mtmype;  // 消息类型,必须是长整型
	char mtext[128];  // 消息正文,最多128个字符
};

#define MSGSZ (sizeof(struct msgbuf) - sizeof(long))  // 计算消息正文的大小,不包含消息类型

// 发送消息到B进程的函数,作为父线程的执行体
void *send_to_b(void *arg)
{
	key_t key = ftok("/", 'I');  // 使用ftok生成唯一的key
	if (key == -1)  // 错误检查
	{
		perror("ftok error");  // 打印ftok调用错误信息
		pthread_exit(NULL);  // 线程退出
	}
	printf("key=%#x\n", key);  // 打印生成的key

	int msqid = msgget(key, IPC_CREAT | 0664);  // 创建或获取消息队列
	if (msqid == -1)  // 错误检查
	{
		perror("msgget error");  // 打印msgget调用错误信息
		pthread_exit(NULL);  // 线程退出
	}
	printf("msqid=%d\n", msqid);  // 打印消息队列ID

	struct msgbuf buf;  // 定义发送消息的缓冲区

	while (1)
	{
		printf("请输入消息类型>>>(只能输入1)");
		scanf("%ld", &buf.mtmype);  // 获取消息类型
		getchar();  // 清除输入缓冲区中的换行符
		if (buf.mtmype != 1)  // 如果消息类型不是1,提示重新输入
		{
			printf("输入有误,请重新输入\n");
			continue;  // 重新输入
		}
		printf("请输入消息正文>>>");
		fgets(buf.mtext, MSGSZ, stdin);  // 获取消息正文
		buf.mtext[strlen(buf.mtext) - 1] = 0;  // 去掉最后的换行符

		msgsnd(msqid, &buf, MSGSZ, 0);  // 将消息发送到消息队列
		printf("发送成功\n");  // 提示发送成功

		if (strcmp(buf.mtext, "quit") == 0)  // 如果消息内容为"quit",退出循环
		{
			break;
		}
	}
	pthread_exit(NULL);  // 线程正常退出
}

// 接收来自B进程消息的函数,作为子线程的执行体
void *receive_from_b(void *arg)
{
	key_t key = ftok("/", 'I');  // 生成唯一的key
	if (key == -1)  // 错误检查
	{
		perror("ftok error");  // 打印ftok调用错误信息
		pthread_exit(NULL);  // 线程退出
	}

	printf("key=%#x\n", key);  // 打印生成的key

	int msqid = msgget(key, IPC_CREAT | 0664);  // 获取或创建消息队列
	if (msqid == -1)  // 错误检查
	{
		perror("msgget error");  // 打印msgget调用错误信息
		pthread_exit(NULL);  // 线程退出
	}

	printf("msqid=%d\n", msqid);  // 打印消息队列ID

	struct msgbuf buf;  // 定义接收消息的缓冲区
	
	while (1)
	{
		msgrcv(msqid, &buf, MSGSZ, 2, 0);  // 从消息队列中接收类型为2的消息
		printf("收到消息为%s\n", buf.mtext);  // 打印收到的消息内容

		if (strcmp(buf.mtext, "quit") == 0)  // 如果收到的消息为"quit",退出循环
		{
			break;
		}
	}

	if (msgctl(msqid, IPC_RMID, NULL) == -1)  // 删除消息队列
	{
		perror("msgctl error");  // 打印msgctl调用错误信息
		pthread_exit(NULL);  // 线程退出
	}
	
	pthread_exit(NULL);  // 线程正常退出
}

// 主函数
int main(int argc, const char *argv[])
{
	pthread_t tid1, tid2;  // 定义两个线程ID变量

	pthread_create(&tid1, NULL, send_to_b, NULL);  // 创建发送消息的父线程
	pthread_create(&tid2, NULL, receive_from_b, NULL);  // 创建接收消息的子线程

	pthread_join(tid1, NULL);  // 等待父线程执行完毕
	pthread_join(tid2, NULL);  // 等待子线程执行完毕

	return 0;  // 程序正常结束
}
进程B:
#include <myhead.h>  

// 定义消息缓冲区结构体,包含消息类型和消息正文
struct msgbuf
{
	long mtmype;  // 消息类型,必须是长整型
	char mtext[128];  // 消息正文,最多128个字符
};

#define MSGSZ (sizeof(struct msgbuf) - sizeof(long))  // 计算消息正文的大小,不包含消息类型

// 发送消息到A进程的函数,作为子线程的执行体
void *send_to_a(void *arg)
{
	key_t key = ftok("/", 'I');  // 使用ftok生成唯一的key
	if (key == -1)  // 错误检查
	{
		perror("ftok error");  // 打印ftok调用错误信息
		pthread_exit(NULL);  // 线程退出
	}
	printf("key=%#x\n", key);  // 打印生成的key

	int msqid = msgget(key, IPC_CREAT | 0664);  // 创建或获取消息队列
	if (msqid == -1)  // 错误检查
	{
		perror("msgget error");  // 打印msgget调用错误信息
		pthread_exit(NULL);  // 线程退出
	}
	printf("msqid=%d\n", msqid);  // 打印消息队列ID

	struct msgbuf buf;  // 定义发送消息的缓冲区

	while (1)
	{
		printf("请输入消息类型>>>(只能输入2)");
		scanf("%ld", &buf.mtmype);  // 获取消息类型
		getchar();  // 清除输入缓冲区中的换行符
		if (buf.mtmype != 2)  // 如果消息类型不是2,提示重新输入
		{
			printf("输入有误,请重新输入\n");
			continue;  // 重新输入
		}
		printf("请输入消息正文>>>");
		fgets(buf.mtext, MSGSZ, stdin);  // 获取消息正文
		buf.mtext[strlen(buf.mtext) - 1] = 0;  // 去掉最后的换行符

		msgsnd(msqid, &buf, MSGSZ, 0);  // 将消息发送到消息队列
		printf("发送成功\n");  // 提示发送成功

		if (strcmp(buf.mtext, "quit") == 0)  // 如果消息内容为"quit",退出循环
		{
			break;
		}
	}
	pthread_exit(NULL);  // 线程正常退出
}

// 接收来自A进程消息的函数,作为父线程的执行体
void *receive_from_a(void *arg)
{
	key_t key = ftok("/", 'I');  // 生成唯一的key
	if (key == -1)  // 错误检查
	{
		perror("ftok error");  // 打印ftok调用错误信息
		pthread_exit(NULL);  // 线程退出
	}

	printf("key=%#x\n", key);  // 打印生成的key

	int msqid = msgget(key, IPC_CREAT | 0664);  // 获取或创建消息队列
	if (msqid == -1)  // 错误检查
	{
		perror("msgget error");  // 打印msgget调用错误信息
		pthread_exit(NULL);  // 线程退出
	}

	printf("msqid=%d\n", msqid);  // 打印消息队列ID

	struct msgbuf buf;  // 定义接收消息的缓冲区
	
	while (1)
	{
		msgrcv(msqid, &buf, MSGSZ, 1, 0);  // 从消息队列中接收类型为1的消息
		printf("收到消息为%s\n", buf.mtext);  // 打印收到的消息内容

		if (strcmp(buf.mtext, "quit") == 0)  // 如果收到的消息为"quit",退出循环
		{
			break;
		}
	}

	if (msgctl(msqid, IPC_RMID, NULL) == -1)  // 删除消息队列
	{
		perror("msgctl error");  // 打印msgctl调用错误信息
		pthread_exit(NULL);  // 线程退出
	}
	
	pthread_exit(NULL);  // 线程正常退出
}

// 主函数
int main(int argc, const char *argv[])
{
	pthread_t tid1, tid2;  // 定义两个线程ID变量

	pthread_create(&tid1, NULL, receive_from_a, NULL);  // 创建接收消息的父线程
	pthread_create(&tid2, NULL, send_to_a, NULL);  // 创建发送消息的子线程

	pthread_join(tid1, NULL);  // 等待父线程执行完毕
	pthread_join(tid2, NULL);  // 等待子线程执行完毕

	return 0;  // 程序正常结束
}

成果展示:

方法一:进程法

方法二:线程法

三、总结

学习内容概述

1. 信号通信:

信号处理的三种方式(默认处理、捕获、忽略)、常见信号类型及其处理、如何使用 `signal` 函数绑定信号处理方式、通过 `alarm` 启动定时器,以及使用 `kill` 和 `raise` 发送信号给进程。

2. System V 提供的 IPC 机制:

包括消息队列的创建、发送、接收及控制函数的使用,重点学习了如何通过 `ftok` 函数生成消息队列的 `key`,以及消息队列的存取操作(`msgsnd` 和 `msgrcv`)。

学习难点

1. 信号处理的机制与应用:

1、理解信号的捕获机制和如何使用 `signal` 函数绑定信号处理函数是较为复杂的部分,尤其是不同信号的处理行为,如 SIGPIPE、SIGSEGV、SIGINT 等的触发条件和处理方式。

2、理解信号的实时性及不可排队处理。多个相同信号发送到一个进程时,可能会导致一些信号丢失,这要求处理程序具有足够的容错能力。

2. 消息队列的使用:

1、理解消息队列的结构体 `msgbuf` 的设计,以及消息类型 `mtype` 的特殊要求(必须为大于 0 的值)。

2、学习如何根据消息类型有选择地从消息队列中接收消息(通过 `msgrcv` 的 `msgtyp` 参数指定),并处理消息队列中的并发与顺序问题。

3、控制消息队列的操作,如 `msgctl` 用于删除或查看消息队列的状态,这在多进程协作中是一个重要但复杂的环节。

主要事项

1. 信号通信的使用:

信号的捕获与处理:

信号是操作系统发送给进程的异步通知,通过 `signal` 函数,进程可以指定自定义的信号处理函数来捕获并处理特定的信号。

常见信号处理:

例如 `SIGINT` 用于捕捉用户按下 `Ctrl+C` 产生的中断信号,`SIGPIPE` 用于处理写管道断裂,`SIGALRM` 是通过 `alarm` 函数生成的定时信号。

信号的发送:

通过 `kill` 可以向指定进程发送信号,`raise` 则用于向当前进程发送信号。信号可以用于进程间的简单通信和控制。

2. System V IPC 机制中的消息队列:

创建消息队列:

通过 `msgget` 函数创建消息队列,`ftok` 函数用于生成消息队列的唯一标识 `key`,确保不同进程可以共享同一个队列。

消息的发送与接收:

使用 `msgsnd` 向队列发送消息,使用 `msgrcv` 从队列中读取消息。每条消息都必须指定一个类型 `mtype`,用于标识消息的优先级或类别。

消息队列的控制:

`msgctl` 提供了一些管理消息队列的功能,如删除队列或查看队列状态,确保队列不再使用时能够清理资源。

未来学习的重点:

1. 深入理解信号的并发处理:

1、学习如何处理多个信号的并发情况,避免重要信号被忽略或丢失,特别是在高并发环境下如何避免信号混乱。同时学习如何使用 `sigaction` 替代 `signal` 进行更精细的信号处理控制。

2、学习更多系统信号的具体作用和场景应用,如 `SIGCHLD` 用于处理子进程退出,`SIGKILL` 强制结束进程等。

2. 高级 IPC 机制:

1、继续深入学习其他 System V IPC 机制,如共享内存(`shmget`、`shmat`)和信号量(`semget`、`semop`),这些是进程间通信的重要方式。

2、理解消息队列的并发访问控制,通过适当的同步机制(如信号量或锁)保证多进程之间的顺序操作和数据一致性。

3. 实战应用与优化:

1、将所学的信号处理机制与 IPC 机制结合应用于实际项目中,如创建一个简单的多进程任务管理系统,使用信号和消息队列进行进程间的协调和通信。

2、学习如何优化 IPC 性能,特别是在多进程并发较高的场景中,如何减少 IPC 操作的延迟,提升消息队列的处理效率。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/898931.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot1~~~

目录 快速入门 依赖管理和自动配置 修改自动仲裁/默认版本号 starter场景启动器 自动配置 修改默认扫描包结构 修改默认配置 读取application.properties文件 按需加载原则 容器功能 Configuration Import ​编辑 Conditional ImportResource 配置绑定Configur…

华为云购买弹性云服务器(教程)

配置弹性云服务器 基础配置 实例 操作系统

『完整代码』坐骑召唤

创建一个按钮 作为召唤/消失坐骑的开关 将预制体放入指定文件夹 命名为Mount01 创建脚本并编写&#xff1a;CallMount.cs using UnityEngine; using UnityEngine.UI; public class CallMount : MonoBehaviour{public Button callBtn;GameObject mountPrefab;GameObject mountIn…

信息搜集 --子域名

1.证书查询 通过ssl证书指纹在crt.sh |证书搜索网站搜索 这些就是证书一样的 2.fofa等空间测绘平台查询 3.dns查询 https://dnsdumpster.com/ 4.威胁情报中心 360 微步等等 5.枚举 暴力破解 工具推荐&#xff1a;oneforall GitHub - shmilylty/OneForAll: OneForAll是一款…

windows 上面交叉编译 适合arm架构上的linux内核系统的qt 版本,源码编译

1. 在机器上确认系统信息 cat /proc/cpuinfomodel name : ARMv7 Processor rev 5 (v7l) arm 32位 BogoMIPS : 57.14 Features : swp half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 CPU implementer : 0x41 CPU architecture: 7 …

【Linux】线程互斥与同步,生产消费模型(超详解)

目录 线程互斥 进程线程间的互斥相关背景概念 数据不一致问题 锁 深度理解锁 原理角度理解&#xff1a; 实现角度理解&#xff1a; 线程同步 条件变量 测试代码 生产消费模型 生产消费模型概念 编写生产消费模型 BlockingQueue &#xff08;1&#xff09;创建生产…

双十一宠物空气净化器哪款吸毛好而且噪音低?希喂、IAM、有哈真实测评

家人们谁懂啊&#xff0c;家里怎么会有一只这么爱掉毛的小猫咪啊&#xff0c;看着香香软软的&#xff0c;谁知道掉起毛来六亲不认啊&#xff0c;搞得我这个老母亲筋疲力尽啊&#xff0c;每天只想着清理它掉下来的浮毛&#xff0c;主要是还特别难清理。 所以后面入手了能吸毛的…

OpenAI o1复现:自动构造prm训练数据-OmegaPRM

作者&#xff1a;cmathx 原文&#xff1a;https://zhuanlan.zhihu.com/p/1477078851 openai o1复现中&#xff0c;有个比较关键的问题&#xff0c;怎么样自动化构造prm模型的训练数据&#xff1f;本文主要从代码层面&#xff0c;来解析OmegaPRM原理。 论文 Improve Mathemat…

Discuz | 起尔开发 传奇开服表游戏公益服发布论坛网站插件

Discuz | 起尔开发 传奇开服表游戏公益服发布论坛网站插件 插件下载&#xff1a;源码 - 起尔开发的插件下载 演示地址&#xff1a;discuz.72jz.com 标黄和非标黄自动分开 在标黄时间内显示在上面置顶&#xff0c;标黄过期后自动显示在下面白色区域。 后台可以设置非标黄默认…

四、多线程带来的的⻛险-线程安全

4.1 观察线程不安全 运行以下代码&#xff1a; package demo02;public class Test {private static int count 0;public static void main(String[] args) throws Exception {Thread t1 new Thread(() -> {for (int i 0; i < 50_000; i) {count;}});Thread t2 new …

通过Docker Compose构建自己的Java项目

通过Docker Compose构建自己的Java项目 前置条件 安装了Docker,未安装的请移步:CentOS7 / CentOS8 安装 Docker-ce安装了Docker-Compose,未安装的请移步:在CentOS7、CentOS8系统下安装Docker Compose1. 配置阿里云镜像仓库 为了提高Docker镜像的下载速度,我们可以配置阿…

版本工具报错:Error Unity Version Control

NotConfiguredClientException: Unity VCS client is not correctly configured for the current user:Client config file.

python 爬虫 入门 三、登录以及代理。

目录 一、登录 &#xff08;一&#xff09;、登录4399 1.直接使用Cookie 2.使用账号密码进行登录 可选观看内容&#xff0c;使用python对密码进行加密&#xff08;无结果代码&#xff0c;只有过程分析&#xff09; 二、代理 免费代理 后续&#xff1a;协程&#xff0c;…

TitanIDE:解锁编程教学新范式

在高校软件工程类课程教育中&#xff0c;传统编程教学方式正面临着多重痛点&#xff1a; 环境配置繁琐&#xff1a;软件工程类课程往往需要学生自行配置复杂的开发环境。但是&#xff0c;学校硬件设备条件差异、软件兼容性问题等因素&#xff0c;导致学生学习效率低下&#xf…

热销王西圣H1头戴式耳机—全平台售罄断货:揭秘抢购潮究其原因?

西圣xisem作为国内平价享轻奢的领军品牌&#xff0c;就在今年它家的头戴式蓝牙耳机性价比标杆—西圣H1&#xff0c;凭借其发烧级的千元音质、降噪与满级的旗舰配置性能&#xff0c;不仅惊艳了整个耳机圈&#xff0c;还在仅仅的几个月内&#xff0c;西圣H1头戴式耳机已经火爆断货…

python 使用gradio启动程序报错

问题一&#xff1a;localhost is not accessible 解决办法&#xff1a; export no_proxy"localhost,127.0.0.1,::1"

C#学习笔记(三)

C#学习笔记&#xff08;三&#xff09; 第 二 章 命名空间和类、数据类型、变量和代码规范二、类的组成和使用分析1. 基本概念2. 类的内容组成3. 方法的初步理解 第 二 章 命名空间和类、数据类型、变量和代码规范 二、类的组成和使用分析 1. 基本概念 类是程序的基本单元&a…

PostgreSQL中触发器递归的处理 | 翻译

许多初学者在某个时候都会陷入触发器递归的陷阱。通常&#xff0c;解决方案是完全避免递归。但对于某些用例&#xff0c;您可能必须处理触发器递归。本文将告诉您有关该主题需要了解的内容。如果您曾经被错误消息“超出堆栈深度限制”所困扰&#xff0c;那么这里就是解决方案。…

Javascript算法——二分查找

1.数组 1.1二分查找 1.搜索索引 开闭matters&#xff01;&#xff01;&#xff01;[left,right]与[left,right) /*** param {number[]} nums* param {number} target* return {number}*/ var search function(nums, target) {let left0;let rightnums.length-1;//[left,rig…

大话网络协议:从OSI七层模型说开去

时至今日,互联网已经是大家日常生活中不可或缺的一部分,购物、点餐、刷剧、网课,已经融入了我们生活的方方面面。但网络具体是怎么工作的呢? 特别是我们具体从事软件研发、ICT行业的同学,理解和掌握这个我们产品运行的基础设施尤为必要。 本文,我们会力争用最简单易懂的…