C中的进程同步不会第一次执行
Process Synchronisation in C does not execute the first time
所以我在使用 C 中的进程同步程序时遇到了这个问题。
我应该编写一个代码,使用 fork()
,将产生如下内容:
PARENT
PARENT
CHILD
PARENT
CHILD
PARENT
使用我找到的代码 here,我能够让它工作,但由于某些原因,第一个出现在屏幕上的结果是乱七八糟的,而其他所有结果都很好。
要编译,输入:gcc test.c display.c -o test -pthread
无论如何,这是我正在测试的代码(我再说一遍:这不是我的代码):
#include <semaphore.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
int main(void)
{
int i;
/* place semaphore in shared memory */
sem_t *sema = mmap(NULL, sizeof(sema), PROT_READ |PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1, 0);
/* create/initialize semaphore */
if ( sem_init(sema, 1, 0) < 0)
{
perror("sem_init");
exit(EXIT_FAILURE);
}
int nloop=10;
int pid = fork();
if (pid == 0)
{
for (i = 0; i < nloop; i++)
{
// child unlocks semaphore
display("CHILD\n");
if (sem_post(sema) < 0)
perror("sem_post");
sleep(1);
}
if (munmap(sema, sizeof(sema)) < 0)
{
perror("munmap");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
else
{
for (i = 0; i < nloop; i++)
{ // parent starts waiting
display("PARENT\n");
if (sem_wait(sema) < 0)
perror("sem_wait");
// parent finished waiting
}
if (sem_destroy(sema) < 0)
{
perror("sem_destroy failed");
exit(EXIT_FAILURE);
}
if (munmap(sema, sizeof(sema)) < 0)
{
perror("munmap failed");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
}
这是输出:
PACREHNT
ILD
PARENT
CHILD
PARENT
CHILD
PARENT
CHILD
PARENT
CHILD
PARENT
CHILD
PARENT
CHILD
PARENT
CHILD
PARENT
CHILD
PARENT
CHILD
为什么一开始会这样?
你不能编写一个程序,在 parent 和 child 进程之间交替使用单个信号量(不诉诸某种形式的忙等待标志或其他东西),因为两者进程将竞相获取信号量;无法预测哪个进程将首先获取它。您的代码(第一次迭代除外)似乎按预期工作,因为 child 休眠了很长时间,但从技术上讲它仍然是竞争条件,无法保证 parent 会得到有机会在 child 醒来之前获取信号量(尽管这种可能性很小)。
所以,你需要 2 个信号量:一个被 child 用来通知 parent 轮到他了,另一个被 parent 用来通知 child.要选择谁先开始,将相应的信号量初始化为 1(另一个初始化为 0)。
另外,这是错误的:
sem_t *sema = mmap(NULL, sizeof(sema), PROT_READ |PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1, 0);
第二个参数应该是 sizeof(*sema)
因为你想为信号量 object 而不是指针分配内存。
你从来没有#include "display.h"
,你可能应该。
错误处理可以改进,但对于这个玩具程序,我认为这不是什么大问题。
这是一个使用 2 信号量方法的工作版本(这会将 parent 信号量初始化为 1,因此 parent 将首先启动):
#include <semaphore.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
int main(void)
{
int i;
/* place semaphore in shared memory */
sem_t *child_sem = mmap(NULL, sizeof(*child_sem), PROT_READ |PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1, 0);
sem_t *parent_sem = mmap(NULL, sizeof(*parent_sem), PROT_READ |PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1, 0);
/* create/initialize semaphore */
if ( sem_init(child_sem, 1, 0) < 0)
{
perror("sem_init");
exit(EXIT_FAILURE);
}
if (sem_init(parent_sem, 1, 1) < 0) {
perror("sem_init");
exit(EXIT_FAILURE);
}
int nloop=10;
int pid = fork();
if (pid == 0)
{
for (i = 0; i < nloop; i++)
{
if (sem_wait(child_sem) < 0)
perror("sem_wait");
display("CHILD\n");
if (sem_post(parent_sem) < 0)
perror("sem_post");
sleep(1);
}
}
else
{
for (i = 0; i < nloop; i++)
{ // parent starts waiting
if (sem_wait(parent_sem) < 0)
perror("sem_wait");
display("PARENT\n");
if (sem_post(child_sem) < 0)
perror("sem_post");
}
}
}
为了简洁起见,我删除了 munmap(2)
和 sem_destroy(3)
调用,因为它们是不必要的,因为无论如何进程都会退出。
注意两个进程都遵循相同的模式:等待它们的信号量、工作、通知其他进程的信号量。这是进行一些重构并将其全部移动到接收要显示的字符串、要等待的信号量以及之后要通知的信号量的函数的好机会。
你也应该习惯用-Wall
编译。
所以我在使用 C 中的进程同步程序时遇到了这个问题。
我应该编写一个代码,使用 fork()
,将产生如下内容:
PARENT
PARENT
CHILD
PARENT
CHILD
PARENT
使用我找到的代码 here,我能够让它工作,但由于某些原因,第一个出现在屏幕上的结果是乱七八糟的,而其他所有结果都很好。
要编译,输入:gcc test.c display.c -o test -pthread
无论如何,这是我正在测试的代码(我再说一遍:这不是我的代码):
#include <semaphore.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
int main(void)
{
int i;
/* place semaphore in shared memory */
sem_t *sema = mmap(NULL, sizeof(sema), PROT_READ |PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1, 0);
/* create/initialize semaphore */
if ( sem_init(sema, 1, 0) < 0)
{
perror("sem_init");
exit(EXIT_FAILURE);
}
int nloop=10;
int pid = fork();
if (pid == 0)
{
for (i = 0; i < nloop; i++)
{
// child unlocks semaphore
display("CHILD\n");
if (sem_post(sema) < 0)
perror("sem_post");
sleep(1);
}
if (munmap(sema, sizeof(sema)) < 0)
{
perror("munmap");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
else
{
for (i = 0; i < nloop; i++)
{ // parent starts waiting
display("PARENT\n");
if (sem_wait(sema) < 0)
perror("sem_wait");
// parent finished waiting
}
if (sem_destroy(sema) < 0)
{
perror("sem_destroy failed");
exit(EXIT_FAILURE);
}
if (munmap(sema, sizeof(sema)) < 0)
{
perror("munmap failed");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
}
这是输出:
PACREHNT
ILD
PARENT
CHILD
PARENT
CHILD
PARENT
CHILD
PARENT
CHILD
PARENT
CHILD
PARENT
CHILD
PARENT
CHILD
PARENT
CHILD
PARENT
CHILD
为什么一开始会这样?
你不能编写一个程序,在 parent 和 child 进程之间交替使用单个信号量(不诉诸某种形式的忙等待标志或其他东西),因为两者进程将竞相获取信号量;无法预测哪个进程将首先获取它。您的代码(第一次迭代除外)似乎按预期工作,因为 child 休眠了很长时间,但从技术上讲它仍然是竞争条件,无法保证 parent 会得到有机会在 child 醒来之前获取信号量(尽管这种可能性很小)。
所以,你需要 2 个信号量:一个被 child 用来通知 parent 轮到他了,另一个被 parent 用来通知 child.要选择谁先开始,将相应的信号量初始化为 1(另一个初始化为 0)。
另外,这是错误的:
sem_t *sema = mmap(NULL, sizeof(sema), PROT_READ |PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1, 0);
第二个参数应该是 sizeof(*sema)
因为你想为信号量 object 而不是指针分配内存。
你从来没有#include "display.h"
,你可能应该。
错误处理可以改进,但对于这个玩具程序,我认为这不是什么大问题。
这是一个使用 2 信号量方法的工作版本(这会将 parent 信号量初始化为 1,因此 parent 将首先启动):
#include <semaphore.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
int main(void)
{
int i;
/* place semaphore in shared memory */
sem_t *child_sem = mmap(NULL, sizeof(*child_sem), PROT_READ |PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1, 0);
sem_t *parent_sem = mmap(NULL, sizeof(*parent_sem), PROT_READ |PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1, 0);
/* create/initialize semaphore */
if ( sem_init(child_sem, 1, 0) < 0)
{
perror("sem_init");
exit(EXIT_FAILURE);
}
if (sem_init(parent_sem, 1, 1) < 0) {
perror("sem_init");
exit(EXIT_FAILURE);
}
int nloop=10;
int pid = fork();
if (pid == 0)
{
for (i = 0; i < nloop; i++)
{
if (sem_wait(child_sem) < 0)
perror("sem_wait");
display("CHILD\n");
if (sem_post(parent_sem) < 0)
perror("sem_post");
sleep(1);
}
}
else
{
for (i = 0; i < nloop; i++)
{ // parent starts waiting
if (sem_wait(parent_sem) < 0)
perror("sem_wait");
display("PARENT\n");
if (sem_post(child_sem) < 0)
perror("sem_post");
}
}
}
为了简洁起见,我删除了 munmap(2)
和 sem_destroy(3)
调用,因为它们是不必要的,因为无论如何进程都会退出。
注意两个进程都遵循相同的模式:等待它们的信号量、工作、通知其他进程的信号量。这是进行一些重构并将其全部移动到接收要显示的字符串、要等待的信号量以及之后要通知的信号量的函数的好机会。
你也应该习惯用-Wall
编译。