【问题标题】:Synchronization between childs and parent processes c子进程和父进程之间的同步 c
【发布时间】:2019-06-17 23:34:02
【问题描述】:

我正在尝试实现这一点:

制作一个执行以下操作的 C 多进程程序:

一个进程 P 生成两个子进程 P1 和 P2。两个儿子 P1 和 P2 执行一个不确定的循环,其中每秒生成一个介于 0 和 100 之间的随机整数。每次抽奖时,孩子们交流由父 P 进程生成的数字,该进程提供将它们相加,并将它们打印在屏幕并将它们存储在一个文件中。进程 P1 必须处理 SIGINT 中断信号。特别是,在此信号到达时,P1 必须显示警告消息“P1 进程忙!”。当父 P 进程验证它从子进程接收到的数字的总和为 100 时,该程序被父 P 进程终止。

现在,我需要一些帮助来实现孩子和父母之间的同步。我试图使用信号量,但看起来不可能。我可以用什么来同步它们?信号?怎么样?

    #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <semaphore.h>
#include <fcntl.h>
#define READ 0
#define WRITE 1
   

void handler(int sig){

    printf("process 1 is busy\n");


}


void codeprocess1(int pd[], sem_t *sem1){
    
    int i = 0;
    int numgenerated;
    

    while( i = 0){
      signal(SIGUSR1, handler);
       numgenerated = rand()%101;
       close(pd[READ]);
       write(pd[WRITE], &numgenerated, sizeof(int));
       sleep(1);
       
       sem_wait(sem1);
    }
}

void codeprocess2(int pd[], sem_t *sem2){
    int i = 0;
    int numgenerated;
          
    

    while( i = 0){

     numgenerated = rand()%101;
     close(pd[READ]);

     write(pd[WRITE], &numgenerated, sizeof(int));

     sleep(1);
     
     sem_wait(sem2);
    }
}


int main(){
 
 
 pid_t pid1, pid2;
 int sum, numread1, numread2, pipe1[2], pipe2[2];
 
    sem_t *sem2 = sem_open("semaph2", O_CREAT | O_EXCL, 1, 0);
    sem_t *sem1 = sem_open("semaph1", O_CREAT | O_EXCL, 1, 0);
   
 
 if(pipe(pipe1)<0){
     exit(1);

 }

 if(pipe(pipe2)<0){
     exit(1);

 }


 pid1 = fork();
 switch(pid1){

     case -1:

       exit(1);

     case 0:

       codeprocess1(pipe1, sem1);
       break;

     default:

        pid2= fork();
        switch( pid2){

       case -1:
          exit(1);

       case 0:

          codeprocess2(pipe2, sem2);
          break;

         default:
           
           while(sum!=1000){
           close(pipe1[WRITE]);

           read(pipe1[READ], &numread1, sizeof(int));
            close(pipe2[WRITE]);
           read(pipe2[READ], &numread2, sizeof(int));
           sum = sum + numread1 + numread2;
           printf("%d\n", sum);
           sem_post(sem1);
           sem_post(sem2);
       }

          kill(0, SIGKILL);
     }
   }

}

【问题讨论】:

  • 为什么你认为你需要一些同步?是否有规定轮到对方时P1和P2必须等待?如果没有,您所要做的就是确保 P 在使用之前从 P1 或 P2 读取完整的数字。您可以使用select 找出准备读取的管道。当 P1 和 P2 每秒写入一个整数时,您可能不会注意到任何问题,但您应该为不完整的writeread 或信号发生时的错误指示做好准备。您对变量等的命名不一致。请修复编译器错误/警告并更新代码。
  • 信号处理存在问题。使用signal(),信号处理程序可能会在信号发生时重置为默认值。然后第二个SIGINT 可能会终止程序。确切的行为取决于库版本、#includes 和预处理器符号。最好使用sigaction。在信号处理程序中调用printf 是不好的,因为它不可重入。最好只在处理程序中设置volatile sig_atomic_t 类型的变量(或更多),然后检查并重置主循环中的值。如果像readwrite 这样的系统调用被信号中断,它将返回-1errno==EINTR
  • 从问题陈述中,我看不出为什么你会使用比管道更复杂的东西来进行通信,并且它们会为你处理同步。

