分类: C/C++
2009-08-22 13:12:36
三.多进程编程技巧
1.主要程序结构
(1)事件主控方式
若是应用程序属于事务处理方式,则在主函数中设计为监控事件发生,
当事件发生时,可以生成一个新的进程来处理该事务,事务处理完成后就
可以让子进程退出系统.这种处理方式一般不要消息传递.
(2)信息协调方式
若是应用程序需要由多个进程协调处理完成,则可以生成这些进程,
通过消息在进程间的传递,使各个进程能相互协调,共同完成事务.这种处
理方式一般是用fork()生成几个进程后,用exec()调用其它程序文件,使
得不同的程序同时在系统内运行.然后通过IPC机制传送消息,使各个程序
能协调运行.
2.选择主体分叉点
(1)事件初始产生
对应于事件主控方式的程序结构.关键点在于以何种方式选择事件的
初始发生点,如网络程序给出的建链信息.主控程序在收到该消息后就认
为是一个事件开始,则可以产生一个子进程处理后面的事务:接收交易信
息,事务处理,发送返回交易信息,关闭链接等,完成后将子进程退出系统.
(2)主程序自主产生
对应于信息协调方式的程序结构.主控程序只负责生成几个子进程,各
个子进程分别调用exec()将不同的执行文件调入内存运行,主控程序在生
成所有的子进程后即可退出系统,将子进程留在内存中运行.
3.进程间关系处理
(1)父子进程关系
. 进程组处理
进程组的概念是这样的,当系统启动时,第一个进程是init,其进程
组号等于进程号,由它产生的所有子进程的进程组号也相同,子进程
的子进程也继承该进程组号,这样,由init所生成的所有子进程都属
于同一个进程组.但是,同一个进程组的父子进程可能在信号上有相
互通讯,若父进程先于子进程退出系统,则子进程会成为一个孤儿进
程,可能变成僵死进程.从而使该子进程在其不"愿意"的情况下退出
运行.为解决这个问题,子进程可以自己组成一个新的进程组,即调
用setpgrp()与原进程组脱离关系,产生一个新的进程组,进程组号
与它的进程号相同.这样,父进程退出运行后就不会影响子进程的当
前运行.
. 子进程信号处理
但是,单做上述处理还不能解决另一个困难,即子进程在退出运行
时,找不到其父进程(父进程已退出,子进程的父进程号改为1).发送
子进程退出信号后没有父进程做出响应处理,该子进程就不可能完
全退出运行,可能进入僵死状态.所以父进程在产生子进程前最好屏
蔽子进程返回信号的处理,生成子进程,在父进程退出运行后,子进
程返回则其进程返回信号的处理会由系统给出缺省处理,子进程就
可以正常退出.
(2)兄弟进程关系
. 交换进程号
对于信息协调方式的程序来说,各兄弟进程间十分需要相互了解进
程号,以便于信号处理机制.比较合理的方法是父进程生成一个共享
内存的空间,每个子进程都在启动时在共享内存中设置自己的进程
号.这样,当一个子进程要向另一个子进程发送信号或是因为其他原
因需要知道另一个子进程号时,就可以在共享内存中访问得到所需
要的进程号.
4.进程间通讯处理
(1)共享内存需要锁机制
由于共享内存在设计时没有处理锁机制,故当有多个进程在访问共享
内存时就会产生问题.如:一个进程修改一个共享内存单元,另一个进程在
读该共享内存单元时可能有第三个进程立即修改该单元,从而会影响程序
的正确性.同时还有分时系统对各进程是分时间片处理的,可能会引起不
同的正确性问题.按操作系统的运作方式,则有读锁和写锁来保证数据的
一致性.所以没有锁机制的共享内存,必须和信号量一起使用,才能保证共
享内存的正确操作.
(2)消息队列需要关键值
消息队列的操作在进程取得消息队列的访问权限后就必须通过关键
值来读消息队列中的相同关键值的消息,写消息时带入消息关键值.这样
可以通过不同的关键值区分不同的交易,使得在同一个消息队列可以供多
种消息同时使用而不冲突.若读消息队列使用关键值0则读取消息队列中
第一个消息,不论其关键值如何.
(3)信号需要信号处理函数设置和再设置
在用户进程需要对某个中断做自己定义的处理时,可以自己定义中断
处理函数,并设置中断处理函数与该中断相关联.这样,用户进程在收到该
中断后,即调用用户定义的函数,处理完成后用户进程从被中断处继续运
行(若用户定义的中断函数没有长跳函数或退出运行等会改变运行指令地
址的系统调用).在中断信号被处理后,该中断的处理函数会恢复成上次缺
省处理函数而不是保持用户定义函数,故在用户定义的中断处理函数中一
般都再定义该中断和函数自己的关联.
(4)IPC的权限设置
在消息队列,共享内存和信号量的访问时有用户访问权限设置,类同
于文件的访问权限的设置如(777表示rwxrwxrwx),用命令ipcs即可看到在
系统中生成的消息队列,共享内存和信号量的访问权限.其意义也类似于
文件访问权限.只是执行位无效.
在有名管道和文件方式共享内存中以系统文件的方式定义了用户的
访问权限.用命令ls -l可以看到它们以系统文件方式存在并具有访问权
限值,并可以看到有名管道的文件类型为p,文件方式共享内存的文件类型
为s.
(5)信号中断对系统调用一级有效
系统在设计系统调用时就考虑了中断处理问题.当进程运行到一个系
统调用时发生了中断,则进程进入该中断处理,处理完成后,进程会跳过该
系统调用而进入下一条程序指令.
应该注意的是中断发生在系统调用一级而不是子程序或函数一级.比
如一个程序在一个子程序被调用前设置了超时中断,并在子程序中收到超
时中断,系统在处理完超时中断后接着处理该子程序被中断的系统调用之
后的指令,而不是从调用该子程序名指令的后一条指令继续处理.
(6)各种IPC方式的特点
. 消息队列:
通过消息队列key值定义和生成消息队列.
任何进程只要有访问权限并知道key即可访问消息队列.
消息队列为内存块方式数据段.
消息队列中的消息元素长度可为系统参数限制内的任何长度.
消息元素由消息类型分类,其访问方式为按类型访问.
在一次读写操作前都必须取得消息队列标识符,即访问权.访问后即
脱离访问关系.
消息队列中的某条消息被读后即从队列中删除.
消息队列的访问具备锁机制处理,即一个进程在访问时另一个进程
不能访问.
操作时要注意系统资源和效率.
在权限允许时,消息队列的信息传递是双向的.
. 共享内存
通过共享内存key值定义和生成共享内存.
任何进程只要有访问权限并知道key即可访问共享内存.
共享内存为内存块方式的数据段.
共享内存中的数据长度可为系统参数限制内的任何长度.
共享内存的访问同数组的访问方式相同.
在取得共享内存标识符将共享内存与进程数据段联接后即可开始对
之进行读写操作,在所有操作完成之后再做共享内存和进程数据
段脱离操作,才完成全部共享内存访问过程.
共享内存中的数据不会因数据被进程读取后消失.
共享内存的访问不具备锁机制处理,即多个进程可能同时访问同一
个共享内存的同一个数据单元.
共享内存的使用最好和信号量一起操作,以具备锁机制,保证数据的
一致.
在权限允许时,共享内存的信息传递是双向的.
. 信号量
用于生成锁机制,避免发生数据不一致.
没有其他的数据信息.
不需要有父子关系或兄弟关系.
. 信号
信号由系统进行定义.
信号的发送只要有权限即可进行.
信号是一个事件发生的信息标志,不带有其它信息.
信号不具备数据块.
信号的处理可由用户自己定义.
信号可能由用户进程,操作系统(软件或硬件原因)等发出.
有一些信号是不可被屏蔽的.
信号中断的是系统调用级的函数.
信号的信息传递是单向的.
. 管道
做为系统的特殊设备文件,可以是内存方式的,也可以是外存方式的.
管道的传输一般是单向的,即一个管道一向,若两个进程要做双向传
输则需要2个管道.管道生成时即有两端,一端为读,一端为写,两个
进程要协调好,一个进程从读方读,另一个进程向写方写.
管道的读写使用流设备的读写函数,即:read(),write.
管道的传输方式为FIFO,流方式的.不象消息队列可以按类型读取.
* 有名管道
一般为系统特殊文件方式,使用的进程之间不一定要有父子关系
或兄弟关系.
* 无名管道
一般为内存方式,使用的进程之间一定要有父子关系或兄弟关系.
. 文件
文件是最简单的进程间通讯方式,使用外部存贮器为中介.
操作麻烦,定位困难.
保密程度低.
容易出现数据不一致问题.
占用硬盘空间.
只要有权限并知道文件名,任何进程都可对之操作.
* 特殊处理
为避免出现保密问题,在打开文件,取得文件描述符后,调用
unlink()将硬盘上的文件路径名删除,则硬盘上就没有文件拷贝
了.但在进程中该文件描述符是打开的,由该进程生成的子进程中
该文件描述符也是打开的,就可以利用系统提供的文件缓冲区做
进程间通讯,代价是进程间必须有父子关系或兄弟关系.
. 环境变量
信息的传送一般是单向的,即由父进程向子进程传送.
保密性较好.
双方必须约定环境变量名.
只占用本进程和子进程的环境变量区.
. 共享数据段
操作比较复杂.
占用硬盘空间,生成系统特殊文件.
其他性质与共享内存相类似.
. 流
文件描述符的操作方式.
进程间不一定要有父子关系或兄弟关系.
双向传送信息.
进程各自生成socket,用bind()联接.
其他性质与管道相类似.
流编程为TCP/IP网络编程范围,在本文中暂不阐述.
. 传递参数
信息的传送一般是单向的, 即由父进程向子进程传送.
保密性较差,用进程列表即可显示出来.
双方必须约定参数位置.
只占用子进程的参数区.