原文地址:http://embed.21ic.com/softwar...当一个程序开始执行时,从头到尾退出执行时,它在内存中的部分称为进程。Linux是一个多任务操作系统,也就是说,在同一时刻,可以有多个进程同时执行。我们大家常用的单CPU计算机实际上在一个时间片内只能执行一条指令。那么Linux是如何实现多个进程同时执行的呢?原来Linux使用了一种叫做“进程调度”的方法。首先,为每个进程分配一定的运行时间。这个时间通常很短,短至毫秒。选择其中一个运行,其他进程暂时等待。当运行的进程超时,或者执行完退出,或者因为某种原因暂停时,Linux会重新调度并选择一个进程运行,因为每个进程占用的时间片都很短,从用户的角度来看,它似乎有多个进程同时运行。在Linux中,每个进程在创建时都被分配了一个数据结构,称为进程控制块(PCB)。PCB包含了很多系统调度和进程执行的重要信息。最重要的是进程ID。进程ID也叫进程标识符,是一个非负整数。系统中进程的唯一标识符。在最常用的I386架构上,一个非负整数的值是0~32767,这也是我们可能得到的进程ID,也就是进程的ID号。僵尸进程的产生僵尸进程是已经结束,但还没有从进程表中删除的进程。僵尸进程过多会导致进程表中的表项已满,从而导致系统崩溃,但并不占用系统资源。在进程的状态中,僵尸进程是一种非常特殊的种类。它已经放弃了几乎所有的内存空间,没有任何可执行代码,也无法被调度。它只在进程列表中保留一个位置来记录进程的状态。退出状态等信息由其他进程收集。此外,僵尸进程不再占用任何内存空间。它需要它的父进程来收集它的尸体。如果父进程没有安装SIGCHLD信号处理函数,调用wait或者waitpid()等待子进程结束,没有明确忽略该信号,就会一直保持僵尸状态。如果父进程结束,init进程会自动接管子进程并为其收集body,仍然可以清除。但是如果父进程是一个循环,不会结束,那么子进程就会一直处于僵尸状态。僵尸进程产生的原因:每个Linux进程在进程表中都有一个入口点(Entry),核心程序在执行进程时使用的所有信息都存储在入口点中。在使用ps命令查看系统中的进程信息时,看到的是进程表中的相关数据。fork系统调用创建新进程时,核心进程会在进程表中为新进程分配一个入口点,然后将相关信息存储在入口点对应的进程表中。其中一项信息是父进程的ID。当进程完成自己的生命周期后,就会执行exit()系统调用。这时候,原来进程表中的数据就会被进程的退出码、执行所用的CPU时间等数据所取代。这些数据将一直保留到系统将其传递给其父进程为止。可以看出,僵尸进程的出现时间是在子程序终止之后,但在父进程读取数据之前。如何避免僵尸进程1、父进程通过wait、waitpid等函数等待子进程结束,会导致父进程挂掉。2、如果父进程很忙,可以使用signal函数为SIGCHLD安装一个handler,因为子进程结束后,父进程会收到这个信号,可以在handler中调用wait进行回收。3、如果父进程不关心子进程什么时候结束,可以使用“singal(SIGCHLD),SIG_IGN”通知内核你对子进程结束不感兴趣。子进程结束后,内核会对其进行回收,不再给出更多信息。父进程发送信号。4.有一些技巧,就是fork()两次,父进程fork一个子进程,然后继续工作,子进程fork一个孙子进程,然后退出,然后孙进程被init接管,而孙进程结束后,init会进行回收,但是子进程回收还得自己做。让我们用进程和线程做一个类比。多线程是一个交集。多线程是一个平面交通系统。成本低,但红绿灯多,经常堵车。多进程是一个立交桥。虽然成本高,上坡下坡耗油多,但不堵车。这是一个抽象的概念。相信大家看了之后都会有这样的感受。进程和线程是两个相对的概念。一般来说,一个进程可以定义一个程序的实例(Instance)。在Win32中,一个进程不执行任何东西,它只是占用应用程序使用的地址空间。为了让一个进程做任何工作,该进程必须拥有至少一个线程,并且正是这个线程负责在进程的地址空间中包含代码。事实上,一个进程可以包含多个线程,这些线程可以同时执行进程地址空间中的代码。为了做到这一点,每个线程都有自己的一组CPU寄存器和堆栈。每个进程至少有一个线程在其地址空间中执行代码。如果进程地址空间中没有线程在执行代码,进程就没有继续存在的理由,系统会自动清理进程及其地址空间。多线程原理当一个进程被创建时,它的第一个线程称为主线程(Primarythread),它是由系统自动生成的。这个主线程然后可以产生额外的线程,而这些线程又可以产生更多的线程。在运行多线程程序时,从表面上看,这些线程似乎是同时运行的。事实并非如此,为了运行所有这些线程,操作系统会为每个单独的线程分配一些CPU时间。单CPU操作系统以时间片轮换的方式为线程提供时间片(Quantum)。每个线程使用完时间片后交出控制权,系统再将CPU时间片分配给下一个线程。由于每个时间片都足够短,这给人一种这些线程同时运行的错觉。创建额外线程的唯一目的是尽可能多地利用CPU时间。多线程的问题使用多线程编程可以给程序员带来很大的灵活性,也可以更容易地解决原本需要复杂技能的问题。但是,你不应该人为地将写好的程序分成一些片段,让这些片段按照各自的线程执行。这不是开发应用程序的正确方法。线程很有用,但使用时,您可能会在解决旧问题的同时产生新问题。例如,如果你想开发一个文字处理程序并希望打印功能作为一个单独的线程来执行。这听起来是个好主意,因为在打印时,用户可以立即返回并开始编辑文档。但是这样一来,在打印文档时可能会修改文档中的数据,导致打印出来的结果不再是预期的那样。也许打印功能最好不要放在单独的线程中,但是如果一定要使用多线程,也可以考虑以下解决方案:第一种方法是锁定正在打印的文档,让用户编辑其他文档。这样,文档在打印完成之前不会被修改;另一种方法可能更有效,那就是将文档复制到一个临时文件中,打印临时文件的内容,同时允许用户修改原始文档。当包含文档的临时文件被打印时,临时文件被删除。从上面的分析可以看出,多线程在帮助解决问题的同时,也可能带来新的问题。因此,有必要弄清楚什么时候创建多线程,什么时候不创建。一般来说,多线程常用于需要在前台运行的同时进行后台计算或逻辑判断的情况。线程的分类在MFC中,线程分为两类,即工作线程和用户界面线程。如果一个线程只完成后台计算,不需要和用户交互,可以使用工作线程;如果你需要创建一个线程来处理用户界面,你应该使用用户界面线程。两者的主要区别是MFC框架会在用户界面线程中加入一个消息循环,使用户界面线程可以处理自己消息队列中的消息。由此看来,如果需要在后台做一些简单的计算(比如电子表格的重新计算),首先应该考虑使用工作线程,而当后台线程需要处理更复杂的任务时,准确的说,当后台threads当执行过程会根据实际情况发生变化,应该使用用户界面线程,这样它就可以响应不同的消息。线程优先级当系统需要同时执行多个进程或线程时,有时需要指定线程的优先级。线程的优先级一般是指线程的基本优先级,即线程相对于进程的相对优先级与包含该线程的进程的优先级的组合。操作系统根据优先级安排所有活动线程。系统中的每个线程都分配了一个优先级,范围从0到31。在运行时,系统只是简单地将CPU时间分配给优先级为31的第一个线程,在该线程的时间片结束后,系统将CPU时间分配给下一个优先级为31的线程。当没有优先级为31的线程时,系统会开始为优先级为30的线程分配CPU时间,以此类推。除了程序员在程序中改变线程的优先级外,有时系统会在程序执行过程中自动动态改变线程的优先级,这是为了保证系统对最终用户的高响应性。例如,当用户按下键盘上的某个键时,系统会临时将处理WM_KEYDOWN消息的线程的优先级提高2到3,CPU按照一个完整的时间片来执行该线程。当时间片执行完毕后,系统会将线程的优先级减1。线程同步在使用多线程编程时,另一个非常重要的问题就是线程同步。所谓线程同步是指线程之间在相互通信时能够避免破坏自身数据的能力。同步问题是由于上述Win32系统的CPU时间片分配方式造成的。虽然在某一时刻,只有一个线程占用了CPU(单CPU)时间,但是没有办法知道线程是在何时何地被中断的,所以如何保证线程之间不破坏彼此的数据就显得尤为重要。在MFC中,可以使用4个同步对象来保证多个线程同时运行。它们是临界区对象(CCriticalSection)、互斥对象(CMutex)、信号量对象(CSemaphore)和事件对象(CEvent)。在这些对象中,临界区对象是最容易使用的,它的缺点是只能同步同一进程中的线程。此外,还有一种基本方法,本文称之为线性化方法,即在一个线程中完成编程过程中某些数据的写入操作。这样一来,由于同一个线程中的代码总是顺序执行的,不可能同时改写数据。总结:在线程中(相对于进程),线程是一个更接近执行体的概念。它可以与同一进程的其他线程共享数据,但有自己的栈空间和独立的执行顺序。这两者都可以提高程序的并发性,提高程序的运行效率和响应时间。线程和进程各有优缺点:线程执行开销小,但不利于资源管理和保护;而过程恰恰相反。根本的区别只有一点:每个进程都有自己的地址空间,多个进程,线程共享地址空间。在速度方面:线程生成速度快,线程间通信快,切换快等,因为它们在同一个地址空间里面。在资源利用率方面:线程的资源利用率更好,因为它们在同一个地址空间。同步方面:线程在使用公共变量/内存时需要使用同步机制,因为它们在同一个地址空间进程中:子进程是父进程的副本,子进程获取父进程的副本进程数据空间、堆和栈。