标签: c multiprocess


【解决方案1】:

我在这里报告sem_overview(7)手册页的相关部分:

   POSIX  semaphores come in two forms: named semaphores and unnamed sema‐
   phores.

   Named semaphores
          A named semaphore is identified by a name of the form /somename;
          that  is,  a  null-terminated  string of up to NAME_MAX-4 (i.e.,
          251) characters consisting of an initial slash, followed by  one
          or  more  characters,  none of which are slashes.  Two processes
          can operate on the same named semaphore by passing the same name
          to sem_open(3).

          The  sem_open(3) function creates a new named semaphore or opens
          an existing named  semaphore.   After  the  semaphore  has  been
          opened, it can be operated on using sem_post(3) and sem_wait(3).
          When a process has finished using  the  semaphore,  it  can  use
          sem_close(3)  to  close  the semaphore.  When all processes have
          finished using the semaphore, it can be removed from the  system
          using sem_unlink(3).

   Unnamed semaphores (memory-based semaphores)
          An  unnamed  semaphore  does not have a name.  Instead the sema‐
          phore is placed in a region of memory  that  is  shared  between
          multiple  threads  (a  thread-shared  semaphore) or processes (a
          process-shared semaphore).  A thread-shared semaphore is  placed
          in  an  area  of memory shared between the threads of a process,
          for example, a global variable.  A process-shared semaphore must
          be  placed  in  a  shared memory region (e.g., a System V shared
          memory segment created using shmget(2), or a POSIX shared memory
          object built created using shm_open(3)).

          Before  being  used,  an  unnamed  semaphore must be initialized
          using sem_init(3).  It can then be operated on using sem_post(3)
          and  sem_wait(3).  When the semaphore is no longer required, and
          before the memory in which it is  located  is  deallocated,  the
          semaphore should be destroyed using sem_destroy(3).

您正尝试在标准内存中使用未命名的信号量。但它们仅用于同步线程,而不是进程。

我建议使用命名信号量(应该更容易)或共享内存支持的未命名信号量(使用shmget()shm_open() 获取它,然后使用sem_init() - 父进程和分叉进程必须使用相同的共享内存段来访问进程间信号量)。

事实上,在您的代码sem1sem2 中,在主进程中初始化,不会传播到分叉的进程:它们具有独立的内存区域和地址,并且不能共享。

修改后,关于信号量有很多问题:

  • 最逻辑错误:不能将一个进程的指针传递给另一个进程:地址不共享。每个进程都必须独立打开信号量并将其与自己的处理程序一起使用。
  • while (i=0)...哎呀,尝试用-Wall编译。
  • 您没有检查 sem_open() 的返回码,它因 errno=13 (EACCESS) 而失败
  • 您没有正确设置信号量的权限...这是一个(某种)文件。请注意,一旦您使用错误的权限创建它,它就会保留在那里,并且无法使用相同的名称再次创建它(直到您重新启动系统)。您可以使用ls -l /dev/shm 来查看它们,最终只需使用rm 删除它们。
  • 您请求的是 O_EXCL,即对一个进程的独占访问,这不是您想要的。见man 2 open
  • 信号量的名字必须以/开头,见man sem_overview

这是修改后的代码,一些cmets in-line:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <semaphore.h>
#include <fcntl.h>
#include <errno.h>

#define READ 0
#define WRITE 1

#define SEM1_NAME "/semaph_1a"
#define SEM2_NAME "/semaph_2a"


void handler(int sig) {
  printf("process 1 is busy\n");
}

void codeprocess1(int pd[]) {
  int i = 0;
  int numgenerated;

  // each process must open the handle to the same named semaphore.
  // they cannot share a local memory address.
  sem_t *my_sem = sem_open(SEM1_NAME, O_CREAT , 0777, 0);
  if (my_sem==SEM_FAILED) {
    printf("semaphore creation failed, errno=%d\n", errno);
    exit(1);
  }

  // the seed for the two children must be different or they will be generating the same
  // sequence of random numbers. 
  srand(3333);

  while(i == 0) {
    signal(SIGUSR1, handler);
    numgenerated = rand()%101;
    // close(pd[READ]);
    write(pd[WRITE], &numgenerated, sizeof(int));
    sleep(1);

    sem_wait(my_sem);
  }
}

