一种基于共享内存的消息传递方法

文档序号:6482229阅读:337来源:国知局
专利名称:一种基于共享内存的消息传递方法
技术领域
本发明涉及解决消息的生产者消费者问题的方法,尤其是软件系统中多段并发执
行的代码通过访问共享内存的方式传递消息的方法。
背景技术
在计算机程序设计中经常遇到多个并发执行的代码之间通过一片共享的内存区 域传递消息的情形。 一般称产生消息的代码为生产者,处理消息的代码为消费者,二者被统 称为任务。此时的消息传递一般这样实现生产者和消费者在两者都能访问的内存中分配 一片内存区域(该区域被称作消息槽),生产者将消息记录在该消息槽,消费者读取该消息 槽获得消息。 由于生产者和消费者一般是异步执行的,可能会在某段时间内出现生产者生产消 息的速度快于消费者消费消息的速度。这时,为了避免让生产者被迫等待消费者处理消息 或者消息在消费之前被覆盖,程序员一般会分配多个消息槽。这些消息槽的集合被称作消 息缓冲区。此时,生产者将消息写入消息缓冲区的某个消息槽,消费者从消息缓冲区的消息 槽中读取消息。当不采用任何同步机制时,可能会发生以下三种类型的错误1)读写冲突 消费者试图读取"生产者正在进行写入的消息槽",这就造成消费者获得了错误的消息;2) 多次处理多个消费者试图从同一个消息槽读取消息,这就造成了一个消息被多次处理; 3)写写冲突多个生产者试图向同一个消息槽写入消息,这就造成消息的丢失甚至最终产 生不正确的消息。 为了避免发生这种生产者和消费者访问消息缓冲区时相互干扰的情况,通常的做 法是使用一个锁(lock)来强制所有的生产者或消费者只能采取串行的方式访问消息缓冲 区生产者和消费者在试图访问某个消息槽前,必须先试图获得整个消息缓冲区的锁,只有 成功获得锁后才能继续执行,否则就一直尝试或者睡眠,直到成功获得锁。上锁成功者将在 访问消息槽结束后释放已获得的锁,并且有可能唤醒睡眠在该锁上的执行者(生产者或消 费者)。这种方式使得在同一个时刻最多只能有一个生产者或消费者访问消息缓冲区中的 消息槽,虽然使得不再出现任何访问冲突,但也有如下缺点 首先,这种基于锁的方法限制了软件系统的并发性。当生产者、消费者访问不同的 消息槽时,实际上并不冲突,它们可以并发运行,但当使用锁保护整个消息缓冲区使得这种 并发不再可能。 其次,当不能成功获得锁的生产者/消费者转入睡眠时,系统运行的开销很大。由 于消费者或生产者一般仅在获得锁后执行很少的几条指令就释放锁,而睡眠和唤醒则需要 执行大量的指令,睡眠会严重影响访问消息缓冲区的性能。 第三,当不能成功获得锁的生产者/消费者一直尝试获得锁时,系统很容易出现 死锁。当采用固定优先权时,若低优先权的一方获得了锁,但高优先权的一方获得了 CPU执 行权,系统会出现死锁获得了 CPU执行权的一方无法获得锁,一直尝试;低优先权的一方 获得了锁,但没有机会在CPU上运行。
最后,当生产者、消费者不是传统操作系统中的线程、进程时,这种方法并不适用。 如当操作系统内核通过共享缓冲区与核外线程进行消息通信时,操作系统内核无条件优先 于核外线程运行,且内核不是一个线程,若内核获得锁不成功不能转入睡眠。
目前关于生产者和消费者问题最好的解决方案是非阻塞缓冲区 NBB (Non-BlockingBuffer)方法。它没有上述缺点,可有效地解决单个生产者和单个消费者 之间的消息传递问题。NBB方法为为一个环形消息缓冲区设立head和tail两个指针,消 费者只关心head所指向的消息槽,在消费完消息后将head调整为指向当前head指向消息 槽的下一个消息槽;生产者只关心tail所指向的消息槽,在生产完消息后将tail调整为当 前tail指向消息槽的下一个消息槽。只要二者不相等,生产者和消费者之间就不会同时访 问一个消息槽,从而不会出现任何访问冲突;当head与tail相等时定义消息缓冲区为空, 生产者在完成生产后才将tail的值调整为指向下一个消息槽,从而在head和tail相等时 只有生产者访问消息缓冲区。 NBB的主要缺点是仅支持单生产者单消费者问题。如多个消费者同时访问head 所指向的消息槽将导致消息被多次处理的错误,多个生产者同时访问tail所指向的消息 槽将导致产生写写冲突。 在有些多生产者多消费者系统中,可以采取设置生产者锁来解决写写冲突,采取 设置消费者锁来防止消息被多次处理。但对于不同生产者具有不同CPU执行优先权的场 合,有可能会出现低优先权的生产者获得了锁,但高优先权的生产者获得了 CPU执行权,由 于获得CPU执行权的一方无法获得锁,而获得锁的一方又没有机会释放锁,从而出现死锁。
—般的说,对于有M个生产者、N个消费者的情形,NBB要求设立M*N个非阻塞缓冲 区,这会浪费大量的存储空间,且程序设计不便。更重要的是,消费者之间不能自动进行负 载平衡存放在第j (j为小于M的自然数)个消费者消息缓冲区中的消息不能被现在处于 空闲状态的第i (i为小于N的自然数)个消费者使用。

