2-6-1-1 QNX编程入门之进程和线程(六)
阅读前言
本文以QNX系统官方的文档英文原版资料“Getting Started with QNX Neutrino: A Guide for Realtime Programmers”为参考,翻译和逐句校对后,对在QNX操作系统下进行应用程序开发及进行资源管理器编写开发等方面,进行了深度整理,旨在帮助想要了解QNX的读者及开发者可以快速阅读,而不必查看晦涩难懂的英文原文,这些文章将会作为一个或多个系列进行发布,从遵从原文的翻译,到针对某些重要概念的穿插引入,以及再到各个重要专题的梳理,大致分为这三个层次部分,分不同的文章进行发布,依据这样的原则进行组织,读者可以更好的查找和理解。
1. 进程和线程
1.3. 线程和进程
1.3.3. 启动一个线程
2-6-1-1 QNX编程入门之进程和线程(一)
2-6-1-1 QNX编程入门之进程和线程(二)
2-6-1-1 QNX编程入门之进程和线程(三)
2-6-1-1 QNX编程入门之进程和线程(四)
2-6-1-1 QNX编程入门之进程和线程(五)
接前面章节内容继续。
1.3.3.5. 相互独立的场景中的多线程
正如上文在 “线程是个好主意” 部分所讨论的那样,当存在多个带有共享数据结构的独立处理算法同时运行时,线程也能派上用场。严格来讲,虽然你可以让多个进程(每个进程含一个线程)显式地共享数据,但在某些情况下,在一个进程中使用多个线程反而会方便得多。让我们来看看为什么以及在哪些情况下会这样使用线程。
在我们的示例中,我们将逐步构建一个标准的输入/处理/输出(input/process/output)模型。从最通用的意义上来说,该模型的一部分负责从某处获取输入,另一部分负责对输入进行处理以生成某种形式的输出(或控制信息),第三部分则负责将输出发送到某处。
1.3.3.5.1. 多进程
让我们首先从多进程(每个进程一个线程)的角度来理解这种情况。在这种情况下,我们会有三个进程,确切地说是一个输入进程、一个“处理”进程和一个输出进程。
这是一种高度抽象的形式,也是 “耦合性” 最弱的形式。“输入” 进程与 “处理” 进程或 “输出” 进程之间并没有真正的 “绑定” 关系,它仅仅负责收集输入信息,并以某种方式将其传递给下一个阶段(即 “处理” 阶段)。对于 “处理” 进程和 “输出” 进程,我们也可以这么说,它们彼此之间同样没有真正的绑定关系。在这个示例中,我们还假定通信路径(也就是从输入到处理以及从处理到输出的数据流)是通过某种面向连接的协议来实现的(例如,管道、POSIX 消息队列、原生 QNX Neutrino 消息传递,诸如此类)。
1.3.3.5.2. 多进程共享内存
根据数据流的体量大小,我们可能想要对通信路径进行优化。
实现这一点最简单的方法就是让这三个进程之间的耦合更紧密一些。我们现在不再使用通用的面向连接的协议,而是选择一种共享内存机制(在示意图中,粗线表示数据流,细线表示控制流):
在这种机制下,我们加强了耦合程度,从而实现了更快速、更高效的数据流。我们可能仍然会使用一种 “通用” 的面向连接的协议来传输 “控制” 信息,我们预计控制信息不会占用大量带宽。
在这里我们可以看到,一个进程中有三个线程。这三个线程隐式地共享数据区域。而且,控制信息既可以像之前示例中那样实现,也可以通过一些线程同步原语来实现(我们已经了解过互斥量、屏障和信号量,稍后我们还会看到其他的)。
1.3.3.5.4. 比较
现在,让我们从多个方面来对比这三种方法,同时也会阐述它们各自的一些权衡取舍之处。
对于系统 1,我们看到它的耦合性是最松散的。这有一个优势,那就是这三个进程中的每一个都可以轻松地被替换为不同的模块(也就是通过命令行操作,而非重新编译或重新设计)。这是很自然的,因为 “模块化单元” 就是整个模块本身。系统 1 也是唯一的一种可以在 QNX Neutrino 网络中的多个节点间进行分布式部署的方式。由于通信路径是通过某种面向连接的协议进行抽象的,所以很容易明白这三个进程可以在网络中的任意机器上执行。对于你的设计来说,这可能是一个非常强大的可扩展性因素,你可能需要你的系统能够在地理上扩展到(或者以其他方式,比如基于外围硬件能力)分布在数百台机器上并且相互通信。
然而,一旦我们采用了共享内存区域这种方式,我们就失去了在网络上进行分布式部署的能力。QNX Neutrino 操作系统并不支持网络分布式的共享内存对象。所以在系统 2 中,我们实际上就把自己限制在了同一台机器上运行这三个进程。我们并没有失去轻松移除或更改某个组件的能力,因为我们仍然拥有可以通过命令行进行控制的独立进程。但我们增加了一个限制条件,那就是所有可移除的组件都需要遵循共享内存模型。
在系统 3 中,我们失去了上述所有的能力。我们肯定没办法将一个进程中的不同线程运行在多个节点上(不过,我们仍然可以将它们运行在对称多处理器(SMP)系统中的不同处理器上)。而且我们也失去了配置方面的灵活性,现在我们需要有一个明确的机制来定义我们想要使用哪种 “输入”“处理” 或 “输出” 算法(但是,我们可以通过共享对象,也就是动态链接库(DLLs)来解决这个问题)。
那么,我为什么还要把我的系统设计成像系统 3 那样包含多个线程的形式呢?为什么不选择灵活性最大的系统 1 呢?
嗯,尽管系统 3 是最缺乏灵活性的,但它很可能是运行速度最快的。不同进程中的线程之间不存在线程上下文切换的问题,也不必显式地设置内存共享,而且不需要使用像管道、POSIX 消息队列或消息传递这类抽象的同步方法来传递数据或控制信息,可以使用基本的内核级线程同步原语。另一个优势是,当由一个进程(包含三个线程)所描述的系统启动时,我们知道我们所需的一切都已经从存储介质中加载好了(也就是说,我们不会之后才发现 “哎呀,磁盘上的处理驱动程序不见了!”)。最后,系统 3 也很可能是占用空间最小的,因为我们不会有三份 “进程” 信息(例如,文件描述符)的副本。
总而言之:要了解其中的权衡取舍之处,然后根据你的设计需求来选用合适的方式。
未完待续,请继续关注本专栏内容……