void codeprocess2(int pd[]){
  int i = 0;
  int numgenerated;

  sem_t *my_sem = sem_open(SEM2_NAME, O_CREAT, 0777, 0);
  if (my_sem==SEM_FAILED) {
    printf("semaphore creation failed, errno=%d\n", errno);
    exit(1);
  }

  srand(1111);

  while(i == 0) {
    numgenerated = rand()%101;
    // close(pd[READ]);
    write(pd[WRITE], &numgenerated, sizeof(int));
    sleep(1);
    sem_wait(my_sem);
  }
}


int main(){
  pid_t pid1, pid2;
  int sum, numread1, numread2, pipe1[2], pipe2[2];


  // O_EXCL removed
  // the mode flag must be set to 0777 for example, not "1".
  // return value check added
  sem_t *sem1 = sem_open(SEM1_NAME, O_CREAT , 0777, 0);
  if (sem1==SEM_FAILED) {
    printf("semaphore sem1 creation failed, errno=%d\n", errno);
    exit(1);
  }

  sem_t *sem2 = sem_open(SEM2_NAME, O_CREAT, 0777, 0);
  if (sem2==SEM_FAILED) {
    printf("semaphore sem2 creation failed, errno=%d\n", errno);
    exit(1);
  }

  if (pipe(pipe1) < 0 ) {
    exit(1);
  }

  if (pipe(pipe2) < 0) {
    exit(1); 
  }

  pid1 = fork();
  switch(pid1){

  case -1:
    exit(1);

  case 0:
    codeprocess1(pipe1);
    break;

  default:

    pid2= fork();
    switch( pid2) {      
    case -1:
      exit(1);
    case 0:      
      codeprocess2(pipe2);
      break;      
    default:      
      // 100, not 1000
      while (sum != 100) {
    // all the "close()" calls  are commented out
    // close(pipe1[WRITE]);
    read(pipe1[READ], &numread1, sizeof(int));
    // close(pipe2[WRITE]);
    read(pipe2[READ], &numread2, sizeof(int));
    // sum must not be incremented
    sum = numread1 + numread2;
    printf("%d\n", sum);
    sem_post(sem1);
    sem_post(sem2);
      }

      kill(0, SIGKILL);
    }
  }  
}

【讨论】:

  • 好的,我已经编辑了代码,现在可以执行了。我现在使用两个命名信号量,但我得到的输出是一个 0 列表...可能 sem_open 函数有错误。
  • 正如@Bodo 正确指出的那样,如果您打算与管道通信,则不需要使用信号。我立即在这里看到了信号量的错误用法并写了这个答案。不工作的代码没有帮助,但现在这方面没问题。稍后我会看看,如果没有其他人会贡献。然而“谷歌”为“叉管”找到类似的例子
  • 我已经编辑了答案并添加了有关同步部分的修复。剩下的就是你的职责了:)
【解决方案2】:

您的问题确实有很多问题。

正如@Sigismondo 答案中所发布的,您将多线程与多进程编程混淆了。他们有不同的沟通方式。

为了过度简化线程共享相同的内存,因此一个线程可以看到例如信号量互斥锁等全局变量的值:如果一个线程修改它,另一个线程将受到影响。

在你fork()的多处理中,会生成一个拥有自己内存空间的新进程。在fork() 变量值几乎相同(除了pidppid 等)之后,但它们位于不同的内存空间中:如果您的代码块仅由一个进程执行,则修改它不会影响其他进程的变量(程序中的信号量)。

在您的情况下:首先,如果子进程执行相同的操作(即生成随机数),为什么您必须使用不同的功能?你不能这样做吗:

#include<stdlib.h>
int generateRand()
{
     n = rand() % 100 + 1; //should be random in [1, 100]
}