发明内容
本发明要解决的技术问题是提供一种新的基于共享内存的消息传递方法,实现
多生产者多消费者情形下生产者和消费者之间高并行消息通信。 本发明的技术方案是 本发明的技术方案包括以下步骤 1在共享内存中创建并初始化消息缓冲区——消息槽数组。消息槽数组从0开始 编号、容量为C,C的大小由生产者(消费者)数量、生产者(消费者)生产(消费)消息的 频度、系统对"消息槽满"事件的容忍程度、系统可用内存等因素综合决定。每个元素包含 一个消息槽及该消息槽对应的状态。每个消息槽处于以下三种状态之一 "有消息","无消 息"、"使用中"。初始化时所有的消息槽都设置成"无消息"状态。当消息槽被生产者获取 后,生产者将消息槽改为"使用中"状态,以便阻止其它生产者和消费者使用本消息槽;当生 产者将已生产的消息放入消息槽后,它将消息槽状态由"使用中"修改为"有消息";当处于 "有消息"状态的消息槽被消费者获得后,消息槽的状态被更新为"使用中",消费者就可以 安全的访问存放在本消息槽中的消息了 ;当消费者获得了完整消息之后,它将消息槽的状 态由"使用中"更新为"无消息"。
在共享内存中分配两个变量head和tail,并令head = tail = 0。在本发明中, 每当生产者和消费者成功获取了某个消息槽的独占式访问权限时,head或tail的值都将 被加1。因此在一段时间内head和tail的值是否发生过变化就反映了是否有生产者或消 费者在本段时间内改变过消息槽数组的状态,所以它们的取值被称为版本号。同时,head
和tail也是消息槽数组的索引。head指向的编号为head% C的消息槽,tail指向编号为 tail% C的消息槽,其中%为求余数运算。 由于计算机系统硬件字长有限,head和tail的值可能会因为溢出而重新变为0, 进而导致head和tail又变回了它的某个初始值。记head和tail从取值为value到下次 取值再次为value所经历的最短时间为Tv,则在head和tail发生变化时,连续两次获取相 同值必须至少间隔Tv。记生产者一次存放消息期间连续两次访问tail或消费者一次获取 消息期间连续两次访问head的时间间隔最大为Ta(这个时间间隔包含了程序指令执行时 间和因为调度带来的睡眠时间)。则在对head和tail进行"加l"操作时,由于计算机字 长至少为32位(即head和tail溢出的时间大于232次"加l"操作),因此Ta < i;,故当 生产者发现tail的值没有发生变化时,tail的值实际上也一定没有发生过变化;当消费者 发现head的值没有发生变化时,head的值实际上一定没有发生过变化。而一般做法是将 head和tail增到缓冲区大小C时就将head和tail置0,这将导致Tv过小,使得虽然head 和tail值发生过变化,但生产者和消费者看不到这个变化。 2第x (x为小于生产者个数M的自然数)个生产者Px采用以下方法访问消息槽数 组,以插入其生产出的消息 2. 1创建生产者Px的私有变量cached_tailx ; 2. 2将当前的tail值缓存到Px的私有变量cached_tailx中; 2. 3判断cachecLtail,是否与head+C相等,若相等则表示消息槽数组没有可用的
消息槽,执行用户自定义的"消息缓冲区满"处理流程(如等待或报错等),然后转到步骤
2. 2,若不相等则转2. 4 ;这个判断保证了生产者不可能试图去覆盖未被消费的消息。 2. 4比较交换原子指令CAS(&tail, cached_tailx, cached_tailx+l),若指令失败
则转步骤2.2,否则执行2.5。 大多数微处理器均支持CAS指令,不支持CAS指令的微处理器一般均提供可以模 拟CAS功能的类似指令。CAS指令可以用函数CAS(addr, old_value, new_value)来表示, 其含义为若在CAS指令执行时内存地址addr处的值为olcLvalue,则将其修改为new_ value,并返回操作成功;否则addr处的值保持不变,返回操作失败。在CAS (&tail, cached— tail, cached_tail+l)中,&tail表示获取tail变量的内存地址。CAS指令具有一个很重 要的性质即使系统中存在多个微处理器,硬件保证CAS指令都只能串行执行,不可能出现 在同一个时刻有多于1个处理器同时执行CAS指令的情况。 若多个生产者,如生产者P。和生产者P工都缓存了当前tail的最新值,记这些缓存 值为cached_tail。 = cachecLtai^ = . . . = Y。由于CAS指令执行的串行性,在同一时刻 只能有一个生产者执行CAS指令。设生产者P。成功执行了 CAS指令,则tail值被更新为 Y+l。当Pi执行CAS时会出现其cachecLtaili为Y而tail > Y,从而CAS指令会执行失败, Pi必须重新尝试,在重新尝试时tail的值将大于Y,而P。只操作版本号Y所指向的消息槽, 由此写写冲突不可能发生。
6
2. 5使用CAS(&stat,无消息,使用中)操作修改cachecLtailJ旨向的消息槽状态, 尝试将cachecLtail,指向的消息槽的状态设置成"使用中"状态。若操作失败转2.2,否则 执行2. 6。其中stat为CaChed_tailx指向的消息槽的状态变量。 —般说来cachecLtail,指向的消息槽状态应该是无消息,但有些生产者若在执行 步骤2. 4和2. 5之间暂时放弃了 CPU,则有可能会出现cached_tailx指向的消息槽状态不 为无消息的情形。如设生产者Px刚执行完步骤2. 4时CaChed_tailx对应消息槽的状态为 无消息,但在执行2. 5前由于操作系统调度的原因Px暂时停止执行,则head和tail在其 它生产者和消费者的作用下会继续增长。有可能在某一时刻tail会等于CaChed_tailx+C。 此时,别的生产者也会获得与本生产者同样的消息槽,并修改了消息槽的状态。这时若P/咴 复执行,会发现消息槽的状态不是无消息。 若本步骤执行成功,则CaChed_tailx指向的消息槽的状态被设置成"使用中"状 态,这保证了生产者Px对于该消息槽的访问是独占式的。 步骤2. 4和步骤2. 5不能颠倒。假设生产者先执行2. 5,然后执行2. 4,则若低优 先权的生产者修改了消息槽状态,但没有来得及调整tail的值就被高优先权的生产者抢 先执行了,高优先权的生产者将发现消息槽已被标为使用中,但是它没有办法去尝试下一 个消息槽,从而造成死锁。 2. 6将消息写入CaChed_tailx指向的消息槽。 具体的写入方法因应用而异。为了縮短消息写入时间,建议在执行步骤2. 2前首
先将消息写入另外一片共享内存区,在本步骤仅写入存放消息的内存地址。 2. 7释放消息槽将CaChed_tailx指向的消息槽的状态由"使用中"更新为"有消
息",释放本生产者私有的临时变量CaChed_tailx。 3消费者Cx(x为小于生产者个数N的自然数)采用以下方法访问消息槽数组,以 获取消息 3. 1创建生产者Cx的私有变量cached_headx ;
3. 2将当前的head值缓存到cached_headx中; 3. 3判断CaChed_headx是否与tail相等,若相等则说明所有的消息槽都为"无消 息"状态,执行用户自定义的"消息缓冲区空"处理流程(如等待或报错等),转步骤3. 2,若 不相等则转3. 4 ; 这个判断保证了消费者和生产者不可能访问同一个消息槽,从而避免了读写冲突。 3. 4执行CAS (&head, cached_headx, cached_headx+l)指令,若指令失败则转步骤 3. 2,否则执行3. 5。 若多个消费者都缓存了当前head的最新值,记这些缓存值为cachedjiead。= cachedjieac^ = . . . = Z。由于CAS指令执行的串行性,在同一时刻只能有一个消费者执行 CAS指令。设(;成功执行了 CAS指令,则head值被更新为Z+1。其它的消费者在执行CAS 时会出现其cachedjiead为Z而head > Z,从而CAS指令会执行失败,这些失败者必须重新 尝试,在重新尝试时head的值将大于Z,而Cx则只操作版本号Z所指向的消息槽,由此多次 读取不可能发生。 3.5使用CAS(&sta^,有消息,使用中)操作修改caChed_headx指向的消息槽状态,尝试将CaChed_headx指向的消息槽的状态设置成"使用中"状态。若操作失败转3. 2, 否则执行3. 6。其中statx为cachecLhead,指向消息槽的状态变量。 —般说来cachecLhead,指向的消息槽应该为有消息状态,但若有些消费者在执行 步骤3. 4和3. 5之间暂时放弃了 CPU,则有可能会出现cachecLhea4指向的消息槽不是有消 息状态。如设消费者Cx刚执行完步骤3. 4时CaChed_headx对应消息槽的状态为有消息, 但在执行3. 5前(;因某种原因暂时停止了运行,则head和tail在其它生产者和消费者的 作用下会继续增长。有可能在某一时刻head会等于cachecLhea4+C。此时,别的消费者者 也会获得与本生产者同样的消息槽,若别的消费者成功的执行了步骤3. 5,则该消息槽的状 态会变为使用中。这时若C/咴复执行,会发现消息槽的状态不是有消息。若本步骤执行成 功,则可保证了消费者Cx对于cachecLhead,所指向的消息槽的访问是独占式的。
—般说来,由于消费者能处理各种类型的消息,它们之间具有不同的优先权没有 什么实际意义,所以3. 4和3. 5两个步骤可以颠倒,但按照3. 4和3. 5的顺序性能会更高些。
3. 6获取cachecLhead,指向的消息槽中的消息。 3. 7释放消息槽将cachecLhead,指向的消息槽状态由"使用中"更新为"无消 息",释放本消费者私有的临时变量cachecLhead^
采用本发明可以达到以下技术效果 采用本发明可以实现任意场景下多个生产者和多个消费者并行进行消息通信。对 于可以使用锁的场景而言,采用本发明的系统性能远优于使用锁的系统性能。


图1是本发明总体流程图; 图2为本发明第二步生产者生产消息的流程图;
图3为本发明第三步消费者消费消息的流程图; 图4为本发明与基于锁实现的系统进行的"平均发送一条消息所用的时间"的比 较示意图。
具体实施例方式图1为本发明的总体流程图。 第一步,在共享内存中创建消息槽数组。 第二步,生产者在有消息要放入消息槽时进行消息插入;消费者在需要消费消息 时进行消息获取。
图2为本发明中生产者生产消息的流程图。 此执行流程与公知的生产者消费者问题中生产者的执行流程基本相同,区别主要 在于消息槽的获取和释放过程,即图2中的虚线展开部分。
生产者获取消息槽的流程如下 第一步,创建一个本生产者私有的临时变量CaChed_tailx ;
第二步,将当前tail的值放入cached_tailx变量中; 第三步,若tail的值等于head+C,则执行缓冲区满的处理流程,然后转第二步;否 则继续执行;
第四步,执行CAS(&tail, cached_tailx, cached_tailx+l)操作,若失败,则转第二 步;否则继续执行; 第五步,执行CAS(&sta^,无消息,使用中)操作。其中&statx为CaChed_tailx所 指向消息槽的状态变量的地址,若失败,则转第二步;否则表明获得消息槽成功,。
生产者释放消息槽的流程为 第一步,将CaChed_tailx指向的消息槽状态由"使用中"改为"有消息";
第二步,释放本生产者私有的临时变量CaChed_tailx。
图3为本发明中消费者消费消息的流程图。 此执行流程与公知的生产者消费者问题中消费者的执行流程基本相同,区别主要 在于消息槽的获取和释放过程,即图3中的虚线展开部分。
消费者获取消息槽的流程如下 第一步,创建一个本消费者私有的临时变量cachedjiead ;
第二步,将当前head的值放入cached_headx变量中; 第三步,若head的值等于tail,则执行缓冲区空的处理逻辑,然后转第二步;否则 继续执行; 第四步,执行CAS(&head, cached_headx, cached_headx+l)操作,若失败,则转第二 步;否则继续执行; 第五步,执行CAS(&sta^,有消息,使用中)操作。其中&statx为CaChed_headx所
指向消息槽的状态变量的地址,若失败,则转第二步;否则表明获得消息槽成功。 消费者释放消息槽的流程为 第一步,将CaChed_headx指向的消息槽状态由"使用中"改为"无消息";
第二步,释放本消费者私有的临时变量caChed_headx。 图4为本发明与基于锁实现的系统进行的"平均发送一条消息所用的时间"的比 较示意图。经对采用锁机制的系统和采用本发明的系统消息平均发送时间进行评测,评测 表明,当生产者和消费者数量均为1时,采用锁机制的系统和采用本发明的系统消息平均 发送时间均较小,分别为1.24微秒和0. 55微秒;随着生产者和消费者数量的增加,基于锁 的系统的消息发送时间快速增长,而采用本发明的系统则具有较好的可扩展性,如当生产 者和消费者均为6个时,基于锁的系统平均发送一条消息需要耗时13. 34微秒,而采用本发 明的系统平均发送一条消息则只需要4. 31微秒。
权利要求
一种基于共享内存的消息传递方法,其特征在于包括以下步骤第一步,在共享内存中创建并初始化消息缓冲区——消息槽数组,消息槽数组每个元素包含一个消息槽及该消息槽对应的状态,每个消息槽处于以下三种状态之一“有消息”,“无消息”、“使用中”;初始化时所有的消息槽都设置成“无消息”状态,当消息槽被生产者获取后,生产者将消息槽改为“使用中”状态;当生产者将已生产的消息放入消息槽后,它将消息槽状态由“使用中”修改为“有消息”;当处于“有消息”状态的消息槽被消费者获得后,消息槽的状态被更新为“使用中”;当消费者获得了完整消息之后,它将消息槽的状态由“使用中”更新为“无消息”;同时,在共享内存中分配两个变量head和tail,并令head=tail=0,这两个变量只能按照递增的方式进行改变,它们的取值被称为版本号;同时,head/tail也是消息槽数组的索引,head指向的编号为head%C的消息槽,tail指向编号为tail%C的消息槽,其中%为求余数运算;第二步,第x个生产者Px采用以下方法访问消息槽数组,以插入其生产出的消息,x为小于生产者个数M的自然数2.1创建生产者Px的私有变量cached_tailx;2.2将当前的tail值缓存到Px的私有变量cached_tailx中;2.3判断cached_tailx是否与head+C相等,若相等则执行用户自定义的“消息缓冲区满”处理流程,然后重新执行步骤2.3,若不相等则转2.4;2.4比较交换原子指令CAS(&tail,cached_tailx,cached_tailx+1),若指令失败则转步骤2.2,否则执行2.5;2.5使用CAS(&stat,无消息,使用中)操作修改cached_tailx指向的消息槽状态,尝试将cached_tailx指向的消息槽的状态设置成“使用中”状态,若操作失败转2.2,否则执行2.6,其中stat为cached_tailx指向的消息槽的状态变量;2.6将消息写入cached_tailx指向的消息槽;2.7释放消息槽将cached_tailx指向的消息槽的状态由“使用中”更新为“有消息”,释放本生产者私有的临时变量cached_tailx;第三步,消费者Cx采用以下方法访问消息槽数组,以获取消息3.1创建生产者Cx的私有变量cached_headx;3.2将当前的head值缓存到cached_headx中;3.3判断cached_headx是否与tail相等,若相等则执行用户自定义的“消息缓冲区空”处理流程,转步骤3.2,若不相等则转3.4;3.4执行CAS(&head,cached_headx,cached_headx+1)指令,若指令失败则转步骤3.2,否则执行3.5;3.5使用CAS(&statx,有消息,使用中)操作修改cached_headx指向的消息槽状态,尝试将cached_headx指向的消息槽的状态设置成“使用中”状态;若操作失败转3.2,否则执行3.6,其中statx为cached_headx指向的消息槽的状态变量;3.6读取cached_headx指向的消息槽中的消息;3.7释放消息槽将cached_headx指向的消息槽状态由“使用中”更新为“无消息”,释放本消费者私有的临时变量cached_headx。
2.如权利要求1所述的基于共享内存的消息传递方法,其特征在于所述消息槽数组从 0开始编号、容量为C,C的大小由生产者或消费者数量、生产者生产消息或消费者消费消息 的频度、系统对"消息槽满"事件的容忍程度、系统可用内存综合决定。
全文摘要
本发明公开了一种基于共享内存的消息传递方法,目的是实现多生产者多消费者情形下生产者和消费者之间高并行消息通信。技术方案是先在共享内存中创建并初始化消息槽数组,分配两个变量head和tail;生产者访问消息槽数组时采用比较交换原子指令CAS保证写写冲突不可能发生,消费者访问消息槽数组时采用比较交换原子指令CAS保证多次读取不可能发生。采用本发明可以实现任意场景下多个生产者和多个消费者并行进行消息通信,对于可以使用锁的场景而言,采用本发明的系统性能远优于使用锁的系统性能。
文档编号G06F9/38GK101763289SQ20091004440
公开日2010年6月30日 申请日期2009年9月25日 优先权日2009年9月25日
发明者任怡, 刘晓建, 吴庆波, 孔金珠, 廖湘科, 张卫华, 戴华东, 易晓东, 李姗姗, 董攀, 谭郁松, 邵立松, 颜跃进 申请人:中国人民解放军国防科学技术大学
网友询问留言 已有0条留言
  • 还没有人留言评论。精彩留言会获得点赞!
1