操作系统实验——利用Linux的消息队列通信机制实现两个线程间的通信
目录
一. 题目描述
编写程序创建三个线程:sender1线程、sender2线程和receive线程,三个线程的功能描述如下:
①sender1线程:运行函数sender1(),它创建一个消息队列,然后等待用户通过终端输入一串字符,并将这串字符通过消息队列发送给receiver线程;可循环发送多个消息,直到用户输入“exit”为止,表示它不再发送消息,最后向receiver线程发送消息“end1”,并且等待receiver的应答,等到应答消息后,将接收到的应答信息显示在终端屏幕上,结束线程的运行。
②sender2线程:运行函数sender2(),共享sender1创建的消息队列,等待用户通过终端输入一串字符,并将这串字符通过消息队列发送给receiver线程;可循环发送多个消息,直到用户输入“exit”为止,表示它不再发送消息,最后向receiver线程发送消息“end2”,并且等待receiver的应答,等到应答消息后,将接收到的应答信息显示在终端屏幕上,结束线程的运行。
③Receiver线程:运行函数receive(),它通过消息队列接收来自sender1和sender2两个线程的消息,将消息显示在终端屏幕上,当收到内容为“end1”的消息时,就向sender1发送一个应答消息“over1”;当收到内容为“end2”的消息时,就向sender2发送一个应答消息“over2”;消息接收完成后删除消息队列,结束线程的运行。选择合适的信号量机制实现三个线程之间的同步与互斥。
二.实验思路
首先明确线程的功能,实验要求要有两个sender线程发送和一个receive线程进行接收
Receive:接收sender线程发送的信息并显示在终端,当接收到sender线程发送的end后,回应一个over并释放over信号。
Sender:发送消息,在用户输入exit后,sender发送end,接收receive线程的over信号。
信号量:
//互斥信号量mutex,互斥使用消息队列
sem_init(&mutex, 0, 1);
//同步信号量full,empty
sem_init(&full, 0, 0);
sem_init(&empty, 0, 5);
//用over信号判断某个sender线程的结束
sem_init(&over, 0, 0);
//display信号量实际作用只是为了能让sender线程和receive线程实现一发一收的格式
//不使用这两个信号量可能会无法保证线程的执行顺序
sem_init(&s_display, 0, 1);
sem_init(&r_display, 0, 0);
图1.线程关系

