为什么要通信
通信是人的基本需求。而进程作为人的发明,自然脱离不了人的习性,也有通信需求。如果进程之间不进行任何通信,那么进程所能完成的任务就要大打折扣。 例如,父进程在创建子进程后,通常须要监督子进程的状态,以便在子进程没有完成给定的任务时,可以再创建一个子进程来继续。这就需要父子进程间通信。
而线程间的通信则需要更多。由于一个进程通常包括多个线程,这多个线程之间因资源共享自然地就存在一种合作关系。这种合作关系虽然可以表现为相互独立,但更多地时候是互相交互。这就是通信。就像舞台上的多个演员,他们之间是一种合作关系,共同将戏演好。虽然这些演员在舞台上的时候可以各自演各自的,不说话,也没有肢体接触,即没有交互,但他们更多的时候会进行对白和拥抱等交互操作。
线程之间的交互我们就称之为线程通信。线程通信是从进程通信演变而来的,进程通信有个专有缩写,叫IPC( Inter-Process Communication)。由于每个进程至少有一个线程,进程的通信就是进程里面的线程通信。在随后的讨论中,我们将统一使用线程通信来进行讲解。
那么线程之间的通信是如何进行的呢?
舞台上的演员可以通过对白,手势和拥抱等方法来交互通信。类似地,线程也可以同样的方式来进行通信。下面我们就来看一下线程的这些交互方式。
管道、记名管道、套接字
演员最常使用的交互手段就是对白。对白就是一方发出声音,另一方接受声音。声音的传递则通过空气(当面或无线交谈)、线缆(有线电话)进行传递。类似地,线程对白就是一个线程发出某种数据信息,另外一方接受数据信息,这些数据信息通过一片共享的存储空间进行传递。
在这种方式下,一个线程向这片存储空间的一端写入信息,另一个线程从存储空间的另外一端读取信息。这看上去像什么?管道。管道所占的空间既可以是内存,也可以是磁盘。就像两人对白的媒介可以是空气,也可以是线缆一样。要创建一个管道,一个线程只需调用管道创建的系统调用即可。
管道(无名管道)
从根本上说,管道是一个线性字节数组,类似文件。使用文件读写的方式进行访问,但却不是文件。因为通过文件系统看不到管道的存在。另外,我们前面说了,管道可以设在内存里,而文件很少设在内存里。创建管道在壳命令行下和在程序里是不同的。壳命令行下,只需要使用符号“|”即可。
在程序里面,创建管道需要使用系统调用popen()或者pipe()。popen需要提供一个目标进程作为参数,然后在调用该函数的的进程和给出的目标进程之间创建一个管道。这很像人们打电话时必须提供对方的号码,才能创建连接一样。
创建时还需要提供一个参数表明管道类型:读管道或者是写管道。而 pipe 调用将返回两个文件描述符(文件描述符是用来识别一个文件流的一个整数,与句柄不同),其中一个用于从管道进行读操作,一个用于写入管道。也就是说, pipe将两个文件描述符连接起来,使得一端可以读,另一端可以写。通常情况下,在使用pipe调用创建管道后,再使用fork产生两个进程,这两个进程使用pipe返回的两个文件描述符进行通信。