Linux父子进程通信及进程同步
【实验目的】
- 理解进程通信的概念和方法。
- 掌握使用pipe()系统调用创建管道,实现父子进程间的信息通信的方法。
- 理解进程同步概念。
- 掌握信号量机制的原理。
- 掌握使用信号量及PV操作的使用方法,学会使用信号量解决同步、互斥问题。
【实验原理/实验基础知识】
- 进程间的通信
进程间的通信指进程间的信息交换。
- 进程通信的类型
- 低级进程通信。通信交换的信息量有限,通常是一些控制信息。例如,进程间同步、互斥等控制信息的传递。
- 高级进程通信。大量的信息传输过程。
- 共享存储器系统
- 消息传递系统(消息缓冲机制、信箱通信)
- 管道通信系统
- 管道通信系统
- 创建管道pipe系统调用
语法:int pipe(int pipefd[2])
参数:pipefd[1]写 ,pipefd[0] 读,数据只能(单向)沿pipefd[1]入(写)pipefd[0]出(读)。
返回值:0表示调用成功,-1表示调用失败。
注:读取空的pipe将导致读操作阻塞;向满的pipe写数据将导致写操作阻塞。
- 写入write系统调用
语法:write(pipefd[1],buf,size):
功能及参数:把buf中size长度的字符写入管道入口pipefd[1]。
- 读出read系统调用
语法:read(pipefd[0],buf,size)
功能及参数:从管道出口pipefd[0]读出size长度的字符置入buf。
- 管道锁定lockf系统调用
语法:int lockf(int fd, int cmd, off_t len);
参数fd:锁定对象
参数cmd:0(解锁),1(锁定)
参数off_t:锁定或解锁的字节数
- 进程同步
在多道系统中,一个进程相对于另一个进程的运行速度是不确定的,但相互合作的几个进程需要在某些情况下相互协调工作。
同步关系是指多个相互合作的进程,在某些情况下可能需要相互等待或相互交换信息,这种相互制约关系称为进程同步。
- 信号量及相关操作
- 信号量机制
信号量机制是一种进程通信方式,通过该信号量以阻塞及唤醒的方式实现进程之间的同步和互斥。Linux信号量分为POSIX信号量和System V信号量。以下为POSIX信号量相关操作。
- 信号量的创建
int sem_init(sem_t *sem,int pshared,unsigned int value)
- 信号量的删除
int sem_destroy(sem_t *sem)
- 信号量P操作
int sem_wait(sem_t *sem)
- 信号量V操作
int sem_post(sem_t *sem)
- 向缓冲区写产品
buffer=(char *)malloc(MAX); //给缓冲区分配内存空间
fgets(buffer,MAX,stdin); //输入产品至缓冲区
- 从缓冲区读产品
printf("read product from buffer:%s",buffer); //从缓冲区中取出产品
memset(buffer,0,MAX); //清空缓冲区
- 线程创建、等待线程结束
ret=pthread_create(&id_producer,NULL,producer,NULL); //创建生产者线程
ret=pthread_create(&id_consumer,NULL,consumer,NULL); //创建消费者线程
pthread_join(id_producer,NULL); //等待生产者线程结束
pthread_join(id_consumer,NULL); //等待消费者线程结束
【实验环境】VMware Workstation、RedHat
【实验步骤】
- 编写程序,实现管道通信机制。父进程生成子进程P1,P1进程向管道写入字符串,父进程读出管道中的字符串。
#include <semaphore.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#define MAX 256
char *buffer;
sem_t empty; //定义同步信号量empty
sem_t full; //定义同步信号量full
sem_t mutex; //定义互斥信号量mutex
void *producer(void *arg) //生产者
{
while (1)
{
// empty的P操作
sem_wait(&empty);
// mutex的P操作
sem_wait(&mutex);
printf("Input something to buffer:");
buffer = (char *)malloc(MAX); //给缓冲区分配内存空间
fgets(buffer, MAX, stdin); //输入产品至缓冲区
// mutex的V操作
sem_post(&mutex);
// full的V操作
sem_post(&full);
}
}
void *consumer(void *arg) //消费者
{
while (1)
{
// full的P操作
sem_wait(&full);
// mutex的P操作
sem_wait(&mutex);
printf("Read product from buffer: %s", buffer); //从缓冲区中取出产品
memset(buffer, 0, MAX); //清空缓冲区
// mutex的V操作
sem_post(&mutex);
// empty的V操作
sem_post(&empty);
}
}
int main()
{
pthread_t id_producer; // pthread_t :unsigned long int
pthread_t id_consumer;
int ret;
sem_init(&empty, 0, 10); //创建信号量,设置empty到初值为10
sem_init(&full, 0, 0); //创建信号量,设置full到初值为0
sem_init(&mutex, 0, 1); //创建信号量,设置mutex到初值为1
ret = pthread_create(&id_producer, NULL, producer, NULL); //创建生产者线程
ret = pthread_create(&id_consumer, NULL, consumer, NULL); //创建消费者线程
pthread_join(id_producer, NULL); //等待生产者线程结束
pthread_join(id_consumer, NULL); //等待消费者线程结束
sem_destroy(&empty); //删除同步信号量empty
sem_destroy(&full); //删除同步信号量full
sem_destroy(&mutex); //删除互斥信号量mutex
printf("The End...\n");
return 0;
}
- 使用信号量机制模拟解决生产者消费者问题。假设有一个生产者线程和一个消费者线程,缓冲区可以存放产品,生产者不断生产产品并存入缓冲区,消费者不断从缓冲区取出产品并消费。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#define BUFFER_SIZE 50
int main() {
int pipe_fd[2]; // 管道文件描述符数组,pipe_fd[0]用于读,pipe_fd[1]用于写
char buffer[BUFFER_SIZE];
// 创建管道
if (pipe(pipe_fd) == -1) {
perror("Pipe creation failed");
exit(EXIT_FAILURE);
}
pid_t pid = fork();
if (pid < 0) {
// fork 失败
perror("Fork failed");
exit(EXIT_FAILURE);
} else if (pid == 0) {
// 子进程 P1 写入字符串到管道
close(pipe_fd[0]); // 关闭读取端
char message[] = "Hello, parent! I am P1.";
write(pipe_fd[1], message, strlen(message) + 1); // 包括字符串末尾的 NULL 字符
close(pipe_fd[1]); // 关闭写入端
exit(EXIT_SUCCESS);
} else {
// 父进程读取字符串从管道
close(pipe_fd[1]); // 关闭写入端
// 读取字符串
read(pipe_fd[0], buffer, sizeof(buffer));
// 输出读取到的字符串
printf("Parent received message from P1: %s\n", buffer);
close(pipe_fd[0]); // 关闭读取端
// 等待子进程退出
wait(NULL);
}
return 0;
}
【实验报告】
填写《上机实验报告》。
【思考题】
- 实验(1)中,若父进程生成子进程P1、P2,这两个进程分别向管道写入各自的字符串,如何保证P1向管道写数据时P2不能用管道?
答:使用进程同步机制来确保P1和P2的执行顺序.可以使用信号量(信号量)或互斥锁(互斥)来实现进程同步.例如,当P1向管道写入数据时,可以设置一个信号量来控制P2对管道的访问.只有当P1完成写入后,信号量才会被释放,从而允许P2使用管道.
- 用多线程实现生产者消费者问题与用多进程实现该问题有何区别?
答:
多线程实现:
(1)多线程实现生产者消费者问题通常使用共享内存作为公共资源。
(2)线程间的同步可以通过使用互斥锁(mutexes)、条件变量(condition variables)等机制来实现。
(3)由于所有线程都在同一个进程内,因此它们可以方便地共享内存变量、文件描述符等资源。这也意味着线程间的通信和同步更加容易实现。
(4)但是,如果线程数量过多或者线程竞争过于激烈,可能会导致频繁的上下文切换和调度延迟,从而降低性能。
多进程实现:
(1)多进程实现生产者消费者问题通常使用消息队列或其他形式的通信通道作为公共资源。
(2)进程间的同步和通信通常通过使用消息队列、管道、信号量等机制来实现。
(3)由于每个进程都有自己的内存空间和资源,因此它们之间的通信和同步需要更多的工作。这也意味着多进程实现更加复杂和困难。
(4)但是,多进程实现的并发性能通常比多线程实现更高,因为每个进程有自己的地址空间和资源,减少了上下文切换和调度延迟的影响。此外,多进程实现也更适合处理大规模并行任务。