处理信号

进程 P1 必须处理 SIGINT 中断信号。特别是,在 此信号 P1 的到来必须显示警告消息“P1 进程忙!”。程序被父 P 进程终止时 它验证它收到的数字的总和 子进程,假定值为 100。

在我看来,这真的不清楚。父母应该捕捉到SIGINT 信号。孩子们应该怎么做?从你所说的来看,他们似乎不应该捕捉到那个信号。在这种情况下,您必须查看信号掩码:基本上您必须阻止父级中的信号,调用fork()s 然后放回原始掩码。现在你应该更深入一些,但像这样 (here)

sigset_t *parent_mask, *child_mask
//get the current mask 
if (int res =  sigprocmask (0, NULL, child_mask)<0)
    printf("some error\n");
//make the mask block the signal
if (int res =  sigaddset(child_mask, SIGINT)<0)
    printf("some error in sigaddset \n");
// block the signal with the new mask
if (int res =  sigprocmask (SIG_SETMASK, child_mask, parent_mask)<0)
    printf("some error\n");
//do your forks: children will inherit the current mask and will not catch SIGINT
...
fork()
...
fork()
....
//set back the original mask so the parent catches SIGINT
if (int res =  sigprocmask (SIG_SETMASK, parent_mask, NULL)<0)
    printf("some error\n");

This answer of mine,虽然对于多线程应该更清楚一点。

信号处理器

为什么要在codeprocess1(int pd[]) 中注册信号处理程序?我完全不明白。为什么SIGUSR1

您应该在父级中执行此操作(在 fork()s 之前或之后不应更改,因为信号被子级阻塞:这取决于您是否希望用户在启动 forks() 之前退出程序或不是:在第一种情况下,在fork() 之后注册信号处理程序,否则将其放在main() 的开头。在这两种情况下你都应该这样做:

signal(SIGINT, handler); 

现在是您程序的核心:要与您的程序通信,您可以将pipe() 与文件描述符一起以阻塞方式使用:检查here

您需要两个文件描述符(每个子进程一个并关闭进程未使用的结尾(读/写))。 考虑一个子进程:

int p = fork();
int fd1[2]; //file descriptor for child1
int fd2[2]; //file descriptor for child2

if (p>0)//parent
{
    close(fd1[1]);//close writing end
    int n;
    read(fd1[0], &n, sizeof(n));
    //you might to call the other fork here and redo the same stuff
    int p2 = fork();
    if (p2>0)
    {
         close(fd2[1]);//close writing end
         int n2;
         read(fd2[0], &n2, sizeof(n2));
         sum = n2+n1
         if (sum==100 && exit = 1)
         {
             kill(p, SIGKILL);
             kill(p2, SIGKILL);
         }
    }
}
else if(p==0)//child
{
    close(fd1[0]);//close read end
    int rand_n = generateRand();//or whaterver the name
    wrote(fd1[1], &rand_n, sizeof(rand_n));


}

退出条件基于总和的值 (100) 和按下 CTRL+C 的事实。前者在上面的代码中很明显。对于后者,您可以声明一个全局变量(我使用了exit),如果没有按下 0 CTRL+C,如果按下了 1。在上面代码的退出条件中检查该值。您的处理程序将负责编写此变量:

//global variable here
int exit = 0;

void handler(int signo)
{
    print("Parent busy doing stuff\n");
    exit =1;
}

注意一件事exit 是由父级编写的,因为它仅在仅由父级调用的处理程序中编写,并且在仅由父级执行的代码部分中读取:子级读取其value 对他们来说永远是 0。

由于您的问题过于笼统,我试图给出一些提示:我的代码中可能存在错误,因为我没有尝试过。你应该自己研究。如果你能提供一个最小的工作示例,我会尽力提供帮助。

【讨论】:

  • 您好,感谢您的帮助。我已经上传了编辑后的代码。我现在尝试使用命名信号量,但还是不行。
猜你喜欢
  • 2011-01-12
  • 2016-07-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-12
相关资源
最近更新 更多