C语言 如何在没有sleep()的情况下实现两个进程之间的来回同步进程间通信

frebpwbc  于 2023-08-03  发布在  其他
关注(0)|答案(3)|浏览(95)

所以问题是这样的:
有两个进程,前台进程和后台进程。两者共享一个整数数组。前台进程通过终端接受整数,并将每个整数追加到共享数组中。后台进程在每次添加后将共享数组按升序排序。当最后一个元素放置在正确的位置时,前台进程将在终端上显示共享数组。
这是我的实现:

#include <stdio.h>
#include <stdlib.h>

#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <semaphore.h>

#define MAX_BUFFER_SIZE 64
#define BUFFER_NAME "/shared"

struct data {
  int buffer[MAX_BUFFER_SIZE];
  sem_t mutex;
};

int foreground_process(int size)
{
  int element;

  int buff_fd = shm_open(BUFFER_NAME, O_RDWR, 0666);
  struct data *D = mmap(NULL, sizeof(struct data), PROT_READ | PROT_WRITE, MAP_SHARED, buff_fd, 0);

  sem_init(&(D->mutex), 7, 1); 

  for (int i = 0; i < size; ++i) {
    sem_wait(&(D->mutex));
      printf("Enter element %d: ", i);
      scanf("%d", &element);
      
      D->buffer[i] = element;
    sem_post(&(D->mutex));

    sleep(1);
  }

  sem_wait(&(D->mutex));
    printf("Final array: ");
    for (int i = 0; i < size; ++i)
      printf("%d ", D->buffer[i]);
    printf("\n");
  sem_post(&(D->mutex));

  close(buff_fd);

  exit(0);
}

int background_process(int size)
{
  int prev_element, tmp;

  int buff_fd = shm_open(BUFFER_NAME, O_RDWR, 0666);
  struct data *D = mmap(NULL, sizeof(struct data), PROT_READ | PROT_WRITE, MAP_SHARED, buff_fd, 0);

  for (int i = 0; i < size; ++i) {
    sem_wait(&(D->mutex));
    
    prev_element = D->buffer[i];
    printf("Got element %d at B[%d]!\n", D->buffer[i], i);

    for (int j = 0; j < i; ++j) {
      if (D->buffer[j] > prev_element) {
        tmp = D->buffer[j];
        D->buffer[j] = prev_element;
        prev_element = tmp;
      }
    }

    D->buffer[i] = prev_element;

    sem_post(&(D->mutex));

    sleep(1);
  }

  close(buff_fd);

  exit(0);
}

int main(void) {
  int size;
  pid_t fg_pid, bg_pid;

  pid_t wpid;
  int status;

  printf("Size: ");
  scanf("%d", &size);

  int buff_fd = shm_open(BUFFER_NAME, O_CREAT | O_RDWR, 0666);
  ftruncate(buff_fd, sizeof(struct data));

  if ((fg_pid = fork()) == 0) foreground_process(size);
  else if (fg_pid < 0) perror("foreground process");

  sleep(1);

  if ((bg_pid = fork()) == 0) background_process(size);
  else if (bg_pid < 0) perror("background process");

  while ((wpid = wait(&status)) != - 1)
    fprintf(stderr, "Process %d exists with status %d\n", wpid, WEXITSTATUS(status));

  shm_unlink(BUFFER_NAME);

  return 0;
}

字符串
要共享的数据对象是一个struct,其中包含一个整数数组&一个二进制信号量(用于同步)。
这样就可以了:O/P示例:

Size: 5
Enter element 0: 2
Got element 2 at B[0]!
Enter element 1: 10
Got element 10 at B[1]!
Enter element 2: 1
Got element 1 at B[2]!
Enter element 3: 9
Got element 9 at B[3]!
Enter element 4: 8
Got element 8 at B[4]!
Final array: 1 2 8 9 10 
Process 1849 exists with status 0
Process 1850 exists with status 0


但这不是一个合法的解决方案,因为我使用了3次sleep()来实现这种来回同步。在fork后台和前台进程之间,确保前台进程首先运行。第二次和第三次,在前台和后台进程中的for循环的每次迭代之后。
删除sleep()会产生各种各样的问题,例如,后台进程似乎被mutex饿死了,因此似乎无法运行等等,后台进程有时会在前台进程之前运行。
我能做些什么改进来解决这个问题吗?

