香烟吸烟者问题C实现无法终止的原因排查求助
香烟吸烟者问题C实现无法终止的原因与解决思路
我实现了香烟吸烟者问题的C语言解决方案,但程序无法终止。该问题需使用信号量同步四个进程。
实现代码
主程序代码
#include "sem.h" // 支持semget()、sem_create()及P()/V()函数 #include <stdlib.h> // 支持srand()和rand() #include <time.h> // 支持time() #include <stdbool.h> // 支持用于终止吸烟者进程的布尔变量 #include <unistd.h> // 支持fork() #include <sys/wait.h> // 支持waitpid() int main() { /* 创建并初始化信号量 */ int smkr_matches = semget(IPC_PRIVATE, 1, 0666 | IPC_CREAT); // 持有火柴的吸烟者信号量 int smkr_tobacco = semget(IPC_PRIVATE, 1, 0666 | IPC_CREAT); // 持有烟草的吸烟者信号量 int smkr_paper = semget(IPC_PRIVATE, 1, 0666 | IPC_CREAT); // 持有纸的吸烟者信号量 int agent = semget(IPC_PRIVATE, 1, 0666 | IPC_CREAT); int lock = semget(IPC_PRIVATE, 1, 0666 | IPC_CREAT); // 互斥锁信号量 /* 根据问题描述,除lock初始化为1外,其余信号量初始化为0 */ sem_create(smkr_matches, 0); sem_create(smkr_tobacco, 0); sem_create(smkr_paper, 0); sem_create(agent, 0); sem_create(lock, 1); /* 初始化随机数生成器,用于随机选择配料组合 */ srand(time(NULL)); // 声明用于终止吸烟者进程的布尔变量 bool done = false; // 声明存储wait()返回PID和状态的变量 int pid = 0; int status = 0; /* 存储各进程PID的变量 */ int smkr_mat_PID = 0; int smkr_tob_PID = 0; int smkr_pap_PID = 0; int agent_PID = 0; /* 创建agent进程 */ agent_PID = fork(); if (agent_PID == -1) { // 创建agent进程失败 perror("Forking the agent process"); exit(EXIT_FAILURE); } else if (agent_PID == 0) { printf("AGENT process created (PID = %d).\n", getpid()); int rand_num = 0; for (int i = 0; i < 4; i++) { // agent将放置4次配料到桌上 P(lock); // 随机选择一组配料 rand_num = (rand() % 3) + 1; // 放置配料并唤醒对应的吸烟者 if (rand_num == 1) { printf("AGENT has placed TOBACCO and PAPER on the table.\n"); printf("AGENT wakes smoker with MATCHES.\n"); V(smkr_matches); } else if (rand_num == 2) { printf("AGENT has placed TOBACCO and MATCHES on the table.\n"); printf("AGENT wakes smoker with PAPER.\n"); V(smkr_paper); } else if (rand_num == 3) { printf("AGENT places MATCHES and PAPER on the table.\n"); printf("AGENT wakes smoker with TOBACCO.\n"); V(smkr_tobacco); } // 释放锁,允许吸烟者操作 V(lock); printf("AGENT will now sleep.\n"); P(agent); } done = true; } /* 创建持有烟草的吸烟者进程 */ else { smkr_tob_PID = fork(); if (smkr_tob_PID == -1) { perror("Forking smoker with tobacco"); exit(EXIT_FAILURE); } else if (smkr_tob_PID == 0) { printf("SMOKER WITH TOBACCO created. (PID = %d)\n", getpid()); while(done == false) { P(smkr_tobacco); P(lock); printf("SMOKER WITH TOBACCO picks up matches and paper from the table.\n"); V(agent); V(lock); printf("SMOKER WITH TOBACCO begins smoking.\n"); } } /* 创建持有纸的吸烟者进程 */ else { smkr_pap_PID = fork(); if (smkr_pap_PID == -1) { perror("Forking smoker with paper"); exit(EXIT_FAILURE); } else if (smkr_pap_PID == 0) { printf("SMOKER WITH PAPER created (PID = %d).\n", getpid()); while(done == false) { P(smkr_paper); P(lock); printf("SMOKER WITH PAPER picks up tobacco and matches from the table.\n"); V(agent); V(lock); printf("SMOKER WITH PAPER begins smoking.\n"); } } /* 创建持有火柴的吸烟者进程 */ else { smkr_mat_PID = fork(); if (smkr_mat_PID == -1) { perror("Forking smoker with matches"); exit(EXIT_FAILURE); } else if (smkr_mat_PID == 0) { printf("SMOKER WITH MATCHES created (PID = %d).\n", getpid()); while(done == false) { P(smkr_matches); P(lock); printf("SMOKER WITH MATCHES picks up tobacco and paper from the table.\n"); V(agent); V(lock); printf("SMOKER WITH MATCHES begins smoking.\n"); } } else { pid = wait(&status); printf("PID = %d terminated with status = %d\n.", pid, status); pid = wait(&status); printf("PID = %d terminated with status = %d\n.", pid, status); pid = wait(&status); printf("PID = %d terminated with status = %d\n.", pid, status); pid = wait(&status); printf("PID = %d terminated with status = %d\n.", pid, status); } exit(EXIT_SUCCESS); } exit(EXIT_SUCCESS); } exit(EXIT_SUCCESS); } exit(EXIT_SUCCESS); }
sem.h头文件代码
/************************************************************************/ /* Operating Systems - Fall /* Originally developed at KSU by a teaching assistant */ /* */ /* Description : The following library is a collection of */ /* routines for using binary semaphores in C: */ /* 1. seminit - to initialize a semaphore. */ /* 2. P - to perform a P(S) (wait) operation. */ /* 3. V - to perform a V(S) (signal) operation. */ /* 4. semkill - to remove a semaphore */ /* */ /* These routines call system routines: */ /* 1. semget - to get a semaphore */ /* 2. semctl - semaphore control operations */ /* 3. semop - semaphore operations */ /* */ /* Complete manual entries can be obtained by: */ /* man semctl | col -b | lpr */ /************************************************************************/ #include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> union arg{ /* 用于semctl调用的结构体 */ int val; struct semid_ds *buf; char *array; }; /* * 根据key参数创建信号量并初始化为initval */ void sem_create(int semid, int initval) { int semval; union semun { int val; struct semid_ds *buf; unsigned short *array; }s; s.val=initval; if((semval=semctl(semid,0,SETVAL,s))<0) printf("\n Error in executing semctl"); } /* * 从内核中移除指定sid的信号量 */ static void semkill(int sid) { if (semctl(sid,0,IPC_RMID,0) == -1) perror("semctl (kill)"); printf("Semaphore with value of sid = %d is killed \n",sid); } /* * 对信号量执行指定操作op:op=-1时为P操作(等待),op=1时为V操作(通知) */ static void semcall(int sid, int op) { struct sembuf sb; sb.sem_num = 0; /* sid中的信号量编号 */ sb.sem_op = op; sb.sem_flg = 0; /* 阻塞调用 */ if (semop(sid, &sb, 1) == -1) perror("semop"); } /* * 对信号量sid执行P操作,进入临界区时调用 */ static void P(int sid) { semcall(sid, -1); } /* * 对信号量sid执行V操作,退出临界区时调用 */ static void V(int sid) { semcall(sid, 1); }
调试发现的问题
- 程序执行到末尾第二个
wait()调用时陷入无限等待,推测某个吸烟者进程进入无限循环。 - agent进程能正常终止,但布尔变量
done并未如预期传递给吸烟者进程,吸烟者的while(done == false)循环无法退出。
尝试过的修改(均未解决)
- 将
done初始值从false改为true,验证吸烟者是否因提前启动陷入循环; - agent进程设置
done = true前获取lock,尝试解决done未更新问题; - 调整吸烟者进程操作顺序,先释放
lock再递增agent信号量; - 调整agent进程操作顺序,先释放
lock再递增对应吸烟者信号量。
补充信息
- sem.h头文件功能正常;
- 代码在VirtualBox虚拟机的Ubuntu系统中,使用Microsoft Visual Studio编写。
核心原因与解决思路
核心原因:进程间内存不共享
fork()创建的子进程会复制父进程的内存空间,每个进程拥有独立的done变量副本。agent进程修改自己的done = true后,吸烟者进程的done变量仍然是初始的false,导致吸烟者的while循环永远无法终止,最终父进程的wait()会一直等待未退出的吸烟者进程。
解决思路
可以通过以下两种方式实现进程间的终止通知:
- 使用共享内存存储
done变量:通过shmget()、shmat()等系统调用创建共享内存区域,让所有进程访问同一个done变量,同时用信号量同步对共享内存的访问。 - 用额外信号量触发终止:在agent进程结束时,对每个吸烟者的信号量执行一次
V()操作,让吸烟者从P()阻塞中唤醒,然后在循环内检查是否需要退出。
示例修改(用信号量触发终止):
在agent进程的done = true之后,添加:
// 唤醒所有吸烟者,触发退出逻辑 V(smkr_matches); V(smkr_tobacco); V(smkr_paper);
同时修改吸烟者的循环逻辑(需配合共享内存的done变量判断):
// 假设done已改为共享内存变量 while(true) { P(smkr_tobacco); if (done) break; // 检测到终止信号则退出循环 P(lock); printf("SMOKER WITH TOBACCO picks up matches and paper from the table.\n"); V(agent); V(lock); printf("SMOKER WITH TOBACCO begins smoking.\n"); }
内容的提问来源于stack exchange,提问作者Carlos Torres
相关产品推荐
相关产品推荐

