【问题标题】:Why doesn't my mutex work properly in a multi-process C application?为什么我的互斥锁不能在多进程 C 应用程序中正常工作?
【发布时间】:2011-05-16 13:31:40
【问题描述】:

我正在处理一个 uni 分配,并且遇到了我的代码的问题,该代码应该产生 2 个进程,其中第二个进程在执行之前等待第一个进程完成。这是我目前所拥有的:

sem_t mutex;
int producer; int consumer;
sem_init(&mutex, 0, 1);
producer = fork();
consumer = fork();

if (producer == 0) {
    if (VERBOSE) printf("Running producer\n");
    /* down semaphore */
    sem_wait(&mutex);
    /* START CRITICAL REGION */
    get_files(N);
    /* END CRITICAL REGION */
    /* up semaphore */
    sem_post(&mutex);
    if (VERBOSE) printf("Ending producer\n");
    exit(0);
}

if (consumer == 0) {
    if (VERBOSE) printf("Running consumer\n");
    /* down semaphore */
    sem_wait(&mutex);
    /* START CRITICAL REGION */
    /* do stuff */
    /* END CRITICAL REGION */
    /* up semaphore */
    sem_post(&mutex);
    if (VERBOSE) printf("Ending consumer\n");
    exit(0);
}
/* parent waits for both to complete */
wait(NULL);

现在,我知道在“现实世界”中这真的很愚蠢。如果我的“消费者”在我的“生产者”完成之前什么都不做,那么你可能没有像这样的 2 个进程,但任务是试图说明一个竞争条件,所以为什么我们被特别告知要这样做。

所以,我的问题是消费者进程没有等待生产者。我假设由于信号量在生产者 (sem_wait(&mutex);) 中被删除,那么在生产者中调用 sem_post(&mutex); 之前,消费者将无法使用它。

此外,据我所知,wait(NULL); 行并未等待这两个进程完成。

我是否严重误解了什么?

【问题讨论】:

  • 你创建进程而不是线程。
  • fork 共享内存......它应该可以看到并能够使用互斥锁,不是吗? (我可能在这里过得很好,我没试过)
  • @tobyodavies:我这么说是因为“应该产生 2 个线程”这个短语
  • @tobyodavies:不,fork() 创建的进程不共享内存(除非内存已专门映射为MAP_SHARED)。
  • @tobyodavies:你确定 fork 共享内存吗?我认为它会重复从父母到孩子的记忆......

标签: c multithreading unix semaphore


【解决方案1】:

您应该对信号量调用进行错误检查。如果sem_wait()sem_init()sem_post() 返回非零,则使用perror() 显示错误。

其次,您创建的流程比您想象的要多。您的第一个 fork() 会产生一个父级(producer 非零)和一个子级(producer 零)。 两个进程然后执行第二个fork(),所以你现在有四个进程。

第三,sem_t 变量必须在进程之间共享,因此它必须存储在共享内存区域中。最简单的方法是使用mmap():

sem_t *sem = mmap(NULL, sizeof *sem, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);

(在sem_init() 和第一个fork() 之前执行)。

第四,没有定义哪个进程将首先运行,因此您不能依赖生产者线程在消费者之前调用sem_wait()。相反,使用sem_init() 将信号量初始化为零,并且 在消费者中调用sem_wait() - 这将阻止消费者。生产者执行,完成后调用sem_post(),允许消费者继续。

sem_init() 调用应将 pshared 指定为非零,将 value 指定为 0,因此应如下所示:

if (sem_init(sem, 1, 0) != 0) {
    perror("sem_init");
    exit(1);
}

第五,wait(NULL) 只等待单个子进程退出。调用两次以等待两个子进程。

【讨论】:

  • 您实际上已经发现了错误 #2,非常感谢。该问题已被修改,以更新您答案的其他元素。不过,尽管您是正确的,但在这种情况下,我认为这不是问题所在。
  • 根据 O'Reily (linuxdevcenter.com/pub/a/linux/2007/05/24/…),如果进程不是父/子进程,您只需要共享内存
  • sem_wait 只会阻塞并且永远不会在初始值为 0 的信号量上成功 否?我可能误读了手册页,但我的理解是信号量的值表示它可以处理的额外“用户”的数量,因此 w/value=1 的信号量相当于互斥体......
  • @tobyodavies: sem_wait() 将阻塞值为 0 的信号量,直到该信号量被发布。这正是这里所需要的行为——0 的初始值,sem_wait() 在消费者的开头和sem_post() 在生产者的结尾。在这种情况下,信号量的值是“已生产但尚未消费的事物的数量”。
  • 是的,我还在努力修复第一个错误,你已经领先了我
【解决方案2】:

仅仅因为你fork生产者线程首先并不意味着操作系统会安排它首先运行 - 很可能消费者实际运行并首先获得锁。

另外,您应该检查sem_wait 的返回值 - 可以在不持有信号量的情况下从它返回。

也很有可能(正如一些人在 cmets 中指出的那样)信号量可能根本无法跨 forked 进程工作

编辑 - 如果在初始化 posix 信号量时将非零值传递给 sem_init(sem_t *sem, int pshared, unsigned value) 的参数 2 将跨进程工作

编辑 - 请参阅 here 以获得比我能给出的更好的解释,并附上源代码,几乎完全符合您的要求

【讨论】:

  • 好的,我查看了您的建议,似乎没有授予任何信号量锁。两条sem_wait(&mutex); 行都返回值-1,Google 告诉我这意味着失败。我现在该怎么办? (P.S. 我想这就是为什么我的 wait(NULL); 也不起作用的原因吧?
  • 是的,关键是你需要实际调用sem_init,并检查sem_wait的状态。
  • 你应该做while(sem_wait(mutex)!=0);作为一个最简单的建议(注意';' - 没有做任何事情的循环)
  • 我已经修改了问题以包含我在程序中的sem_init(&mutex, 0, 1); 行。它看起来正确吗?
  • 不,你需要一个非零的第二个参数来允许信号量状态在孩子之间共享 - 即sem_init(&mutex,TRUE,0)
【解决方案3】:

您是否在问题中提供了完整的代码?

如果是这样,您缺少信号量初始化。在使用信号量之前,您必须调用 sem_initsem_open

阅读here

EDIT 您在sem_init 调用中指定pshared = 0。这使得信号量进程本地化(即它只能用于同步一个进程的线程)。 fork 创建一个子进程,所以信号量没有做任何有用的事情。

如果 pshared 的值为 0,则 信号量在 进程的线程。如果 pshared 是 非零,则共享信号量 进程之间。

(引用来自上面的链接)

【讨论】:

    【解决方案4】:

    首先,我认为互斥锁不适用于进程。原因是分叉的进程实际上并不共享内存。他们将能够读取与分叉之前相同的内存,但每当其中一个写入内存时,新进程都会收到自己的副本。

    其次,您可能必须初始化互斥锁,否则它们的使用可能未定义。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-03-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多