我怀疑,线程共享进程的所有段,堆栈除外。因此,为了在线程之间进行通信,假设我想将一个词“Hello”从一个线程传递到另一个线程,那么 IPC 机制(例如消息队列)的需要是什么。
2 回答
在一个进程中的线程之间以及与其他进程中的线程之间有各种各样的数据通信方式。您只需选择一个适合您的需求。
共享内存
单个进程中的线程可以访问进程中的所有内存,尽管正如您所说,它们有自己的堆栈。这意味着一个线程与另一个线程共享一块内存是相当直接的。通常你会使用某种信号量来确保它们不会同时尝试访问内存(这通常会导致非常混乱的程序行为......)。
代码快速高效,因为数据永远不会被复制,但要正确处理复杂的程序可能非常困难。
消息队列、管道等
但是,消息队列之类的东西是一种将数据从一个线程使用的内存发送到另一个线程使用的内存的方法。
有很多优点,因为使用内存队列编写复杂程序通常比共享内存要容易得多。但是,内存队列本质上效率较低。这是因为操作系统中的消息队列实现必须复制数据(通常是两次)。
线程与进程
对于共享内存方法,将所有线程放在一个进程中是很自然的,而对于消息队列,将线程放在单独的进程中开始变得更加自然。但这并不重要。大多数操作系统允许进程将它们的一些内存暴露给其他进程(Linux 上的 /dev/shm),并且命名信号量可以被任何进程获取。
因此,从架构上讲,“一个进程中的所有线程”与“它们自己进程中的所有线程”的问题并不重要。您可能需要记住操作系统的线程与进程上下文切换时间,因为这也会影响效率。
可扩展性
如果您选择像管道这样的 IPC 机制,那么就有机会实现可扩展性。特别是管道的行为非常像套接字。因此,如果您的程序在您的计算机上运行得太慢,那么将管道转换为套接字并将线程置于分布在两台或多台计算机上的进程中并不需要太多工作。所以使用管道意味着把你的应用程序变成多机分布式应用程序并不难。
当然,套接字要慢得多,因此在确定之前,您必须考虑每秒实际需要从一个线程发送到另一个线程的数据量。
CPU 内存架构
还记得我说过内存队列的效率不如共享内存吗?嗯,这几天不是很清楚。英特尔当前的架构,尤其是 AMD 的架构,意味着复制数据与读取数据并没有太大区别。
查看英特尔在芯片之间的 QPI 链接,并考虑在电子设备层面实际发生了什么。假设您的机器中有两个已填满的 CPU 插槽,并且您的应用程序有两个线程共享一个内存缓冲区。
对于一个芯片上的线程要访问驻留在另一个芯片内存中的数据,它必须通过 QPI 链路读取该数据。
现在,假设这些线程使用了消息队列。操作系统必须将数据从一个线程复制到另一个线程。这意味着通过 QPI 链接读取数据。QPI 链接上的活动量大致相似。
AMD 的架构使这一点更加明显。与 QPI 相当的(和更优秀的......)被称为 Hypertransport,它在 AMD 的芯片中运行。
这不是那么直截了当。缓存使事情变得更加复杂。但是对于一些有很多线程分布在两个或更多芯片上共享大量数据的程序,QPI 有可能减慢一切。英特尔当然知道这一点并对其进行设计,以使 QPI 通常不会成为瓶颈。但是,如果您的程序不是英特尔在选择 QPI 参数时所考虑的,那么性能可能会低于预期。
实际上,在某些情况下并且非常小心地使用管道、消息队列等是可能的,并且最终获得比使用共享内存的程序更好的性能。
但是,出于简单性和可靠性的原因,我仍然会选择管道和消息队列。
您真的应该花几个小时通过阅读好的Pthreads 教程来了解更多信息。您可能应该了解更多基本的 IPC(进程间通信)机制,例如通过阅读Advanced Linux Programming
使用支持最新C++11标准的 C++ 编译器,您可以使用其C++ 线程库(通常构建在 pthreads 之上)。为此,您需要GCC 4.8 或更高版本或 Clang 3.3 或更高版本(使用-std=c++11
标志g++
或clang++
编译器)。
某个给定进程的所有线程都通过一些内存模型共享相同的地址空间(并且经常使用硬件辅助缓存一致性) 。
我建议您了解有关proc(5)的更多信息。该命令cat /proc/1234/maps
显示 pid 1234 进程的地址空间。
特别是,堆栈实际上并不是每个线程的隔离段:换句话说,一个线程可以访问其他线程堆栈上的一些数据。例如,您可以让一个线程在其堆栈上具有一个局部 变量,将地址写入某处(例如,在具有C 语句的全局变量中)并让另一个线程通过该指针取消引用(但这是一种糟糕的编程风格)。给定进程的所有线程堆栈仍然在同一地址空间中。int x;
&x;
g
g = &x;
*g
但是,您真正需要的是同步线程。pthread的一个主要目的是为您提供多种同步方式(互斥锁和条件变量、屏障、信号量等)。最近的语言标准还具有原子性结构,例如C++11 的std::atomic或 C11 的stdatomic.h。当心竞争条件和死锁!由于可能存在 heisenbug,调试多线程(或其他并行)程序很痛苦。您可能应该偏爱函数式编程风格。
多线程编程很难,因为并行编程很困难。
您可以在线程之间使用 IPC 机制,例如,您可以有一个pipe(2)并让一个线程写入它,另一个线程读取它(可能与poll(2)多路复用......)。但是在编写线程时,人们通常更喜欢使用 pthread 机制(例如,有一个全局数据链接某个链表并通过锁定互斥锁来串行访问它)。然后,您需要使用条件变量发出从空到非空的转换信号,并在从(空)链表中获取元素时等待该条件。这是一种生产者-消费者的情况。你可以有一个共享队列,或者一个列表等......对于像你这样的字符串,"Hello World"
但你仍然需要同步线程。