【问题标题】:When should we choose other IPC instead of direct memory access for inter-thread communication我们什么时候应该选择其他IPC而不是直接内存访问进行线程间通信
【发布时间】:2013-08-27 19:10:28
【问题描述】:

由于同一进程中的线程共享相同的地址空间,我们可以通过直接内存访问和互斥锁在这些线程之间传输数据,那么在这种情况下我有以下问题:

  1. 全局变量和互斥锁是否足以支持线程间通信?
  2. 如果问题 1 为假,在什么情况下我们应该选择其他 IPC 而不是直接内存访问?或者说,在什么情况下其他 IPC 手段比使用全局变量和互斥体更合适?

谢谢。

更新
感谢@ssyam 指出关于“全局变量”的错误陈述。
除了更正原始段落之外,我选择再添加一个部分,因为 该段落给出了许多cmets。

【问题讨论】:

  • 附带说明(这里只是吹毛求疵...)IPC 表示“进程间通信”,因此它不能应用于线程(in-进程通信)。但无论如何,我们都明白你的意思。 :)
  • @syam 关于你的 nit。进程间通信这个术语出现在线程之前,但我从来没有听说过有人在应用于进程内通信时对这个术语犹豫不决。至少在 linux 中,进程和线程之间并没有太大的区别。
  • @Duck 实际上你是对的。查看维基百科(我知道......)的定义,似乎“IPC”一词也用于线程间通信(即使它扩展到“进程间......”)。我想我的强迫症又发作了,我的错。

标签: c++ linux multithreading networking ipc


【解决方案1】:

关于 (1) 我同意 Dietmar Kühl 的观点,即条件变量是最小集合的一部分。

关于 (2) 我倾向于选择 IPC,只要我能负担得起开销的小成本(主要是系统调用和一些数据复制),因为它们带来的易用性和灵活性。管道、消息队列、域套接字等都具有内置的原子性和同步性,它们根据情况需要提供阻塞、非阻塞或定时读/写。您可以将它们填充到 select 语句中,而无需做任何特别的事情。它以很小的成本获得了很大的功率,并且不涉及重新发明轮子。

【讨论】:

  • ..并且在进程本地线程之间传输大型数据缓冲区的效率低得惊人。
  • @Martin James - 只需将指针传递给缓冲区,它们都在同一个进程中。
【解决方案2】:

不需要全局变量。请记住,线程例程可以接受参数,因此它可能是任何类型的变量,包括动态分配的变量。

通常你会想要将你的线程“包装”在一个类中,类似于:

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)在线程之间进行通信(生产者/消费者模式),这是隔离线程并允许它们通信的一种非常有效的方式。


事实上,在单个进程中,除了直接内存访问之外,很少有其他的东西有意义。

我现在能想到的是,如果您已经有一些工作的进程间代码(例如套接字或共享内存),您可以重用这些代码以允许统一的接口,无论是进程内还是进程间通信。但不要自欺欺人,它不如直接内存访问效率低。然而,统一界面的好处可以轻松克服效率损失。恕我直言,您确实需要根据具体情况处理此类事情。

【讨论】:

    【解决方案3】:

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

    也就是说,在多线程应用程序中,在内存中的线程之间传输数据是合理的。但是,一般来说,我发现处理显式锁定是不可行的。在传输类似于消息传递系统的数据时,即使消息是内存中的数据结构,代码也会变得不那么复杂,效率更高。从这个意义上说,一条消息在任何时候都只能由一个线程使用,唯一的锁定发生在消息传递工具中。

    【讨论】:

    • 我对此感到困惑已久。我们有一个多线程应用程序,其中包含大约 100 个线程,并且所有线程都使用直接共享内存访问而不是消息传递(这里的消息传递是指我们为进程间通信所做的方式)。我发现代码变得丑陋,到处都是互斥锁。
    • 问题是确定何时使用互斥锁和条件变量的直接内存访问以及何时使用消息传递方式的边界在哪里。除了简单常规的直接内存访问,什么时候应该选择消息传递?
    • @StevePeng 消息传递只是直接内存访问的另一种形式。它通常涉及一个存储消息的队列,并且该队列由 mutex/condition_variable 对控制。但最后,您仍然可以直接访问进程的内存,而不使用任何系统工具,除了同步工具。我反对使用套接字、共享内存设施 (mmap) 或其他涉及通过内核进行大量间接的东西。希望我说清楚。 :)
    • @StevePeng:我总是使用通过进程内队列的消息传递来在线程之间传输信息!基本上,系统传递具有单个线程处理给定任务的任务,而不是尝试编排许多对象。唯一使用互斥锁和条件变量的地方是通信原语,例如,用于传输消息的队列(我们的内部版本也很好地处理了线程池)。基本思想叫Actor Model
    • @syam 谢谢,我知道系统内部肯定有一些类似的访问保护机制,但正如 Dietmar Kühl 指出的那样,“一条消息在任何时候都只能由一个线程使用,并且只有锁定发生在消息传递设施中”,因此用户代码可以变得不那么复杂(如果有数百个线程访问相同的一堆不同类型的资源,效率会更高?我不确定)。跨度>
    【解决方案4】:

    这是一个想法:

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

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

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

    【讨论】:

    • '另一方面,IPC 调用不一定需要这个' - 真的吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-11-23
    • 2019-01-24
    • 1970-01-01
    • 2015-12-12
    • 2011-01-25
    • 2011-01-07
    • 2017-07-16
    相关资源
    最近更新 更多