进程间和线程间通信(原文章出自http://blog.sina.com.cn/s/blog_4a93ccea0102ea1w.html)
1.许多程序和应用一起工作达到某个共同目的的任务集。每个任务在开始执行前等待前一个任务完成。为了完成共同目标,相关线程或进程必须相互合作与通信。
2.依赖关系:对于任意两个线程或进程,存在4种依赖关系(如图)
3.进程间和线程间通信:
4.进程间通信:数据对于其他进程来说是受保护的,为了让一个进程访问另一进程的数据,必须最终使用操作系统调用。当进程将数据发送到另一个进程时,必须在进程间建立一种通信方式IPC(进程间通信)。
关联(派生)进程和非关联(非父子关系)进程实现通信的技术
(1)关联进程实现进程间通信技术:继承资源和命令行参数。
当创建一个子进程,它接收了父进程许多资源(如文本、堆栈以及数据片段)的拷贝。父进程可在数据片段或环境中设置变量,然后执行fork(),子进程接收这些值,同样父进程定位一个文件期望的位置,然后执行fork(),子进程就可以在父进程离开读/写指针的准确位置访问该文件。缺点:单向、一次性通信。通信像指挥棒传递。一旦父进程传递了某些资源的拷贝,子进程对他的使用就是独立的,必须使用原始传递资源。另一种单向、一次性通信是命令行参数,它是最简单形式的进程间通信。只限制于关联进程。
(2)非关联进程实现进程间通信技术:管道(pipe).
两种管道类型:a.匿名管道(关联进程)——借助于文件描述符实现(子进程从父进程继承了文件描述符);
b.命名管道(非关联进程)——具有某名字的特殊类型文件,可以建立一个IPC客户/服务器模型,可以跨网络访问命令管道。
命令管道相对于匿名管道的优点:命名管道可以被无关联进程使用、命名管道可以持久、命名管道可以再网络或分布环境中使用、命名管道容易用于多对一关系中。
两者都是通过read()或write()函数来访问。
WIN32环境命名管道工作所需的API调用:
CallNamePipe():连接到一个管道实例,然后写入一条消息、读取消息,并关闭管道句柄。只能被客户进程和消息管道使用
PeekNamedPipe():读取通过管道传输的数据,而不会从字节管道或消息管道取出数据。返回管道实例的信息。
DisconnectNamedPipe():客户和进程使用完管道实例后,服务器进程调用此函数关闭客户进程的连接,客户句柄变成无效,并抛弃管道中的所有未读数据。
TransactNamedPipe():写请求消息并读答复消息。如调用进程的管道句柄设置成消息读取模式,就可以与消息管道一起使用。
FlushFileBuffers():服务器进程调用该函数确保客户进程读取写入管道的所有字节和消息。如果函数没有返回到客户进程,它就从管道读取了所有的数据。
GetNamedPipeInfo():获取命名管道的信息。它返回管道的类型、输入和输出缓冲器的大小以及创建管道实例的最大数。
GetNamedPipeHandleState():提供句柄,包括管道的读和等待模式、管道实例的当前数以及通过网络进行通信的管道的其他所有重要信息。
SetNamedPipeHandleState():设置管道句柄的读和等待模式。它还控制搜集字节的最大数,或者客户进程与远程服务器通信时传输消息前等待的最长时间。
ReadFile()、WriteFile():用于字节和消息管道。使用一个事件对象来通知其完成。
【注意】服务器程序负责建立客户与服务器进程间管道通信的协议。服务器程序必须指定访问模式、阻塞模式、管道类型、读取模式、缓冲器大小及管道使用的即时计数。
命名管道常常用于多线程服务器。
通常使用共享内存比使用管道或队列更简单更有效。共享内存块可用于保存大数据结构。可用于映射文件到内存,使得应用程序减轻了常规文件访问的I/O操作代价。
WIN32 环境创建和使用共享内存所需API:
CreateFileMapping() 创建一个文件映射对象,对文件无限制。
OpenFileMapping() 获取映射对象的句柄
MapViewOfFile() 获取共享内存的起始地址
使用消息传递、共享内存、事务协议、客户/服务器范例、同步规则以及会话协议来让数据和控制信息在进程间流动。
5.线程间通信
[注意]如果多个线程用于写入全局数据结构,必须使用合作技术。
[程序]创建2个线程threadA、threadB,其中4个全局变量(1个有理对象M和3个集迭代器A\B\C)和3个全局数据结构(SetA,SetB,SetC)。
【注意】在多线程环境中,同时访问全局变量和数据结构必须同步化(synchronized)。
6.线程间通信的参数传递:
创建线程:pthread_create(ThreadId,NULL,threadFunction,(void *)X);
当使用线程创建API给一个线程传递指针时,该指针就像exec函数家族的参数一样使用。exec函数家族用于创建子进程,并包含作为形参传递给子进程的参数。
使用exec函数家族与使用线程创建API传递参数的区别:
exec函数中的参数表示一种单向通信,子进程只复制在exec调用中传递的参数值。当线程接收到参数时,它并不是一个拷贝,而是某些数据位置的地址。对线程中数据的任何修改都将反应在创建该数据的进程中,这与在exec调用中子进程所接收的参数相反,子进程对参数所做的任何修改不会反映在父进程中。
[程序]演示线程参数的使用:
注意:由threadA()和threadB()对N指针对象的任何修改都将反映到threadA()、threadB()、main()。因为所有的3个线程可能并发执行,所以不要修改保存在N中的对象,除非修改是同步的。但在此程序中,保存在 N 中的对象只用在读或复制的场合,不必担心数据竞争的发生。
【重点】将参数用作线程间通信的一种形式可能较之将全局变量或全局数据结构用作线程间通信机制更理想。通过监视那些作为参数接收数据的线程可以控制同步。
7.文件句柄:
在多线程间的共享文件作为一种线程的通信形式时,要小心全局变量。在多线程环境中,序列化或同步化文件访问时也要小心,必须使用同步和合作技术。