我有一个疑问,线程在进程中共享所有段,但栈除外。因此,如果要在线程之间通信,比如我想从一个线程传递一个单词“你好”到另一个线程,是否需要使用IPC机制(例如消息队列)。
我有一个疑问,线程在进程中共享所有段,但栈除外。因此,如果要在线程之间通信,比如我想从一个线程传递一个单词“你好”到另一个线程,是否需要使用IPC机制(例如消息队列)。
在进程内或跨进程线程之间,有许多种数据通信方式可供选择,您只需选择适合您需求的一种即可。
共享内存
单个进程中的线程可以访问进程中的所有内存,尽管它们有自己的堆栈。这意味着一个线程可以很容易地与另一个线程共享一个内存片段。通常情况下,您会使用某种信号量来确保它们不会同时访问内存(否则通常会导致非常混乱的程序行为...)。
代码快速高效,因为数据从未被复制,但是对于编写复杂程序来说可能非常困难。
消息队列、管道等
然而,像消息队列这样的东西是从一个线程使用的内存发送数据到另一个线程使用的内存的一种方式。
使用内存队列编写复杂程序通常更加容易,所以它具有很多优点。但是,内存队列天生就不太有效率。这是因为操作系统中的消息队列实现必须复制数据(通常要复制两次)。
线程 vs 进程
采用共享内存的方法,将所有线程放在单个进程中是自然的,而使用消息队列则更自然地将线程放在单独的进程中。不过,这并不重要。大多数操作系统允许进程向其他进程公开一些内存(例如Linux上的/dev/shm),任何进程都可以获取命名信号量。
因此,在架构上,“所有线程在一个进程中”与“所有线程在自己的进程中”的问题并不重要。您可能需要考虑操作系统的线程与进程上下文切换时间,因为这也会影响效率。
可扩展性
如果您选择像管道这样的IPC机制,则有可扩展性的机会。特别是,管道的行为非常类似于套接字。因此,如果您的程序在计算机上运行得太慢,将管道转换为套接字并将线程放置在分布在两台或多台计算机上的进程中并不那么困难。因此,使用管道意味着将您的应用程序转换为多计算机分布式应用程序并不是很困难。在某些情况下,通过使用管道、消息队列等方式,并进行大量细心的处理,可以实现比使用共享内存的程序性能更好的效果。
然而,出于简单性和可靠性方面的原因,我仍会选择使用管道和消息队列。
你应该花几个小时通过阅读一本好的Pthreads教程来学习更多内容,特别是基础的IPC(进程间通信)机制。你可以通过阅读高级Linux编程来更好地理解。
如果你的C++编译器支持最新的C++11标准,那么你可以使用其C++线程库(通常是在pthread之上构建的)。为此,你需要使用GCC 4.8或更高版本,或者Clang 3.3或更高版本(使用-std=c++11
标志来编译g++
或clang++
编译器)。
某个给定的进程的所有线程通过某种内存模型共享相同的地址空间(通常使用硬件辅助的缓存一致性)。
我建议您了解更多关于proc(5)的信息。命令cat /proc/1234/maps
会显示pid为1234的进程的地址空间。
特别地,堆栈并不是每个线程的隔离段:换句话说,一个线程可以访问其他线程堆栈上的一些数据。例如,您可以在一个具有int x;
局部变量的线程上,将地址&x;
写入某个位置(例如,在全局变量g
中使用g = &x;
C语句),然后通过*g
解引用该指针(但这是一种糟糕的编程风格)。给定进程的所有线程的堆栈仍然位于同一个地址空间中。
然而,你真正需要的是同步线程。 pthreads 的主要目的是提供几种同步手段(互斥锁和条件变量、屏障、信号量等)。最近的语言标准也有原子性构造,例如C++11的std::atomic或C11的stdatomic.h。注意竞态条件和死锁!调试多线程(或其他并行)程序很痛苦,因为可能会出现海森堡式错误。你可能应该倾向于函数式编程风格。
多线程编程很难,因为并行编程很困难。
您可以在线程之间使用IPC机制,例如,您可以有一个pipe(2),让一个线程写入它,另一个线程读取它(也许使用poll(2)进行多路复用...)。但是编写线程时,人们通常更喜欢使用pthread机制(例如,将一些链接列表与全局数据链接,并通过locking互斥量串行地访问它)。然后,您需要使用条件变量信号从空到非空的转换,并在从(空)链接列表中获取元素时等待该条件。这是一个producer-consumer情况。您可以有一个共享队列或列表等字符串,例如"Hello World"
,但仍然需要同步线程。