xoefb8l8

xoefb8l81#

sleep()在线程或进程同步方案中没有合法的角色。相反,应使用适当的IPC机制,如互斥锁、信号量、条件变量和管道。
如果不使用sleep,则无法可靠地执行此操作,原因如下:

  • 您有一个用于设置信号量的竞态条件。您需要依靠前台进程来完成这项工作,但是如果没有人为的延迟,后台进程可能会在信号量准备好之前尝试等待它。

解决这个问题的方法不止一种,但我建议让父程序初始化信号量,就像它初始化共享内存一样,这样当其他程序启动时,它就已经准备好了。

  • 前台和后台进程等待相同的信号量,以便清除继续。如果你的意思是让他们轮流,那么这在逻辑上是有缺陷的:两者都解释并使用相同的“go”信号。sleep不能解决这个问题,但它确实降低了一个进程递增信号量、循环然后再次递减信号量而不给另一个进程运行的可能性(否则很高)。

如果你想用信号量来做这件事,那么使用 two。每个进程都在其中的一个进程上等待许可,并发布到另一个进程以让另一个进程继续。

6xfqseft

6xfqseft2#

为了补充John's answer,这里是您的示例的修改版本,使用两个信号量在两个进程之间创建一个 lockstep
请注意在父进程中如何初始化信号量,以及每个sem_waitsem_post的确切顺序。

#include <fcntl.h>
#include <semaphore.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <unistd.h>

#define MAX_BUFFER_SIZE 64
#define BUFFER_NAME "/shared"

struct data {
    int buffer[MAX_BUFFER_SIZE];
    sem_t input;
    sem_t sorting;
};

static int cmp(const void *ap, const void *bp)
{
    int a = *(const int *)ap;
    int b = *(const int *)bp;

    return (a > b) - (a < b);
}

static struct data *get_shared_data(int creat)
{
    int fd = shm_open(BUFFER_NAME, creat | O_RDWR, 0666);

    if (creat)
        ftruncate(fd, sizeof (struct data));

