You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

香烟吸烟者问题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()会一直等待未退出的吸烟者进程。

解决思路

可以通过以下两种方式实现进程间的终止通知:

  1. 使用共享内存存储done变量:通过shmget()、shmat()等系统调用创建共享内存区域,让所有进程访问同一个done变量,同时用信号量同步对共享内存的访问。
  2. 用额外信号量触发终止:在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 14:50:29