三.代码及实验结果
/*
2022/11/11
author:chenchen4396
*/
//API参考手册:https://www.bookstack.cn/read/linuxapi/SUMMARY.md
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <semaphore.h>
#include <fcntl.h>
pthread_t s1, s2, r;
sem_t mutex;
sem_t full, empty;
sem_t over;
sem_t s_display, r_display;
struct msgbuff
{
long msg_type; //消息类型,可以自定义,在使用msgrcv函数的时候会使用
char msg[100]; //消息数组大小
};
void receive(void *arg)
{
struct msgbuff buf;
int flag1 = 1;
int flag2 = 1;
int ret = -1;
while ((flag1 + flag2) != 0)
{
//初始化消息队列
memset(&buf, 0, sizeof(buf));
//互斥信号量mutex,资源信号量full
// mutex和full的wait顺序不能调换,否则会发生死锁,参考生存者与消费者模型
sem_wait(&r_display);
sem_wait(&full);
sem_wait(&mutex);
// msgrcv接受消息
// arg是传入的msgid
// 1代表msgtyp,指定一个接受的消息类型
// IPC_NOWAIT 如果需要等待,则不发送消息并且调用进程立即返回,避免阻塞
ret = msgrcv(*(int *)arg, &buf, sizeof(buf.msg), 1, IPC_NOWAIT);
if (strlen(buf.msg) != 0)
{
printf("receive: %s \n", buf.msg); //打印接收到的内容
printf("-----------------------------------------\n");
}
//判断sender1和sender2是否发送end
if (flag1 && strncmp(buf.msg, "end1", 4) == 0)
{
memset(&buf, 0, sizeof(buf));
strcpy(buf.msg, "over1");
//使over消息的msgtyp为2,方便分开接受
buf.msg_type = 2;
ret = msgsnd(*(int *)arg, &buf, sizeof(buf.msg), 0);
if (ret == -1)
{
printf("sender1 msgsnd error\n");
sem_post(&mutex);
exit(1);
}
sem_post(&over);
flag1 = 0;
}
else if (flag2 && strncmp(buf.msg, "end2", 4) == 0)
{
memset(&buf, 0, sizeof(buf));
strcpy(buf.msg, "over2");
buf.msg_type = 2;
ret = msgsnd(*(int *)arg, &buf, sizeof(buf.msg), 0);
if (ret == -1)
{
printf("sender2 msgsnd error\n");
sem_post(&mutex);
exit(1);
}
sem_post(&over);
flag2 = 0;
}
sem_post(&mutex);
sem_post(&empty);
sem_post(&s_display);
sleep(1);
}
printf("sender1 and sender2 all over!\n");
}
void sender1(void *arg)
{
struct msgbuff buf;
int flag = 1;
int ret = -1;
while (flag)
{
memset(&buf, 0, sizeof(buf));
sem_wait(&s_display);
sem_wait(&empty);
sem_wait(&mutex);
printf("sender1> ");
scanf("%s", buf.msg);
buf.msg_type = 1;
if (!strncmp(buf.msg, "exit", 4))
{
strcpy(buf.msg, "end1");
flag = 0;
}
else
{
strcat(buf.msg, " ——s1");
}
ret = msgsnd(*(int *)arg, &buf, sizeof(buf.msg), IPC_NOWAIT);
if (ret == -1)
{
printf("sender1 msgsnd error\n");
sem_post(&mutex);
exit(1);
}
sem_post(&r_display);
sem_post(&full);
sem_post(&mutex);
sleep(1);
}
sem_wait(&over);
sem_wait(&empty);
sem_wait(&mutex);
ret = msgrcv(*(int *)arg, &buf, sizeof(buf.msg), 2, IPC_NOWAIT);
if (strncmp(buf.msg, "over1", 5) == 0)
{
sem_post(&full);
sem_post(&mutex);
pthread_exit(NULL);
}
}
void sender2(void *arg)
{
struct msgbuff buf;
int flag = 1;
int ret = -1;
while (flag)
{
//初始化buf
memset(&buf, 0, sizeof(buf));
//等待信号量,申请顺序有些不能反!参考生产者消费者死锁问题
sem_wait(&s_display);
sem_wait(&empty);
sem_wait(&mutex);
printf("sender2> ");
scanf("%s", buf.msg);
//设置msg_type为1,与msgrcv第四个参数有关
buf.msg_type = 1;
if (!strncmp(buf.msg, "exit", 4)){
//当sender1输入exit,则对receive发送end1
strcpy(buf.msg, "end2");
flag = 0;
}
else{
strcat(buf.msg, " ——s2");
}
// msgsnd添加消息到队列
ret = msgsnd(*(int *)arg, &buf, sizeof(buf.msg), IPC_NOWAIT);
if (ret == -1){
printf("sender2 msgsnd error\n");
sem_post(&mutex);
exit(1);
}
//释放信号量
sem_post(&r_display);
sem_post(&full);
sem_post(&mutex);
sleep(1);
}
//接受over信号
sem_wait(&over);
sem_wait(&empty);
sem_wait(&mutex);
ret = msgrcv(*(int *)arg, &buf, sizeof(buf.msg), 2, IPC_NOWAIT);
if (strncmp(buf.msg, "over2", 5) == 0)
{
sem_post(&full);
sem_post(&mutex);
pthread_exit(NULL);
}
}
/*
初始化信号量
sem为初始化的信号量名,pshare为0表示只在该进程的所有线程间共享,非0则可以在进程之间共享,value为信号量的大小
kernel源码中有static inline void sema_init(struct semaphore *sem, int val)
sema_init是Linux内核的计数信号量实现初始化函数。
sem_init是Posix线程库的初始化程序(因此被用户空间代码使用)
*/
int main()
{
//互斥信号量mutex,互斥使用消息队列
sem_init(&mutex, 0, 1);
//同步信号量full,empty
sem_init(&full, 0, 0);
sem_init(&empty, 0, 5);
//用over信号判断某个sender线程的结束
sem_init(&over, 0, 0);
//display信号量实际作用只是为了能让sender线程和receive线程实现一发一收的格式
//不使用这两个信号量可能会无法保证线程的执行顺序
sem_init(&s_display, 0, 1);
sem_init(&r_display, 0, 0);
// 可以用判断一下信号量是否初始化成功,sem_getvalue(&mutex,&x);
int ret = 1; //返回值
key_t key = 100;//消息队列的key值
//IPC_CREAT表示key值不存在就创建,0666是设置权限
int msgid = msgget(key, IPC_CREAT | 0666);
if (msgid == -1)
{
printf("msgget error\n");
exit(1);
}
// pthread_create创建三个线程
//第二个参数用来 设置线程的属性,设为NULL
//第四个参数为传入执行函数的实参
ret = pthread_create(&s1, NULL, sender1, (void *)&msgid);
if (ret != 0)
{
printf("create sender1 error!\n");
exit(1);
}
ret = pthread_create(&r, NULL, receive, (void *)&msgid);
if (ret != 0)
{
printf("create receiver error!\n");
exit(1);
}
ret = pthread_create(&s2, NULL, sender2, (void *)&msgid);
if (ret != 0)
{
printf("create sender2 error!\n");
exit(1);
}
// pthread_join使三个进程阻塞
//为了回收资源,主线程会等待子线程结束。该函数就是用来等待线程终止的
pthread_join(s1, NULL);
pthread_join(s2, NULL);
pthread_join(r, NULL);
//删除消息队列
msgctl(msgid, IPC_RMID, 0);
return 0;
}
图2.实验结果