    struct data *dat = mmap(NULL, sizeof *dat,
            PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

    close(fd);
    return dat;
}

static void foreground_process(int size)
{
    struct data *dat = get_shared_data(0);

    for (int i = 0; i < size; i++) {
        sem_wait(&dat->input);

        printf("Enter #%d: ", i + 1);
        if (1 != scanf("%d", dat->buffer + i))
            dat->buffer[i] = i;

        sem_post(&dat->sorting);
    }

    sem_wait(&dat->input);

    printf("Result: ");
    for (int i = 0; i < size; i++)
        printf("%d ", dat->buffer[i]);
    putchar('\n');

    exit(0);
}

static void background_process(int size)
{
    struct data *dat = get_shared_data(0);

    for (int i = 0; i < size; i++) {
        sem_wait(&dat->sorting);

        printf("Got B[%d] as %d\n", i, dat->buffer[i]);
        qsort(dat->buffer, i + 1, sizeof *dat->buffer, cmp);

        sem_post(&dat->input);
    }

    exit(0);
}

int main(void)
{
    int size;
    pid_t fg_pid, bg_pid;

    pid_t wpid;
    int status;

    printf("Size: ");
    if (1 != scanf("%d", &size) || !size || size > MAX_BUFFER_SIZE) {
        fprintf(stderr, "Invalid size.\n");
        return 1;
    }

    struct data *dat = get_shared_data(O_CREAT);
    sem_init(&dat->input, 1, 1);
    sem_init(&dat->sorting, 1, 0);

    if ((fg_pid = fork()) == 0) foreground_process(size);
    else if (fg_pid < 0) perror("foreground process");

    if ((bg_pid = fork()) == 0) background_process(size);
    else if (bg_pid < 0) perror("background process");

    while ((wpid = wait(&status)) != - 1)
        fprintf(stderr, "Process %d exists with status %d\n", wpid, WEXITSTATUS(status));

    sem_destroy(&dat->input);
    sem_destroy(&dat->sorting);
    shm_unlink(BUFFER_NAME);
}

字符串
使用相同输入的结果:

Size: 5
Enter #1: 2
Got B[0] as 2
Enter #2: 10
Got B[1] as 10
Enter #3: 1
Got B[2] as 1
Enter #4: 9
Got B[3] as 9
Enter #5: 8
Got B[4] as 8
Result: 1 2 8 9 10 
Process 148440 exists with status 0
Process 148439 exists with status 0

9jyewag0

9jyewag03#

下面是一个使用Ada编程语言的来回同步生产者-消费者任务的示例。
Ada提供了一种使用Rendezvous模型进行同步任务通信的机制。Ada任务使用一个或多个任务条目直接通信。任务条目可以将数据从调用任务传递到被调用任务,或者从被调用任务传递到调用任务。也可以在根本不传递任何数据的情况下实现任务条目。在这种情况下,任务调用充当简单的事件通知。
工作项目同步行程是由语言隐含行程。没有对任何信号量或互斥体的显式调用。当一个任务调用另一个任务的条目时,调用任务将被挂起,直到被调用任务接受该条目。类似地,如果被调用的任务遇到任务条目的接受语句,则该任务将被挂起,直到另一个任务调用该条目。当两个任务(调用和被调用)同时调用和接受相同的条目时,任何指定的数据都将从一个任务传输到另一个任务。在完成数据传输后,任务恢复其并发行为。
自Ada标准的1983年版本以来,同步任务通信一直是核心Ada语言的一个特征。
以下针对此问题中指定的问题的解决方案使用了通过两个任务条目进行通信的两个任务。

-- There are two processes, a foreground and a background process.
-- Both share an integer array. The foreground process accepts integers
-- through the terminal and appends each integer to the shared array.
-- The background process keeps the shared array sorted in ascending order
-- after each addition. When the last element is placed in its correct
-- position the foreground process then displays the shared array on
-- the terminal.

-- The main procedure executes in the foreground process.
with Ada.Text_IO;         use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
with Ada.Containers.Generic_Array_Sort;

procedure Main is
   type Nums_array is array (Positive range <>) of Integer;

