Files
2026-06-25 00:09:09 +08:00

6.5 KiB
Raw Permalink Blame History

实验一 进程与线程——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() 版本修改要点

  1. 头文件:需要 sys/types.hsys/file.hunistd.h

  2. 全局变量

    • pipe_fd[2] - 管道文件描述符
    • r_buf[4]w_buf[4] - 读写缓冲区
  3. 生产者实现

    • 关闭管道读端 close(pipe_fd[0])
    • 循环写入数据 write(pipe_fd[1], w_buf, 4)
    • 每3秒生产一个数据
  4. 消费者实现

    • 关闭管道写端 close(pipe_fd[1])
    • 循环读取数据 read(pipe_fd[0], r_buf, 4)
    • 读到0字节表示管道关闭退出循环

3.2 clone() 版本修改要点

  1. 头文件:需要 sched.hpthread.hsemaphore.h

  2. 共享资源初始化

    pthread_mutex_init(&mutex, NULL);
    sem_init(&product, 0, 0);
    sem_init(&warehouse, 0, 8);
    
  3. clone() 标志位

    clone_flag = CLONE_VM | CLONE_SIGHAND | CLONE_FS | CLONE_FILES;
    

    表示共享地址空间、信号处理、文件系统信息和文件描述符

  4. 生产者实现

    • P操作申请仓库空位 sem_wait(&warehouse)
    • 加锁写入共享缓冲区 pthread_mutex_lock(&mutex)
    • V操作通知产品 sem_post(&product)
  5. 消费者实现

    • P操作等待产品 sem_wait(&product)
    • 加锁读取共享缓冲区 pthread_mutex_lock(&mutex)
    • V操作释放仓库空位 sem_post(&warehouse)

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 实验体会

通过本实验,深入理解了进程与线程的本质区别:

  • 进程拥有独立的地址空间,适合需要隔离的任务 -线程共享地址空间,适合需要频繁通信和数据共享的任务

生产者-消费者问题是经典的同步问题,通过本实验掌握了:

  1. 进程/线程的创建方法
  2. 进程间通信IPC机制
  3. 线程间同步机制(互斥锁、信号量)
  4. 死锁和竞争条件的避免方法