通信中间件实现的关键技术探讨

所属栏目:软件开发论文 发布日期:2010-09-15 09:23 热度:

  摘要:分布式计算系统的应用越来越广泛,它是由一组分布在网络中不同节点上的进程彼此协作来完成任务的。这些进程通过通信中间件来完成同步、互斥以及数据传送等操作,通信中间件是分布式系统实现的基础。本文就通信中间件的实现所用到的各种技术进行了比较详细的介绍,从而给读者一些有用的参考和启发。
  关键字:守护进程;Posix线程;互斥锁;读写锁
  
  1、守护进程
  1.1什么是守护进程
  守护进程(Daemon)是运行在后台的特殊进程,它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。守护进程是一种很有用的进程。[1]UNIX中的大多数服务器就是用守护进程实现的。比如,超级服务器inetd,Web服务器httpd等。同时,守护进程完成许多系统任务。比如,作业规划进程crond,打印进程lpd等。守护进程的编程本身并不复杂,复杂的是各种版本的UNIX的实现机制不尽相同,造成不同UNIX环境下守护进程的编程规则并不一致。
  1.2守护进程的特性
  守护进程最重要的特性是后台运行。在这一点上DOS下的常驻内存程序TSR与之相似。其次,守护进程必须与其运行前的环境隔离开来。这些环境包括未关闭的文件描述符,控制终端,会话和进程组,工作目录以及文件创建掩模等。这些环境通常是守护进程从执行它的父进程中继承下来的。最后,守护进程的启动方式有其特殊之处。它可以在Linux系统启动时从启动脚本/etc/rcd中启动,也可以由作业规划进程crond启动,还可以由用户终端(通常是shell)执行。
  2、POSIX线程
  2.1POSIX线程的创建
  1线程与进程
  相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。在串行程序基础上引入线程是为了提高程序的并发度,从而提高程序运行效率和响应时间。线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源的管理和保护;而进程正相反。同时,线程适合于在SMP机器上运行,而进程则可以跨机器迁移。[2]
  2创建线程
  POSIX通过pthread_create()函数创建线程,pthread_create()创建的线程并不具备与主线程(即调用pthread_create()的线程)同样的执行序列,而是使其运行start_routine()函数。thread返回创建的线程ID,而attr是创建线程时设置的线程属性。pthread_create()的返回值表示线程创建是否成功。尽管arg是void*类型的变量,但它同样可以作为任意类型的参数传给start_routine()函数;同时,start_routine()可以返回一个void*类型的返回值,而这个返回值也可以是其他类型,并由pthread_join()获取。[3]
  2.2线程的取消
  1线程取消的定义
  一般情况下,线程在其主体函数退出的时候会自动终止,但同时也可以因为接收到另一个线程发来的终止(取消)请求而被强制取消。线程的取消类似于线程的终止,但还是有概念上的不同,虽然取消的目的和结果是终止,但是取消往往是被动的。
  2取消点
  根据POSIX标准,pthread_join()、pthread_testcancel()、pthread_cond_wait()、pthread_cond_timedwait()、sem_wait()、sigwait()等函数以及read()、write()等会引起执行阻塞的系统调用都是Cancelation-point,而其他pthread函数都不会引起Cancelation动作。但是pthread_cancel的手册页声称,由于LinuxThread库与C库结合得不好,因而目前C库函数都不是Cancelation-point;但CANCEL信号会使线程从阻塞的系统调用中退出,并置EINTR错误码,因此可以在需要作为Cancelation-point的系统调用前后调用pthread_testcancel(),从而达到POSIX标准所要求的目标,即如下例子代码段所示[4]
  pthread_testcancel();
  retcode=read(fd,buffer,length);
  pthread_testcancel();
  3程序设计方面的考虑
  如果线程处于无限循环中,且循环体内没有执行至取消点的必然路径,则线程无法由外部其他线程的取消请求而终止。因此在这样的循环体的必经路径上应该加入pthread_testcancel()调用。
  2.3线程的中止
  一般来说,Posix的线程终止有两种情况:正常终止和非正常终止。线程主动调用pthread_exit()或者从线程函数中return都将使线程正常退出,这是可预见的退出方式;非正常终止是线程在其他线程的干预下,或者由于自身运行出错(比如访问非法地址)而退出,这种退出方式是不可预见的。
  3、互斥锁
  尽管在Posix线程中同样可以使用IPC的信号量机制来实现互斥锁mutex功能,但显然semphore的功能过于强大了,在Posix线程中定义了另外一套专门用于线程同步的mutex函数。
  1.创建和销毁
  有两种方法创建互斥锁,静态方式和动态方式。而销毁一个互斥锁即意味着释放它所占用的资源,且要求此锁当前处于开放状态。由于在Linux中,互斥锁并不占用任何资源,因此LinuxThreads中的pthread_mutex_destroy()除了检查锁状态以外没有其他动作。
  2.互斥锁的属性
  互斥锁的属性在创建锁的时候指定,在LinuxThreads实现中仅有一个锁类型属性,不同的锁类型在试图对一个已经被锁定的互斥锁加锁时表现不同。当前有四个值可供选择:
  PTHREAD_MUTEX_TIMED_NP,这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。
  PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。
  PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样就保证当不允许多次加锁时不会出现最简单情况下的死锁。
  PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。
  3.互斥锁的操作
  互斥锁操作主要包括加锁pthread_mutex_lock()、解锁pthread_mutex_unlock()和测试加锁pthread_mutex_trylock()三个,不论哪种类型的锁,都不可能被两个不同的线程同时得到,而必须等待解锁。对于普通锁和适应锁类型,解锁者可以是同进程内任何线程;而检错锁则必须由加锁者解锁才有效,否则返回EPERM;对于嵌套锁,文档和实现要求必须由加锁者解锁,但实验结果表明并没有这种限制,这个不同目前还没有得到解释。
  4、条件变量
  条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待“条件变量的条件成立”而挂起;另一个线程使“条件成立”(给出条件成立信号)。为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起。
  1.创建和注销
  条件变有静态动态两种创建方式,静态方式使PTHREAD_COND_INITIALIZER常量初始化;动态方式则调用pthread_cond_init()函数。注销一个条件变量需要调用pthread_cond_destroy(),只有在没有线程在该条件变量上等待的时候才能注销这个条件变量,否则将返回EBUSY。因为Linux实现的条件变量没有分配什么资源,所以注销动作只包括检查是否有等待线程。
  2.等待和激发
  等待条件有两种方式:无时间设定等待pthread_cond_wait()和计时等待pthread_cond_timedwait(),其中计时等待方式如果在给定时刻前条件没有满足,则返回ETIMEOUT,结束等待。无论哪种等待方式,都必须和一个互斥锁配合,以防止多个线程同时请求pthread_cond_wait()的竞争条件(RaceCondition)。
  激发条件有两种形式:pthread_cond_signal()激活一个等待该条件的线程,存在多个等待线程时按入队顺序激活其中一个;而pthread_cond_broadcast()则激活所有等待线程。
  5、读写锁
  通过读写锁,可以对受保护的共享资源进行并发读取和独占写入。读写锁是可以在读取或写入模式下锁定的单一实体。要修改资源,线程必须首先获取互斥写锁。必须释放所有读锁之后,才允许使用互斥写锁。
  5.1读写锁的属性
  初始化读写锁属性
  pthread_rwlockattr_init使用实现中定义的所有属性的缺省值来初始化读写锁属性对象attr。读写锁属性对象初始化一个或多个读写锁之后,影响该对象的任何函数(包括销毁)不会影响先前已初始化的读写锁。
  销毁读写锁属性
  pthread_rwlockattr_destroy可用来销毁读写锁属性对象。
  实现可以导致pthread_rwlockattr_destroy()将attr所引用的对象设置为无效值。
  设置读写锁属性
  pthread_rwlockattr_setpshared可用来设置由进程共享的读写锁属性。
  5.2读写锁的使用
  1.初始化读写锁
  pthread_rwlock_init可以通过attr所引用的属性初始化rwlock所引用的读写锁。初始化读写锁之后,该锁可以使用任意次数,而无需重新初始化。成功初始化之后,读写锁的状态会变为已初始化和未锁定。
  2.获取读锁
  pthread_rwlock_rdlock可用来向rwlock所引用的读写锁应用读锁。如果写入器未持有读锁,并且没有任何写入器基于该锁阻塞,则调用线程会获取读锁。如果写入器未持有读锁,但有多个写入器正在等待该锁时,调用线程是否能获取该锁是不确定的。如果某个写入器持有读锁,则调用线程无法获取该锁。如果调用线程未获取读锁,则它将阻塞。
  3.获取写锁
  pthread_rwlock_wrlock可用来向rwlock所引用的读写锁应用写锁。如果没有其他读取器线程或写入器线程持有读写锁rwlock,则调用线程将获取写锁。否则,调用线程将阻塞。如果在进行调用时,调用线程持有读写锁(读锁或写锁),则结果是不确定的。为避免写入器资源匮乏,允许在多个实现中使写入器的优先级高于读取器。如果针对未初始化的读写锁调用pthread_rwlock_wrlock(),则结果是不确定的。
  4.释放读写锁
  pthread_rwlock_unlock可用来释放在rwlock引用的读写锁对象中持有的锁。如果通过调用pthread_rwlock_unlock()来释放读写锁对象中的读锁,并且其他读锁当前由该锁对象持有,则该对象会保持读取锁定状态。如果多个线程基于rwlock中的读锁和写锁阻塞,则无法确定读取器和写入器谁先获得该锁。如果针对未初始化的读写锁调用pthread_rwlock_unlock(),则结果是不确定的。
  5.销毁读写锁
  pthread_rwlock_destroy可用来销毁rwlock引用的读写锁对象并释放该锁使用的任何资源。再次调用pthread_rwlock_init()重新初始化该锁之前,使用该锁所产生的影响是不确定的。实现可能会导致pthread_rwlock_destroy()将rwlock所引用的对象设置为无效值。
  6、结束语
  由于文章篇幅有限,只能从大体上对通信中间件实现的关键技术进行简单的介绍,具体的一些细节可能不够深入,在这只能给读者作为参考,请读者见谅。
  参考文献:
  [1]李勇.《进程间通信的分布式实现》.吉林大学,2004.
  [2]JohnShapleyGray著,张宁等译.《UNIX进程间通信(第二版)》.电子工业出版社,2001.
  [3](英)GeorgeCoulouris,JeanDollimore,TimKindberg著,金蓓弘等译.《分布式系统概念与设计》第三版.机械工业出版社,中信出版社,2004.
  [4]DouglasE.Comer,DavidL.Stevens著.赵刚,林瑶,蒋慧译.《用TCP/IP进行网际互联第三卷:客户-服务器编程与应用(Linux/POSIX套接字版)》.电子工业出版社,2001.

  搜论文知识网致力于为需要刊登论文的人士提供相关服务,提供迅速快捷的论文发表、写作指导等服务。具体发表流程为:客户咨询→确定合作,客户支付定金→文章发送并发表→客户接收录用通知,支付余款→杂志出版并寄送客户→客户确认收到。鸣网系学术网站,对所投稿件无稿酬支付,谢绝非学术类稿件的投递!
  

文章标题:通信中间件实现的关键技术探讨

转载请注明来自:http://www.sofabiao.com/fblw/dianxin/ruanjiankaifa/4161.html

相关问题解答

SCI服务

搜论文知识网的海量职称论文范文仅供广大读者免费阅读使用! 冀ICP备15021333号-3