   procedure print (A : Nums_array) is
   begin
      for V of A loop
         Put (V'Image & " ");
      end loop;
      New_Line;
   end print;

   Num_Elements : Positive;

begin
   Put ("Enter the number of elements in the array: ");
   Get (Num_Elements);
   declare
      subtype arr is Nums_array (1 .. Num_Elements);

      task background is
         entry add_element (Item : in Integer);
         entry get_result (Item : out arr);
      end background;

      task body background is
         procedure sort is new Ada.Containers.Generic_Array_Sort
           (Index_Type => Positive, Element_Type => Integer,
            Array_Type => Nums_array);
         Result : arr := (others => Integer'First);
      begin
         for I in Result'Range loop
            accept add_element (Item : in Integer) do
               Result (Result'First) := Item;
            end add_element;
            sort (Result);
         end loop;

         accept get_result (Item : out arr) do
            Item := Result;
         end get_result;
      end background;
      Input  : Integer;
      Output : arr;
   begin
      Put_Line ("Enter the array values:");
      for I in arr'Range loop
         Get (Input);
         background.add_element (Input);
      end loop;
      background.get_result (Output);
      print (Output);
      New_Line;
   end;

end Main;

字符串
Nums_array型别是不受条件约束的数组型别,表示型别的任何特定执行严修在索引型别所施加的限制内,都可以有不同的长度。在本例中,索引类型为“正”。Positive是Integer的预定义子类型,最小值为1,最大值为Integer'Last,通常与C中的MAX_INT相同。
Main过程(也是Main任务或线程)声明一个名为Num_Elements的局部变量,它是Positive子类型的一个示例。
系统会提示使用者输入数组中的元素数目,并将回应指定给Num_Elements。
一个内部块从“declare”保留字开始。Ada要求在块、函数、过程、任务或包的声明性区域中声明所有类型、变量和常量。在此示例中,Nums_array的子类型被定义为索引范围从1开始到Num_Elements结束的数组类型。
后台任务定义有两个条目。

  • add_element条目允许前台任务向后台任务发送一个值。该值将传入后台任务。
  • get_result条目允许后台任务将排序后的数组传递给前台任务。该值从后台任务传出。后台任务的实现在任务主体中定义。“任务主体声明性区域用于示例化在Ada.Containers.Generic_Array_Sort包中找到的通用排序过程。然后创建一个名为Result的arr子类型示例,数组中的所有值都初始化为Integer'First,这通常与C中的MIN_INT相同。

后台任务遍历Result数组,在每次迭代中接受add_element条目中的值。在接受来自前台任务的新值之后,对数组进行排序。当所有元素都已添加到数组中时,后台数组接受get_result条目,并将整个数组传递给前台任务。然后,后台任务终止。
主过程也是前台任务。因此,它会提示用户为所有Num_Elements数组元素输入值,每次都调用background.add_element并将输入得值传递给后台责任.完成输入循环后,主任务调用background.get_result并打印后台任务传递给它的数组值。
前台任务和后台任务之间的通信完全同步,而无需显式地操纵同步原语。
使用Ada编写的第二个版本实现了由前台任务和后台任务访问的共享缓冲区。共享缓冲区是使用Ada保护对象实现的。Ada保护的对象会自动受到保护,防止不适当的任务访问。
可以通过三种受保护的方法访问受保护的对象。这些方法是:

  • 函数,这些函数对封装在受保护对象中的数据实现共享只读锁。共享只读锁允许多个任务同时从受保护对象读取数据,因为只读访问可防止出现任何争用情况。

  • 过程,这些过程对封装在受保护对象中得数据实现无条件独占读写锁.一次只允许一个任务持有读写锁。

  • 项,这些项对封装在受保护对象中的数据实现有条件的读写排他锁。每个条目都有一个关联的边界条件,该条件的值必须为TRUE才能访问受保护对象。当边界条件的值为FALSE时,调用任务被挂起并置于隐式实现的入口FIFO队列中,等待边界条件的值为TRUE。下列范例只会使用受保护的项目来控制对受保护对象中数组的存取,因此可确保前台工作只能在缓冲区没有新元素时写入缓冲区,而后台工作只能在缓冲区有新元素时排序数组。只有当数组处于完全排序状态时,前台任务才能获取共享数组的副本。

作为Ada 1995标准的一部分,Ada保护对象被添加到该语言中。
此解决方案的Ada程序代码为:

with Ada.Text_IO;         use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
with Ada.Containers.Generic_Array_Sort;

procedure buffered_back_forth is
   type Nums_Array is array (Positive range <>) of Integer with
      Default_Component_Value => Integer'First;
   Num_Elements : Positive;
begin
   Put ("Enter the number of array elements: ");
   Get (Num_Elements);

   declare
      subtype arr is Nums_Array (1 .. Num_Elements);

      protected buffer is
         entry add_element (Item : in Integer);
         entry sort_array;
         entry get_buffer (Item : out Nums_Array);
      private
         Buf         : arr;
         New_Element : Boolean := False;
      end buffer;

      protected body buffer is
         entry add_element (Item : in Integer) when not New_Element is
         begin
            Buf (Buf'First) := Item;
            New_Element     := True;
         end add_element;

         entry sort_array when New_Element is
            procedure sort is new Ada.Containers.Generic_Array_Sort
              (Index_Type => Positive, Element_Type => Integer,
               Array_Type => Nums_Array);
         begin
            sort (Buf);
            New_Element := False;
         end sort_array;

         entry get_buffer (Item : out Nums_Array) when not New_Element is
         begin
            Item := Buf;
         end get_buffer;
      end buffer;

      task background;

      task body background is
      begin
         for I in arr'Range loop
            buffer.sort_array;
         end loop;
      end background;

      New_Num : Integer;
      Result  : arr;
   begin
      Put_Line ("Enter the array values:");
      for I in arr'Range loop
         Get (New_Num);
         buffer.add_element (New_Num);
      end loop;
      buffer.get_buffer (Result);
      for value of Result loop
         Put (value'Image & " ");
      end loop;
      New_Line;
   end;
end buffered_back_forth;

相关问题