什么是多进程程序和多线程程序
谈这个问题首先需要知道什么是进程,什么是线程。这个问题在笔者刚接触操作系统时也是一头雾水。这里先贴出两者的概念,然后我们再细细道来。
进程(Process):计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位。
线程(thread):操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
首先来谈一谈进程。进程一般被定义为正在运行的程序的实例,包括运行的代码和运行代码所需要的资源。通俗的说就是,我们在写一个C语言程序(如:Hello World)后,编译后生成的可执行文件,这个文件就是一个程序,执行这个程序之后,操作系统就会执行文件中的代码,运行的这组代码和所需要的资源就称为进程。进程是动态的,且进程的地址空间都是独立的,互不影响。一个进程只能对应一个程序,但是一个程序可以对应多个进程,这就是后面要说的多进程程序。
再来谈一谈线程,在早期的操作系统是没有线程这个概念的,后来随着计算机的发展,越来越复杂的程序出现了,由于进程之间的切换开销比较大,因此线程被发明了。线程是程序执行的最小单位,一个进程可以包含多个线程,所有线程之间除了一些运行必要的资源(如栈),其他资源均共享。线程必须依赖于进程,线程无法独立运行。
如果你还不能理解进程和线程的话,通过下面这个故事或许就能明白了。
在一个遥远的地方,有家工厂,这家工厂中有很多工人在打工,那么这家工厂对应的就是一个进程,它包含有工厂运作的资源和流程(代码)。而这些工人对应的就是许多的线程,他们共享这个工厂的资源,但是每个人都有自己负责的一部分工作内容。但每个人都无法单干,必须依赖于工厂。(未完待续....)
如果你还不能理解进程和线程的话,没关系,继续看下去吧,也许看完就能理解了。
说完了进程和线程,现在来说一下什么是多进程程序。正常情况下,我们所编写的程序一般都是对应一个进程。如果需要对应多个进程就必须在代码中创建进程,创建进程的进程称为父进程,而被创建的进程称为子进程。在类UNIX系统中使用fork函数创建进程。fork函数没有参数,返回值为进程的id(pid),神奇的是fork函数会返回2次,在父进程中返回子进程的id(pid),而在被创建的子进程中返回0(创建失败则返回-1)。因此在多进程程序中需要判断fork函数的返回值,然后执行父进程和子进程的代码。为什么需要判断fork的返回值呢,因为fork创建子进程时,会将父进程的资源和代码cpoy一份,即副本,因此子进程的资源和代码都与父进程当前的状态相同(如父进程已打开的文件,子进程也是已打开),则唯一可以判断是哪个进程的方法就是fork函数的返回值了。根据这个返回值可以让两个资源代码都相同的进程分别执行不同的代码。下面这个例子就是使用fork创建子进程后,令父进程每隔1S输出一个字符串,子进程每隔2S输出一个字符串,最后父进程等待子进程的返回后才返回(否则就要被当成“孤儿进程”让init进程抱走了)。

这个程序的运行结果是这样的:

接下来我们继续讲那个故事。但这次我们讲的是前传,时间线在更早的时候。
在那个遥远的地方,有一位商人在那里盖了几间一样的工厂来制作某种产品,但因为当时技术比较落后,一间工厂的供电只够一台设备运行,而一个完整的产品的制作需要三个材料,这三个材料分别来自于三台不同的设备。而且工厂刚起步,只能靠商人自己。因此为了效率,他不得不在工厂之间来回奔波,这个工厂制作第一个材料,而另一个工厂制作第二个材料....尽管如此麻烦,但是如果有一家工厂因故断电了,其他工厂还能继续运作。
故事的这个部分的意思是,在线程还没被发明时(技术落后),为了高效运行一个完整的程序(制作一个完整的产品),需要采用多进程程序(多家工厂),这些进程的代码资源都是一样的且互相独立(工厂之间相同但互相独立运作),一个进程崩了(一家工厂没电了),其他进程不会受到影响(其他工厂不受影响)。由此可见,多进程程序比较健壮,一个进程的崩溃不容易导致其他进程的崩溃,但进程切换的开销比较大(工厂之间来回奔波)。
说完了多进程程序,那么接下来就是多线程程序。同多进程一样,一般情况下,程序都是单线程,即主线程。如果要使用多线程,就需要进行创建。在类UNIX系统中使用pthread_create函数来创建线程。它的函数原型如下:

此函数会创建一个线程,第一个参数tidp是指向保存线程ID的地址;第二个参数attr是设置线程的属性,无特殊要求时设为NULL即可;第三个参数start_rtn是指向线程的入口地址,也就是一个函数指针,线程函数的原型必须与这个函数指针相同;而第四个参数arg就是要传入线程的参数,对应线程函数参数的void*。线程之间共享进程的资源,每个线程本身仅持有一些栈等必要资源,因此线程之间并发执行效率较高。下面这个例子就是使用pthread_creat创建了一个线程,此线程和主线程并发执行打印字符串,最后主线程等待这个线程的返回后才返回。

这个程序的运行结果是这样的:

最后让我们把那个故事叙述完整。这次的时间线接在上一次讲故事之后。
后来,技术发展起来了,于是商人为了再次提高效率,就把其他工厂都拆了,用来改造剩下的最后一间工厂,改造完后,这间工厂的电力终于可以支撑3台设备同时运行了。于是商人就可以在一间工厂里,(并发)操作3台机器来制作产品了,尽管他不能同时操作3台机器,但是在切换使用机器时不需要像以前那样辛苦了(工厂之间来回跑)。但是,存在一个安全隐患,如果一台机器因为故障导致短路,那么整个工厂都会停电(咱们假设它只有总电源短路保护)。
故事的这个部分的意思是,线程被发明之后(技术发展起来),一个程序可以作为一个进程被分为多个线程并发执行(只在一家工厂分时操作3台机器),相比于分成多个进程并发执行效率会更高(3家工厂来回跑)。但是,一但有一个线程崩溃了,那么这个进程也崩了(一台机器故障,整个工厂停电)。
不过看到这你可能还有一个疑问,那开头说的工人呢,他们哪去了?这就涉及到SMP了,所谓SMP就是指多核处理器。这里说的商人一个人单干指的就是单核处理器,而多核处理器就相当于多请了几个工人,前面说工人就相当于线程,但并不是绝对,只是这样说便于理解罢了,其实也可能相当于进程。不过,这是后话了,这里就不再多提。
最后总结一下多进程程序和多线程程序的区别:
1.多进程程序因为各进程地址空间相互独立,因此资源容易管理和保护,但切换开销较大。而多线程程序因为共享进程资源,容易发生资源的争抢,不利于资源的管理和保护,但切换开销较小。
2.多进程程序中一个进程的崩溃一般不会导致其他进程崩溃。而多线程程序的一个线程崩溃必然导致整个程序对应的进程的崩溃。
对于这一点,我们可以打开一个浏览器,然后再打开任务管理器,点开浏览器应用,会显示浏览器应用下的所有进程,然后我们结束一个进程后,浏览器并不会崩溃,因为它是多进程程序,但是一定会有某个插件提示崩溃了,因为我们将它所对应的进程kill了。

3.当需要并发执行且需要共享一些数据时,只能使用多线程,而不能使用多进程。比如允许多个线程往一个文件写入数据,但不允许多个进程往一个文件写入数据。
以上是笔者个人拙见,而且笔者文笔不是很好,有些比喻可能不太恰的,如果您觉得有不当之处还请批评指正。