如何在共享内存数据中完成同步 linux c

how synchronization is done in shared memory data linux c

我在面试中被问到一个问题,同步是如何在共享内存中完成的。我告诉Take a struct。因为你有一个标志和一个数据。测试标志并更改数据。 我从互联网上获取了以下程序,如下所示。谁能告诉我共享内存中是否有更好的同步方式

#define  NOT_READY  -1
#define  FILLED     0
#define  TAKEN      1

struct Memory {
     int  status;
     int  data[4];
};

假设服务器端和客户端在当前目录下。服务器使用 ftok() 生成密钥并将其用于请求共享内存。在共享内存被数据填充之前,状态设置为NOT_READY。共享内存填满后,服务器将状态设置为 FILLED。然后,服务器等待状态变为 TAKEN,这意味着客户端已获取数据。

以下是服务器程序。单击此处下载此服务器程序的副本 server.c。

#include  <stdio.h>
#include  <stdlib.h>
#include  <sys/types.h>
#include  <sys/ipc.h>
#include  <sys/shm.h>

#include  "shm-02.h"

void  main(int  argc, char *argv[])
{
     key_t          ShmKEY;
     int            ShmID;
     struct Memory  *ShmPTR;

     if (argc != 5) {
          printf("Use: %s #1 #2 #3 #4\n", argv[0]);
          exit(1);
     }

     ShmKEY = ftok(".", 'x');
     ShmID = shmget(ShmKEY, sizeof(struct Memory), IPC_CREAT | 0666);
     if (ShmID < 0) {
          printf("*** shmget error (server) ***\n");
          exit(1);
     }
     printf("Server has received a shared memory of four integers...\n");

     ShmPTR = (struct Memory *) shmat(ShmID, NULL, 0);
     if ((int) ShmPTR == -1) {
          printf("*** shmat error (server) ***\n");
          exit(1);
     }
     printf("Server has attached the shared memory...\n");

     ShmPTR->status  = NOT_READY;
     ShmPTR->data[0] = atoi(argv[1]);
     ShmPTR->data[1] = atoi(argv[2]);
     ShmPTR->data[2] = atoi(argv[3]);
     ShmPTR->data[3] = atoi(argv[4]);
     printf("Server has filled %d %d %d %d to shared memory...\n",
            ShmPTR->data[0], ShmPTR->data[1], 
            ShmPTR->data[2], ShmPTR->data[3]);
     ShmPTR->status = FILLED;

     printf("Please start the client in another window...\n");

     while (ShmPTR->status != TAKEN)
          sleep(1);

     printf("Server has detected the completion of its child...\n");
     shmdt((void *) ShmPTR);
     printf("Server has detached its shared memory...\n");
     shmctl(ShmID, IPC_RMID, NULL);
     printf("Server has removed its shared memory...\n");
     printf("Server exits...\n");
     exit(0);
}

客户端部分与服务器端类似。它一直等到状态为 FILLED。然后,客户端检索数据并将状态设置为 TAKEN,通知服务器数据已被获取。下面是客户端程序。单击此处下载此服务器程序的副本 client.c。

#include  <stdio.h>
#include  <stdlib.h>
#include  <sys/types.h>
#include  <sys/ipc.h>
#include  <sys/shm.h>

#include  "shm-02.h"

void  main(void)
{
     key_t          ShmKEY;
     int            ShmID;
     struct Memory  *ShmPTR;

     ShmKEY = ftok(".", 'x');
     ShmID = shmget(ShmKEY, sizeof(struct Memory), 0666);
     if (ShmID < 0) {
          printf("*** shmget error (client) ***\n");
          exit(1);
     }
     printf("   Client has received a shared memory of four integers...\n");

     ShmPTR = (struct Memory *) shmat(ShmID, NULL, 0);
     if ((int) ShmPTR == -1) {
          printf("*** shmat error (client) ***\n");
          exit(1);
     }
     printf("   Client has attached the shared memory...\n");

     while (ShmPTR->status != FILLED)
          ;
     printf("   Client found the data is ready...\n");
     printf("   Client found %d %d %d %d in shared memory...\n",
                ShmPTR->data[0], ShmPTR->data[1], 
                ShmPTR->data[2], ShmPTR->data[3]);

     ShmPTR->status = TAKEN;
     printf("   Client has informed server data have been taken...\n");
     shmdt((void *) ShmPTR);
     printf("   Client has detached its shared memory...\n");
     printf("   Client exits...\n");
     exit(0);
}

