多线程编程程序设计研究

时间:2022-10-09 07:12:25

多线程编程程序设计研究

摘要:随着硬件性能的提高,多线程编程技术已经成为软件业的主流。文章首先介绍多线程编程所面临的挑战,其次介绍ZeroMQ消息中间件可以如何应对该问题,并对其中的两项关键技术做了分析,最后对采用ZeroMQ与传统方法下多线程编程的性能进行对比。

关键词:ZeroMQ;多线程编程;阻塞;无锁编程

1、多线程编程的挑战

自摩尔定律提出以来,CPU主频一直以指数级的速度在增长。为了充分利用硬件性能,改善程序体验,软件业广泛采用了多线程编程技术。但是由于多个线程是动态运行的,使得这类程序的编写和调试异常困难[1-2]。与单线程程序相比,软件开发人员往往需要付出数十倍的精力。近几年,随着CPU主频逐渐逼近物理极限,芯片工业逐步向多核发展,但新增加的CPU性能无法全面发挥出来。除了像Erlang[3]这样的少数语言外,绝大多数编程语言,包括C和C++,并没有提供对于并发编程的支持。在传统多线程编程技术中,采用加锁以及信号量的方式来实现线程同步。这样的方式存在如下一些问题:(1)编写以及维护这类代码代价非常高昂。根据经验估算,编写多线程代码的成本是编写单线程代码成本的10~100倍;(2)这类方法很难扩展到更多线程。大多数多线程应用使用2个线程(例如典型的生产者消费者线程),有一些会用到3个或者4个。这表明对于一个有着16个或者更多核心的CPU,其硬件性能没有充分利用起来;(3)即便代码是多线程的,它往往也不能受益于多核CPU,因为不同的线程间经常彼此阻塞。开发者很难发现精巧的多线程程序实际上已经退化成了一个单线程;(4)即便在最理想的情况下,应用程序被设计成避免大范围使用加锁操作,还是很难扩展到超过10个核心的CPU上。随着线程数以及CPU核数的增加,硬件资源的利用率会急剧下降;(5)线程被换入CPU中会引发程序的上下文切换,以及CPU缓冲区中的内容失效,这也将极大地影响整个程序的执行效率。为了避免加锁导致的阻塞,最新的编程理论提出了无锁算法来实现数据共享。该算法需要用到硬件指令集中的“比较以及交换原子操作”[4]来避免加锁。为了达到无锁编程的准确性以及安全性,程序员需要具备硬件以及编译器方面的知识背景。无锁编程技术虽然提高了软件性能,但是其高度复杂性使得软件开发过程充满了挑战,所以这样的技术目前并没有获得广泛的使用。

2、ZeroMQ消息中间件技术

ZeroMQ是由iMatix公司开发的一款开源的消息中间件。最初的设计目标是在股票交易系统中实现极快的数据交换,所以性能是设计的首要考虑因素。ZeroMQ看起来像是一套嵌入式的网络链接库,但工作起来更像是一个并发式的框架。它可以在多种协议中传输消息,如线程间、进程间、TCP、广播等。开发人员可以据此构建多种连接模式,如:-订阅、任务分发、请求-应答等。它对几乎所有主流语言均可支持,并能在几乎所有的操作系统上运行。ZeroMQ目前已经在很广泛的范围内获得使用,包括:金融服务、游戏开发、嵌入式系统、科学研究,以及航天系统中。为了达到高性能,ZeroMQ采用了两项核心技术来实现高效的消息传输,分别为并发模型和无锁队列。

2.1ZeroMQ的并发模型

为了充分利用CPU的多核特性,ZeroMQ被设计成完全避免使用锁,从而使得每个线程能够全速运行。线程之间的通信采用基于事件的异步消息发送模式,即经典的参与者模式。它为每一个CPU核启动一个工作线程,从而避免了当两个线程共享一个核时所做的线程上下文切换操作(见图1)。ZeroMQ的内部对象紧密地与特定工作线程绑定到一起。这样就不需要使用任何临界区、锁,以及信号量等同步操作。并且每个ZeroMQ内部对象与特定的CPU核也是关联的,这样也避免了上下文切换,有效利用CPU缓存区机制。这种设计避免了传统多线程开发所面临的诸多问题,因为线程之间不再需要共享对象。不过在该设计中,需要提供一个调度器。调度器采用基于事件驱动的方式管理ZeroMQ内部对象,从而避免了在整个循环中去检测对象,也避免了对象长时间地占用CPU这种情况。这样,整个系统工作在异步模式下,所有的对象以状态机的方式运行。