四.遇到问题及解决方法
1.信号量的申请顺序不对,导致程序发生死锁,full信号和互斥型号量mutex申请顺序不对,造成了死锁,类似生产者消费者的经典死锁问题。
2.在未使用s_display和r_display信号量对线程执行顺序进行约束的时候,线程的执行顺序得不到保证,增加这两个信号量后,可以有效保证在sender线程后会执行receive线程。
3.信号量的使用和初始化要很注意,该实验的很多bug是来源于信号量的使用不恰当。
4.msgrcv 此类函数中的参数IPC_NOWAIT要注意,如果不设置此参数,会导致进程被挂起直到有消息进入队列。
ps:Sleep()是多线程任务常用的一个函数,通过sleep()可以让线程直接告诉操作系统结束时间片进入休眠,在部分情况下可能可以提升很大的CPU利用率。两个display同步信号量可以让sender线程执行完成后就是receive线程,达成一发一收的效果。
五.参考文献
1 linux 多线程之信号量[EB/OL]. [].
【Linux】多线程 之 信号量_am brother的博客-CSDN博客.
2 . linux 多线程信号量[EB/OL]. [].
linux 多线程之信号量 sem_init_岁月斑驳7的博客-CSDN博客.
3 . linux API速查手册[EB/OL]. [].
https://www.bookstack.cn/read/linuxapi/SUMMARY.md.
4. . Linux源码[EB/OL]. [].
https://elixir.bootlin.com/linux/v4.16.3/source.
var code = "771885a0-7704-4c6b-bb9b-490157eeb898"
操作系统实验——利用Linux的消息队列通信机制实现两个线程间的通信的更多相关文章
- 【转】Java学习---线程间的通信
[原文]https://www.toutiao.com/i6572378564534993415/ 两个线程间的通信 这是我们之前的线程. 执行效果:谁抢到资源,谁运行~ 实现线程交替执行: 这里主要 ...
- 【转】Java学习:Java中的线程之线程间的通信
hello各位小伙伴 今天我们来搞一下 线程之间的通信 ( • ̀ω•́ )✧ 让线程按照我们的想法来执行 两个线程间的通信 这是我们之前的线程. 执行效果:谁抢到资源,谁运行~ 实现线程交替执行: ...
- 利用System V消息队列实现回射客户/服务器
一.介绍 在学习UNIX网络编程 卷1时,我们当时可以利用Socket套接字来实现回射客户/服务器程序,但是Socket编程是存在一些不足的,例如: 1. 服务器必须启动之时,客户端才能连上服务端,并 ...
- Linux进程间通信-消息队列(mqueue)
前面两篇文章分解介绍了匿名管道和命名管道方式的进程间通信,本文将介绍Linux消息队列(posix)的通信机制和特点. 1.消息队列 消息队列的实现分为两种,一种为System V的消息队列,一种是P ...
- Android 12(S) 图形显示系统 - SurfaceFlinger的启动和消息队列处理机制(四)
1 前言 SurfaceFlinger作为Android图形显示系统处理逻辑的核心单元,我们有必要去了解其是如何启动,初始化及进行消息处理的.这篇文章我们就来简单分析SurfaceFlinger这个B ...
- Android中利用Handler实现消息的分发机制(三)
在第二篇文章<Android中利用Handler实现消息的分发机制(一)>中,我们讲到主线程的Looper是Android系统在启动App的时候,已经帮我们创建好了,而假设在子线程中须要去 ...
- Posix消息队列实现机制
本文是对<Unix 网络编程 卷2:进程通信>的笔记. 引言 消息队列是进程间通信的一种方式,可是如果不理解他的实现原理,会有众多不理解之处,下面就结合本书中的例子,对posix消息队列来 ...
- 【Scala】利用Akka的actor编程模型,实现2个进程间的通信
文章目录 步骤 一.创建maven工程,导入jar包 二.master进程代码开发 三.worker进程代码开发 四.控制台结果 步骤 一.创建maven工程,导入jar包 <propertie ...
- iOS边练边学--多线程NSOperation介绍,子类实现多线程的介绍(任务和队列),队列的取消、暂停(挂起)和恢复,操作依赖与线程间的通信
一.NSOperation NSOperation和NSOperationQueue实现多线程的具体步骤 先将需要执行的操作封装到一个NSOperation对象中 然后将NSOperation对象添加 ...
- 多线程,线程类三种方式,线程调度,线程同步,死锁,线程间的通信,阻塞队列,wait和sleep区别?
重难点梳理 知识点梳理 学习目标 1.能够知道什么是进程什么是线程(进程和线程的概述,多进程和多线程的意义) 2.能够掌握线程常见API的使用 3.能够理解什么是线程安全问题 4.能够知道什么是锁 5 ...
随机推荐
- 音视频八股文(12)-- ffmpeg 音频重采样
1重采样 1.1 什么是重采样 所谓的重采样,就是改变⾳频的采样率.sample format.声道数等参数,使之按照我们期望的参数输出. 1.2 为什么要重采样 为什么要重采样?当然是原有的⾳频参数 ...
- 2022-09-01:字符串的 波动 定义为子字符串中出现次数 最多 的字符次数与出现次数 最少 的字符次数之差。 给你一个字符串 s ,它只包含小写英文字母。请你返回 s 里所有 子字符串的 最大波
2022-09-01:字符串的 波动 定义为子字符串中出现次数 最多 的字符次数与出现次数 最少 的字符次数之差. 给你一个字符串 s ,它只包含小写英文字母.请你返回 s 里所有 子字符串的 最大波 ...
- 2020-11-01:rust中带move闭包和不带move闭包有什么区别?
福哥答案2020-11-01: 1.是否是同一个变量:带move闭包,函数外和函数内的同名变量不是同一个变量.不带move闭包,函数外和函数内的同名变量是同一个变量.2.执行完闭包后:带move闭包, ...
- 2022-04-04:k8s中kubectl源码用到了哪些设计模式?除了工厂和单例。
2022-04-04:k8s中kubectl源码用到了哪些设计模式?除了工厂和单例. 答案2022-04-04: 1.建造者模式.resource.Builder.D:\go_path\src\git ...
- 记一次,使用python实现一键在爱发电发布带图片的动态
1.背景 本人喜欢转载一些youtube上的视频到b站上面,然后就会有些观众想要视频的封面,那我总不可能一个一个发吧,太麻烦了.故打算将资源发布到爱发电上面.但是爱发电却没有公开对应的api,只能自己 ...
- 基于electron25+vite4创建多窗口|vue3+electron25新开模态窗体
在写这篇文章的时候,查看了下electron最新稳定版本由几天前24.4.0升级到了25了,不得不说electron团队迭代速度之快! 前几天有分享一篇electron24整合vite4全家桶技术构建 ...
- v8 study
v8环境搭建看这里 现在的v8采用的是Ignition(JIT生成) + TurboFan(优化) v8调试 安装pwngdb git clone https://github.com/pwndbg/ ...
- 文字生成图像 AI免费工具第一弹 StableDiffusion
随着ChatGPT的爆火,text-to-image文字生成图像.以及更广义的AIGC(AI Generated Content)相关的话题最近一直热度不减.相信大家这几天经常会在各类的自媒体.甚至是 ...
- 了解基于模型的元学习:Learning to Learn优化策略和Meta-Learner LSTM
摘要:本文主要为大家讲解基于模型的元学习中的Learning to Learn优化策略和Meta-Learner LSTM. 本文分享自华为云社区<深度学习应用篇-元学习[16]:基于模型的元学 ...
- requests Python中最好用的网络请求工具 基础速记+最佳实践
简介 requests 模块是写python脚本使用频率最高的模块之一.很多人写python第一个使用的模块就是requests,因为它可以做网络爬虫.不仅写爬虫方便,在日常的开发中更是少不了requ ...