Can anyone tell if there is better way of synchronization in shared memory?

当然,是的。我会说你在忙等待 (while (ShmPTR->status != FILLED) ;) 中浪费 CPU 个周期的方式已经是一个致命的错误。

请注意,POSIX 共享内存具有比旧 SysV 更明智的接口。有关详细信息,请参阅 man 7 shm_overview

同步原语有两个不同的用途:

  1. 数据同步

    为了保护数据免受并发修改,并确保每个 reader 获得一致的数据视图,可采用三种基本方法:

    • 原子访问

      原子访问需要硬件支持,通常仅支持本机字大小的单位(32 或 64 位)。

    • 互斥锁和条件变量

      互斥锁是互斥锁。这个想法是在检查或修改值之前获取互斥量。

      条件变量基本上是线程或进程等待 "condition" 的无序队列。 POSIX pthreads 库包括用于自动释放互斥量和等待条件变量的工具。这使得等待数据集更改变得微不足道,如果每个修改器在每次修改后都在条件变量上发出信号或广播。

    • 读写锁。

      rwlock 是一种原语,它允许任意数量的并发 "read locks",但任何时候只能持有一个 "write lock"。这个想法是每个 reader 在检查数据之前获取一个读取锁,并且每个写入者在修改它之前获取一个写入锁。当检查数据的频率高于修改数据的频率时,这种方法效果最佳,并且不需要等待更改发生的机制。

  2. 进程同步

    有些情况下线程和进程应该等待 (block) 直到某个事件发生。有两个最常用的原语用于此:

    • 信号量

      A POSIX semaphore 基本上是一个不透明的非负计数器,您可以将其初始化为任何值(零值或正值,在实现设置的限制内)。

      sem_wait() 检查计数器。如果它非零,它会递减计数器并继续执行。如果计数器为零,它会阻塞,直到另一个 thread/process 在计数器上调用 sem_post()

      sem_post() 递增计数器。它是您可以在 signal handler.

    • 中使用的罕见同步原语之一
    • 障碍

      屏障是一种同步原语,它会阻塞直到屏障中阻塞了特定数量的线程或进程,然后立即将它们全部释放。

      Linux 不实现 POSIX 障碍(pthread_barrier_init()pthread_barrier_wait()pthread_barrier_destroy()),但您可以使用互斥锁轻松实现相同的效果,一个计数器(计算释放所有等待者所需的额外进程数)和一个条件变量。


有许多更好的方法来实现上述服务器-客户端对(其中共享内存包含标志和一些数据)。

为了数据完整性和变更管理,应该使用互斥锁和一两个条件变量。 (如果服务器可能随时更改数据,一个条件变量(changed)就足够了;如果服务器必须等到客户端读取数据后再修改数据,则需要两个条件变量(changedobserved).)

这是一个示例结构,您可以用来描述共享内存段:

#ifndef   SHARED_H
#define   SHARED_H
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

struct shared_data {
    /* Shared memory data */
};

struct shared {
    pthread_mutex_t     lock;
    pthread_cond_t      change;     /* Condition variable for clients waiting on data changes */
    pthread_cond_t      observe;    /* Condition variable for server waiting on data observations */
    unsigned long       changed;    /* Number of times data has been changed */
    unsigned long       observed;   /* Number of times current data has been observed */
    struct shared_data  data;
};

/* Return the size of 'struct shared', rounded up to a multiple of page size. */
static inline size_t  shared_size_page_aligned(void)
{
    size_t  page, size;
    page = (size_t)sysconf(_SC_PAGESIZE);
    size = sizeof (struct shared) + page - 1;
    return size - (size % page);
}

#endif /* SHARED_H */

changedobserved 字段是计数器,有助于避免任何检查时间到使用时间的竞争 windows。重要的是,在访问共享内存之前,线程会 pthread_mutex_lock(&(shared_memory->lock)),以确保数据的一致视图。

如果 thread/process 检查数据,它应该做

    shared_memory->observed++;
    pthread_cond_broadcast(&(shared_memory->observe));
    pthread_mutex_unlock(&(shared_memory->lock));

如果 thread/process 修改数据,它应该做

    shared_memory->modified++;
    shared_memory->observed = 0;
    pthread_cond_broadcast(&(shared_memory->change));
    pthread_mutex_unlock(&(shared_memory->lock));

在解锁互斥体时通知任何服务员并更新计数器。