2.2ZeroMQ的无锁队列

为了保证并发操作,ZeroMQ使用了无锁队列在用户线程以及ZeroMQ工作线程之间交换数据。无锁队列中用到无锁算法,该算法不依赖操作系统所提供的锁以及信号量等机制实现数据共享,但是需要依赖CPU提供的原子操作。无锁算法本质上并不是“无锁”的,只是它的锁是在硬件层面上实现。此外,无锁队列的两个特别设计使得其拥有很高的性能。第一个特点是:每个队列对应一个写线程和一个读线程(见图2)。在一个写线程对应多个读线程的通信环境下,ZeroMQ将创建多个队列。这种读写之间一一对应的模式,加上无锁操作,使得无锁队列的实现非常高效。第二个特点是:采用批处理的方法来写入或者读取消息。虽然无锁编程算法比传统的基于信号量的算法更高效,但是CPU的原子操作很费时,特别是当CPU的多个核之间存在竞争时。为了解决这个问题,ZeroMQ采用了批处理的方法来应对。假设从网络上收到了一个数据包,其中包含了10个小消息。为了将这些消息写入无锁队列中去,需要使用10次原子操作。ZeroMQ采用的方式是,先将单个消息保存到一个预写区域中,累积到一定数目后,使用一次原子操作,一起写入队列中;类似地,在读取时也应用了预读取缓冲区。

3、ZeroMQ性能验证

3.1测试方法

通过对ZeroMQ工作原理的分析,知道其性能无疑会超过传统多线程方法的。但是这样的性能差距到底有多大,需要通过实验来分析。ZeroMQ作为消息中间件可以在多种应用场合传递消息。其中的线程间通信模式,使得其可以很好地用于多线程应用程序。在其多种消息传递模型中,任务分发的消息模式特别适合典型的生产者-消费者这种应用形式。传统多线程编程方法采用了互斥锁以及信号量来实现生产者-消费者线程之间的数据共享。每一个生产者线程与一个消费者线程作为一个线程对。线程的执行时间以生产者线程开始工作,到消费者线程处理完最后一个数据之间的时间间隔作为该线程对的执行时间。多线程情况下,将各个线程对的执行时间做相加处理。在每次测试期间,指定采用ZeroMQ的方法与采用传统多线程的方法这两种程序所处理的消息数是相等的。对不同条件下的执行时间进行统计,从而比较哪种方法性能更优。

3.2性能分析

通过测试,分别采用两种方法的多线程程序所用的时间如表1所示。表1中的数据是在一个采用了超线程技术的2核i5CPU上执行结果。从表1中可以看出,采用ZeroMQ的多线程编程方法比采用传统的方法在各种情况下性能均占优势,这表明其对于并发编程有更好的支持。此外,在与其他主流消息队列,比如:RabitMQ、ActiveMQ以及MSMQ的性能比较中,ZeroMQ也大幅胜出。考虑到ZeroMQ在软件开发以及调试上的便利性,采用其进行多线程软件开发将带来巨大的成本优势。

4、结语

随着CPU内核数越来越多,如何有效地发挥硬件运算能力成为越来越重要的任务。传统的基于加锁以及信号量做线程同步的方法已经越来越不适应性能要求;最新的基于无锁算法的编程技术虽然能够提升性能,但是实现的过程非常复杂,使得软件开发充满了挑战性。ZeroMQ这一消息中间件很好地弥补了性能与易用性之间的鸿沟,使得普通开发人员也可以编写出高性能、可靠的软件产品。

参考文献

[1]叶崧,姚健东.基于ZeroMQ&JSON的分布式测控系统消息通信架构设计[J].现代电子技术,2014(2):105-109.

[2]张俊帅.多线程技术在数据通信中的应用[J].科技创新与应用,2016(11):87.

[3]蒲凤平,陈建政.基于ZeroMQ的分布式系统[J].电子测试,2012(7):24-27.

[4]宋海友,张巧珍.多线程技术在数据通信中的应用[J].电子技术与软件工程,2015(5):64.

作者:沙卫平 芮挺 高琦煜 张赛 邹军华 单位:理工大学 野战工程学院

上一篇:一带一路倡议下人民币国际化战略研究 下一篇:高级程序设计课程教学改革研究