我们何时应该选择其他IPC而不是直接内存访问进行线程间通信?

4

由于同一进程中的线程共享相同的地址空间,因此我们可以通过直接内存访问和互斥锁在这些线程之间传递数据。在这个背景下,我有以下问题:

  1. 全局变量和互斥锁是否足以实现线程间通信?
  2. 如果问题1的答案是否定的,那么在什么情况下我们应该选择其他的IPC(进程间通信)而不是直接内存访问呢?或者说,在什么情况下使用其他IPC方法比使用全局变量和互斥锁更合适?

谢谢。

更新
感谢 @ssyam 指出关于“全局变量”的错误陈述。
我选择添加一个额外的部分来除了纠正原始段落之外,因为已经有很多评论针对该段落。


作为一个旁注(只是挑刺一下……),IPC 意味着“进程间通信”,因此不能应用于线程(进程内通信)。但我们都明白你的意思。 :) - syam
@syam 关于你的小问题。术语“进程间通信”比线程先出现,但我从未听说过有人对将其应用于进程内通信感到不满。在Linux中,至少在进程和线程之间没有太大的区别。 - Duck
@Duck 其实你是对的。看了维基百科(我知道...)的定义,似乎“IPC”这个术语也用于线程间通信(尽管它扩展为“进程间...”)。我想我的强迫症又犯了,我的错。 - syam
4个回答

1
不需要使用全局变量。请记住,线程例程可以接受参数,因此它可以是任何类型的变量,包括动态分配的变量。
通常,您会希望在类中“封装”您的线程,类似于:
struct Thread
{
    Thread() : m_thread(&Thread::run, this) {}
    void run()
    {
        // access the current object's member variables, eg:
        do_something_with(m_myvar);
    }

    Object m_myvar;
    std::thread m_thread;
};

但是如果我们忽略全局变量这个小细节,你的#1是正确的...通过受到互斥保护的变量进行通信(无论是消息队列、布尔变量等)并且选择性地使用condition_variable(作为唤醒触发器)几乎总是正确的方法。我自己几乎总是使用线程安全的消息队列(即std::queue + mutex + condition_variable)在线程之间进行通信(生产者/消费者模式),这是一种既隔离线程又允许它们相互通信的非常有效的方式。
事实上,在单个进程内,除了直接内存访问之外,很少有其他情况是有意义的。
我现在能想到的唯一情况是,如果你已经有一些有效的进程间通信代码(例如套接字或共享内存),你可以重用这些代码,以便允许统一的接口,无论是进程内还是进程间通信。但不要自欺欺人,它肯定会比直接内存访问效率低。然而,统一接口的好处可以轻松地克服效率损失。在我看来,你真的需要根据具体情况处理这种问题。

1

在(1)上,我同意Dietmar Kühl的看法,认为条件变量是该最小集合的一部分。

在(2)上,我倾向于选择IPC,只要我能够承受开销的小成本(主要是系统调用和一些数据复制),因为它们带来了便捷和灵活性。 管道、消息队列、域套接字等都具有原子性和同步功能,并且根据情况提供阻塞、非阻塞或定时读/写。 而且,您可以将它们全部放入select语句中,而无需进行任何特殊操作。 这是小成本获得的大量功率,并且不需要重新发明轮子。


1
对于在进程本地线程之间传输大数据缓冲区,它们效率极低,令人难以置信。 - Martin James
@Martin James - 只需传递指向缓冲区的指针,它们都在同一个进程中。 - Duck

0

这里有一个想法:

尽管两个线程正在访问同一块内存,但并不一定意味着它们看到的是相同的值。如果一个线程正在更新值,则另一个线程可能会看到陈旧的值 - 更新之前从处理器本地缓存中取出的值。为了防止这种情况发生,您需要使用互斥锁或其他技术来同步线程。

无论使用哪种技术,都必须使用所谓的“内存屏障”来刷新本地缓存,这是一项非常昂贵的操作,因为它将要求所有处理器停止它们正在进行的任何操作并等待操作完成。

另一方面,IPC调用并不一定需要这样做。


1
"IPC调用另一方并不一定需要这样做" - 真的吗? - Martin James

0

全局变量在单线程代码中很糟糕,在多线程代码中通常会成为一个主要问题。即使使用互斥锁进行同步,它们也往往会成为瓶颈。此外,互斥锁通常不足以实现线程间通信。通常,您还需要条件变量。

话虽如此,在多线程应用程序中,在内存中传输数据是合理的。但是,我发现处理显式锁定通常不可行。当将数据传输类似于消息传递系统时,代码往往较少复杂且更有效率,即使消息是内存中的数据结构。从这个意义上说,一条消息在任何时候只被一个线程使用,并且唯一发生的锁定是隐含在消息传递设施中的。


我已经困惑了很长时间。我们有一个多线程应用程序,其中大约有100个线程,并且所有线程都使用直接共享内存访问而不是消息传递(这里的消息传递是指我们用于进程间通信的方式)。我发现代码变得丑陋,到处都是互斥锁。 - Wallace
问题在于如何确定何时使用带有互斥锁和条件变量的直接内存访问,以及何时改用消息传递方式。除了简单和传统的直接内存访问之外,我应该何时选择消息传递呢? - Wallace
@StevePeng 消息传递只是直接内存访问的另一种形式。它通常涉及存储消息的队列,并且该队列由互斥/条件变量对控制。但最终,您仍然直接访问进程的内存,而不使用任何系统设施,而是使用同步设施。我反对使用套接字、共享内存设施(mmap)或其他需要通过内核进行大量间接操作的东西。希望我表达清楚了。 :) - syam
@syam:嗯,我所描述的并不完全是演员模型(因为通常涉及消息序列化,即实际上传输的是值),但它很相似。...同样的想法有许多名称和变体。我不喜欢生产者/消费者,因为大多数演员实际上都是两者兼备,但基本上是相同的想法。 - Dietmar Kühl
如果可能的话,尽量采用消息传递模型,确保没有数据被多个线程操作。这样做肯定是最好的选择。 - Martin James
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接