【问题标题】:Synchronising processes with semaphores使用信号量同步进程
【发布时间】:2017-04-07 12:48:02
【问题描述】:

我很难理解如何使用信号量在两个进程之间交替控制。这是一个人为设计的流程处理代码示例。

int pid = fork();

if (pid) {
  int counter = 0;
  while (true) {
    counter += 1;
    printf("P%d = %d", pid, counter);
  }
} else {
  int counter = 0;
  while (true) {
    counter += 1;
    printf("P%d = %d", pid, counter);
  }
}

我原以为上面的代码可以并行运行,但对于分叉的进程,控制流似乎会立即继续,而父进程只会在稍后恢复。

这从根本上破坏了我现有的使用信号量来控制哪个进程可以执行的代码。

int id = get_semaphore(1);
int pid = fork();

if (pid) {
  int counter = 0;
  while (true) {
    sem_wait(id);
    counter += 1;
    printf("P%d = %d\n", pid, counter);
    sem_signal(id);
  }
} else {
  int counter = 0;
  while (true) {
    sem_wait(id);
    counter += 1;
    printf("P%d = %d\n", pid, counter);
    sem_signal(id);
  }
}
  • sem_wait 助手只是从信号量值中减去 1 并阻塞直到结果 > 0(在后台使用 semop)。

  • sem_signal 助手只是将信号量值加 1(在后台使用 semop)。

我希望代码在两个进程之间交替,使用sem_wait 阻塞,直到另一个进程使用sem_signal 释放资源。期望的输出是:

P1 = 0
P0 = 0
P1 = 1
P0 = 1
...

但是,由于进程之间的初始执行延迟,子进程获取可用的信号量资源,使用它打印一个数字,然后恢复它并循环——此时资源再次可用,所以它继续没有一直在等待其他进程。

如果进程自行释放资源,那么防止进程使用资源的最佳方法是什么?

【问题讨论】:

  • 您需要两个信号量和一个“executeNow”单元。在进程之间来回传递单元。

标签: c linux process semaphore


【解决方案1】:

分叉进程的控制流似乎会立即继续,而父进程的控制流稍后才会恢复

这是因为流 IO 会缓冲 stdout 上的输出,直到任一

  • 缓冲区已满
  • fflush()stdout 上被调用
  • 遇到换行符 (\n)

在您的程序中,每个进程将在将其内容发送到stdout 之前填充一个缓冲区,从而给出一个进程运行很长时间,然后是另一个进程的外观。用\n 终止printf 语句的格式字符串,您将在第一个程序中看到更符合您预期的行为。

我不确定为什么你的信号量不起作用 - 我对系统 V 信号量不是很了解,但对我来说,你在分叉后得到信号量似乎是一个危险信号。对于更常见的 POSIX 信号量,信号量必须在内存中,两个进程都可以看到,否则它就是两个信号量。

无论如何,假设您的get_semaphore() 函数做了正确的事情来共享信号量,仍然存在一个问题,因为无法保证当一个进程发出信号量时,另一个进程将很快启动以使其获取在第一个过程循环并自行抓取它之前再次它。

您需要两个信号量,一个用于父级,一个用于子级。在打印之前,每个进程都应该等待自己的信号量。打印之后,每个进程都应该向另一个信号量发出信号。此外,一个信号量应该初始化为 1,另一个应该初始化为 0。

【讨论】:

  • 对不起,我对这个人为的例子有点草率,实际上我在用换行符分叉 打印之前得到了信号量。更新了问题。
  • 尝试将一个进程打印到stdout,另一个打印到stderr,但似乎子进程在父进程恢复之前仍然运行了很长时间。
【解决方案2】:

信号量有两个一般用例。一是互斥,二是同步。您的代码中所做的是互斥。您真正想要的是父进程和子进程之间的同步(交替)。

让我解释一下:
互斥意味着在任何时候只有一次进程可以访问“关键部分”,这是一段您希望一次只允许一个进程/线程访问的代码。关键部分通常具有操作共享资源的代码。

来到你的代码,因为你只使用了一个信号量,所以不能保证每个进程被允许进入临界区的“顺序”。 例如:您的代码中的 sem_wait(id) 可以由任何进程执行,两个进程不必交替执行。

对于进程同步(更具体地说是交替),您需要使用两个信号量,一个用于父级,另一个用于子级。

    Sample code:
    int pid = fork();

    int parent_sem = get_semaphore(0);
    int child_sem = get_semaphore(1);

    if (pid) {
       int counter = 0;
       while (true) {
           sem_wait(child_sem);
           counter += 1;
           printf("P%d = %d", pid, counter);
           sem_signal(parent_sem);
       }
     } else {
       int counter = 0;
       while (true) {
            sem_wait(parent_sem);
            counter += 1;
            printf("P%d = %d", pid, counter);
            sem_signal(child_sem);
       }
     }

您需要将一个信号量(在我的情况下为孩子)初始化为 1,将第二个信号量初始化为零。这样,只有两个进程中的一个开始启动,而另一个进入等待。一旦孩子完成打印,它就会向父母发出信号。现在孩子的信号量值为零,因此它在等待(child_sem)时等待孩子发出信号的父母执行。下一次,父母向孩子发出信号并执行。这在交替序列中继续存在,是一个经典的同步问题。

【讨论】:

  • 看起来这段代码的行为完全相同。我在else 分支的开头有一个printf 语句,直到孩子的while 循环运行了很多次之后才会显示。听起来问题可能出在 I/O 而不是信号量上。
猜你喜欢
  • 2015-04-13
  • 2017-08-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-04
  • 1970-01-01
  • 2015-05-06
相关资源
最近更新 更多