【问题标题】:Shared memory - need for synchronization共享内存 - 需要同步
【发布时间】:2012-04-27 21:00:32
【问题描述】:

我见过一个项目,其中进程之间的通信是使用共享内存进行的(例如,在 Windows 下使用::CreateFileMapping)并且每次其中一个进程想要通知共享内存中有一些数据可用时,使用的同步机制命名事件通知相关方共享内存的内容发生了变化。

我担心这样一个事实,即读取新信息的进程不存在适当的内存栅栏,以知道它必须使其数据副本无效,并在数据被“发布”后从主内存中读取它生产者进程。

您知道如何在 Windows 上使用共享内存来实现这一点吗?

编辑 只是想补充一点,在创建文件映射后,进程只使用一次 MapViewOfFile() API,并且对共享数据的每次新修改都使用通过初始调用 MapViewOfFile() 获得的指针来读取通过共享内存发送的新数据。正确的同步是否要求每次共享内存中的数据发生变化时,读取数据的进程每次都必须创建 MapViewOfFile() ?

【问题讨论】:

  • 要成为一种有效的同步机制,命名事件应该有适当的内存栅栏。您是否有证据表明这可能不会发生?
  • @Alexey:确实,通常是这样,但是标准/规范/等有保证吗?
  • 在 windows 上可以使用 CreateEvent api 进行同步。我不知道设置和等待该事件是否会产生获取/释放必要的内存栅栏

标签: c++ windows concurrency shared-memory


【解决方案1】:

如果您使用 Windows 命名事件来发出更改信号,那么一切都应该没问题。

进程A更改​​数据并调用SetEvent

进程 B 使用 WaitForSingleObject 或类似方法等待事件,并看到它已设置。

然后进程 B 读取数据。 WaitForSingleObject 包含所有必要的同步,以确保进程 A 在调用 SetEvent 之前所做的更改被进程 B 读取。

当然,如果您调用SetEvent之后对数据进行了任何更改,那么当进程B读取数据时这些可能会或可能不会显示。

如果您不想使用事件,您可以使用使用CreateMutex 创建的互斥锁,或者您可以使用Interlocked... 函数(例如InterlockedExchangeInterlockedIncrement)编写无锁代码。

无论您如何进行同步,都无需多次调用MapViewOfFile

【讨论】:

    【解决方案2】:

    您在 Windows 上寻找的共享内存是 InterlockedExchange 功能。请参阅msdn article here。引用了真正重要的部分:

    此函数生成一个完整的内存屏障(或栅栏)以确保 内存操作按顺序完成。

    这将跨进程运行。我以前使用过它,发现它对于在共享内存上实现类似互斥的构造是 100% 可靠的。

    您如何做到这一点是将其与“设置”值进行交换。如果你得到“清除”回来,你就有了(很清楚),但如果你得到“设置”回来,那么其他人就有了。你循环,循环之间的睡眠等等,直到你“得到”它。基本上是这样的:

    #define LOCK_SET 1
    #define LOCK_CLEAR 0
    
    int* lock_location = LOCK_LOCATION; // ensure this is in shared memory
    if (InterlockedExchange(lock_location, LOCK_SET) == LOCK_CLEAR)
    {
        return true; // got the lock
    }
    else
    {
        return false; // didn't get the lock
    }
    

    如上,循环直到你“得到”它。

    【讨论】:

    • 所以我在这里有一个互斥锁实现,也可以使用命名的 win 关键部分,但是如何在生产者和消费者进程中使用它?
    • 您的问题是如何在共享内存中建立同步机制。这将使您能够做到这一点。从这里,您可以设计自己的一组计数器,或在共享内存中受此保护的其他变量。如果您不知道如何自己实现,则需要阅读一些有关同步的基础知识。但是,如果您只需要生产者消费者,我会研究命名管道或套接字或其他进程间通信。共享内存并不总是解决简单问题的最佳选择。
    • 我只需要知道平台为我需要的东西提供什么保证
    • 我从MSDN文章中引用的是保证两个进程指向同一个共享内存,如果通过这个函数使用,它将是原子的。而且我自己已经完成了(这不是“理论上的”代码,而是在生产中)。我不知道你还想要什么。
    • 只是解决问题 tnx for support
    【解决方案3】:

    我们称进程 A 为数据生产者,进程 B 为数据消费者。到目前为止,您有一种机制让进程 A 通知进程 B 新数据已生成。我建议您创建一个反向通知(从 B 到 A),它告诉进程 A 数据已被使用。如果出于性能原因,您不希望进程 A 等待数据被消耗,您可以在共享内存中设置 ring-buffer

    【讨论】:

    • 是的,但如何确保消费者看到数据发布?
    • @Ghita 默认情况下,x86 架构是缓存一致的。一个处理器的写入总是被后续读取看到,无论是同一系统的同一处理器还是同一系统的其他处理器。您所要做的就是确保编译器在数据写入共享内存后调用通知函数。我认为您应该将内存标记为“易失性”(在 C 中)以保证这一点。
    • 嗯,我认为即使在 x86 上也可以重新排序以提高性能
    • @Ghita IIRC,x86 架构使用MESI 协议来实现缓存一致性。在写入内存“区域”(即高速缓存行)之前,处理器需要拥有它。仅当处理器是所有者或缓存行没有所有者时,才允许处理器读取缓存行。
    猜你喜欢
    • 2012-12-31
    • 1970-01-01
    • 1970-01-01
    • 2019-10-25
    • 2014-04-08
    • 2019-10-22
    • 2019-06-03
    • 1970-01-01
    • 2018-09-23
    相关资源
    最近更新 更多