6.5 KiB
6.5 KiB
实验一 进程与线程——Linux 进程与线程通信
一、实验内容
深刻理解线程和进程的概念,掌握线程与进程在组成成分上的差别,以及与其相适应的通信方式和应用目标。
以 Linux 系统进程和线程机制为背景,掌握 fork() 和 clone() 系统调用的形式和功能,以及与其相适应的高级通信方式:
- 由 fork() 派生的子进程之间通过 pipe 通信
- 由 clone() 创建的线程之间通过共享内存通信(需要考虑互斥问题)
以生产者-消费者问题为例,实现创建 4 个进程或线程,其中包括两个生产者和两个消费者,生产者和消费者之间能够传递数据。
二、实验设计
2.1 基于 fork() 的进程版本
函数设计:
main()- 主函数,创建管道和4个子进程producer(int id)- 生产者函数,向管道写入数据consumer(int id)- 消费者函数,从管道读取数据
调用关系:
main()
├── pipe() 创建管道
├── fork() → producer(1)
├── fork() → producer(2)
├── fork() → consumer(1)
├── fork() → consumer(2)
└── wait() 等待所有子进程结束
通信机制:
- 使用 pipe() 创建匿名管道
- 生产者关闭管道读端,写入 w_buf 内容
- 消费者关闭管道写端,读取 r_buf 内容
2.2 基于 clone() 的线程版本
函数设计:
main()- 主函数,初始化互斥锁和信号量,创建4个线程producer(void *args)- 生产者线程函数consumer(void *args)- 消费者线程函数
调用关系:
main()
├── pthread_mutex_init() 初始化互斥锁
├── sem_init() 初始化信号量
├── clone(producer) 创建生产者线程1
├── clone(consumer) 创建消费者线程1
├── clone(producer) 创建生产者线程2
├── clone(consumer) 创建消费者线程2
└── exit() 退出
通信机制:
- 使用共享内存 buffer[8][4] 存放数据
- 使用 pthread_mutex_t mutex 进行互斥保护
- 使用 sem_t product/warehouse 进行同步
三、编码实现
3.1 fork() 版本修改要点
-
头文件:需要
sys/types.h、sys/file.h、unistd.h -
全局变量:
pipe_fd[2]- 管道文件描述符r_buf[4]、w_buf[4]- 读写缓冲区
-
生产者实现:
- 关闭管道读端
close(pipe_fd[0]) - 循环写入数据
write(pipe_fd[1], w_buf, 4) - 每3秒生产一个数据
- 关闭管道读端
-
消费者实现:
- 关闭管道写端
close(pipe_fd[1]) - 循环读取数据
read(pipe_fd[0], r_buf, 4) - 读到0字节表示管道关闭,退出循环
- 关闭管道写端
3.2 clone() 版本修改要点
-
头文件:需要
sched.h、pthread.h、semaphore.h -
共享资源初始化:
pthread_mutex_init(&mutex, NULL); sem_init(&product, 0, 0); sem_init(&warehouse, 0, 8); -
clone() 标志位:
clone_flag = CLONE_VM | CLONE_SIGHAND | CLONE_FS | CLONE_FILES;表示共享地址空间、信号处理、文件系统信息和文件描述符
-
生产者实现:
- P操作申请仓库空位
sem_wait(&warehouse) - 加锁写入共享缓冲区
pthread_mutex_lock(&mutex) - V操作通知产品
sem_post(&product)
- P操作申请仓库空位
-
消费者实现:
- P操作等待产品
sem_wait(&product) - 加锁读取共享缓冲区
pthread_mutex_lock(&mutex) - V操作释放仓库空位
sem_post(&warehouse)
- P操作等待产品
3.3 编译命令
# fork 版本
g++ exp01_source.cpp -o exp01_fork
# clone 版本
g++ exp01_clone.cpp -lpthread -o exp01_clone
四、实验结果
4.0 测试命令
# 编译 fork 版本
g++ exp01_source.cpp -o exp01_fork
# 编译 clone 版本
g++ exp01_clone.cpp -lpthread -o exp01_clone
# 运行 fork 版本(等待约30秒后自动结束)
./exp01_fork
# 运行 clone 版本(等待约20秒后自动结束)
./exp01_clone
4.1 fork() 版本运行结果
pipe is created successfully!
producer 1 is running!
producer 2 is running!
consumer 2 is running!
consumer 1 is running!
consumer 1 get aaa, while the w_buf is ccc
consumer 2 get bbb, while the w_buf is ddd
consumer 1 get aaa, while the w_buf is ccc
consumer 2 get bbb, while the w_buf is ddd
...
producer 1 is over!
producer 2 is over!
consumer 1 is over!
consumer 2 is over!
结果说明:
- 4个进程并发运行,进程间独立地址空间
- 通过管道传递数据 "aaa" 和 "bbb"
- 消费者获取的数据与生产者写入的一致
- 每个进程独立运行,无数据竞争
4.2 clone() 版本运行结果
producer0 produce aaa in 0
producer1 produce bbb in 1
consumer0 get aaa in 1
consumer1 get bbb in 0
producer0 produce aaa in 2
producer1 produce bbb in 3
consumer0 get aaa in 3
consumer1 get bbb in 2
...
producer0 is over!
producer1 is over!
consumer0 is over!
consumer1 is over!
结果说明:
- 4个线程共享地址空间,共享 buffer[8][4]
- 互斥锁保护共享缓冲区的读写操作
- 信号量实现生产者和消费者的同步
- 线程间数据一致,无竞争条件
五、实验结果思考与体会
5.1 fork() 与 clone() 的区别
| 特性 | fork() | clone() |
|---|---|---|
| 地址空间 | 独立 | 可共享 |
| 创建开销 | 较大(复制整个进程) | 较小(可共享资源) |
| 通信方式 | 管道/消息队列等 | 共享内存(天然支持) |
| 同步需求 | 需要额外同步机制 | 可直接同步 |
5.2 思考问题解答
问题1:用 shm/msg 实现生产者-消费者
可以使用共享内存(shmget/shmat)替代 clone() 的共享内存机制,用消息队列(msgget/msgsnd)替代管道实现生产者和消费者通信。
问题2:对比 pipe、clone、shm、msg 优缺点
- pipe:适用于有亲缘关系的进程,编程简单,但数据传输单向
- clone():创建灵活,可共享多种资源,但编程复杂
- shm:读写效率高,适合大数据共享,但需要同步
- msg:支持消息类型过滤,但大数据传输需要拆分
5.3 实验体会
通过本实验,深入理解了进程与线程的本质区别:
- 进程拥有独立的地址空间,适合需要隔离的任务 -线程共享地址空间,适合需要频繁通信和数据共享的任务
生产者-消费者问题是经典的同步问题,通过本实验掌握了:
- 进程/线程的创建方法
- 进程间通信(IPC)机制
- 线程间同步机制(互斥锁、信号量)
- 死锁和竞争条件的避免方法