进程和线程的区别

来源:互联网 发布:美工的电脑配置 编辑:程序博客网 时间:2024/03/29 22:15

进程和线程的区别

 

进程(在很多操作系统中也称为任务)是操作系统中的一个十分重要的概念。什么是进程呢?所谓进程是指程序的一次执行过程,在Windows95中,就是一个EXE文件的执行过程。但是应该注意,进程和程序是两个不同的概念,不能等同。程序是一组指令的有序集合,是静态的;进程则是指一组指令序列在处理机上的一次执行过
程,是动态的。严格地说,进程是程序在一个数据集合上的运行过程,它具有动态、并行、独立、异步等特性;一个进程由“创建”而产生、由调度而进入执行、在资源不能满足时被“挂起”、由“撤销”而消亡,因此,进程是有生命的。当然,一个进程将唯一地对应于一个EXE文件。程序和进程的关系还可以打个比方,如果把程序看作一支曲谱,进程可以理解为对这支曲谱的演奏过程。当然,这个比方并不准确,但可以帮助理解程序和进程之间的关系。在Windows95中还采用了线程的概念,所谓线程是指由进程进一步派生出来的一组代码(指令组)的执行过程。一个进程可以产生多个线程,这些线程都共享该进程的地址空间,它们可以并行、异步地执行。采用线程最主要的好处是:使同一个程序能有几个并行执行的路径,提高了执行速度;线程需要的系统开销比进程要小。应该说明的是,在Windows95中,“多任务”是基于线程而不是基于进程。多任务执行是指在同一台计算机系统的同一时刻运行多个程序。由于允许活动任务和后台任务同时运行,所以可以做到有一个任务在后台执行时,前台又能干另一件事。比如说,我们可以一边用图文处理程序编辑一个文件,一边让打印程序完成打印工作。这就极大地提高了工作效率,因为大多数用户都确实需要同时对几个不同的应用程序进行工作。
在Windows 3.X中,多个应用程序同时运行是采取一种所谓的“协作式”方式,称为“协作式多任务”。“协作”这个用语意味着多个应用程序之间必须相互协调,依次实现对操作系统的控制。它们并不是真正的多任务执行,因为其中还有多任务共享系统资源的问题。为了让操作系统把控制权从一个程序转换到另一个程序,当前活动的程序就必须周期地检查一个消息队列。如果某个程序不能经常检查消息队列,操作系统就不能实现控制权的转移。
在Windows 95中采用了一种所谓带优先权的多任务方式来运行基于Win32(Windows的32位)应用程序,称为“抢先式多任务”。在这种方式下,操作系统可以在需要时中断当前的任务,再按照任务队列中各个任务的优先级别来进行任务的调度。为兼容起见,基于Win16(Windows的16位)应用程序仍采用协作式方式完成多任务执
行。在Windows 3.X的协作式多任务环境中,必须在Windows 3.X“控制面板”中386增强模式实用程序的一个对话框中才能设置任务的优先级。而Windows 95则在缺省情况下就是完全带优先级的,所以不需要手工对优先级加以设置,这个任务由Windows 95在后台透明地加以完成。
Windows 95抢先式多任务执行实际上就是抢先式多线程执行。为了抢先式多线程执行实现,每个线程有一个优先级值,范围是从0到31。优先级0最低,保留给系统使用。优先级1到31分成四类:空闲(1-6),正常(5-11),高(11-15)和实时(16-31)。正常分类又进一步分成二级:后台(5-9)和前台(6-11)。注意这些范围是有重叠的。这样做可使调度更灵活,例如,允许某些后台任务比某些前台任务更重要,尽管在通常情况下,前台任务的优先级应该更高。使用实时优先级时要非常当心。如果你把一个任务的优先级设得太高,也可能无法实现多任务执行功能。这是因为一个任务的优先级太高了,它就完全不允许系统中有其他任务运行。
VMM(虚拟机管理程序)负责在分时抢先的环境里调度各个进程和线程,具体包括以下服务:生成线程、管理线程、撤消线程和线程调度。
VMM中有两个调度程序:主调度程序和时间片调度程序。主调度程序负责确定最高优先级的线程。只有最高优先级的线程才会运行,其他优先级较低的都被封锁;时间片调度程序负责为所有具有最高优先级的可运行任务分配时间片。系统运行过程中,线程的优先级可由系统或设备驱动程序(或两者)改变。例如,一旦中断产生,则处理这个中断的线程优先级临时提高,以便它立即得到时间来处理该中断。完成后,优先级可以再降低。
在抢先式多任务中,基于Win32的应用程序不必让位给其它程序就能以友好的方式实现多任务。操作系统会根据系统的需要把控制权交给某个运行中的任务,或从某个运行中的任务移走控制权。这才是真正的多任务操作系统。



 MFC的WinMain

  使用MFC编程的程序员刚开始都会提出这样一个问 题:我的程序是从哪儿开始执行的?回答是:从WinMain()开始执行的。提出这样的问题是由于在他们所编写的MFC应用中看不到WinMain()函 数。这个函数是隐藏在MFC框架中,MFC的设计者将它作得很通用(这主要得益于Window的消息驱动的编程机制,使得作一个通用的WinMain() 很容易),因此在一般情况下,无需更改WinMain()的代码,MFC的设计者也不提倡程序员修改WinMain()的代码。在MFC中,实际实现 WinMain()的代码是AfxWinMain()函数(根据其前缀Afx就知道这是一个全局的MFC函数)。

  一个Win32应用 程序(或进程)是由一个或多个并发的线程组成的,其中第一个启动的线程称为主线程,在Window下,一般将线程分成两大类,界面线程和工作线程,工作线 程就是一般的线程,它没有窗口,没有消息队列等,界面线程拥有一个或多个窗口,拥有一个消息队列和其他专属于界面线程的元素。在讨论AfxWinMain ()之前,首先要简略提一下MFC中的两个重要的类,CWinThread和CWinApp,CWinThread是用来封装界面线程的类, CWinApp是从CWinThread派生而来的。在CWinThread中,有两个很重要的虚拟函数InitInstance()和 ExitInistance(),MFC的程序员应该对这两个函数应该很熟悉。在CWinApp中,增加了另外一个虚拟函数 InitApplication(),讨论AfxWinMain()的主要目的是看这些函数是如何被调用的。

  AfxWinMain()的代码如下:

  int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,

  LPTSTR lpCmdLine, int nCmdShow)

  {

   ASSERT(hPrevInstance == NULL); file://在win32下,hPrevInstance始终为NULL

   int nReturnCode = -1;

   CWinThread* pThread = AfxGetThread();

   CWinApp* pApp = AfxGetApp();

   // AFX internal initialization

   if (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow))

    goto InitFailure;

    // App global initializations (rare)

   if (pApp != NULL && !pApp->InitApplication())

    goto InitFailure;

    // Perform specific initializations

   if (!pThread->InitInstance())

   {

    if (pThread->m_pMainWnd != NULL)

    {

     TRACE0("Warning: Destroying non-NULL m_pMainWnd/n");

     pThread->m_pMainWnd->DestroyWindow();

    }

    nReturnCode = pThread->ExitInstance();

    goto InitFailure;

    }

   nReturnCode = pThread->Run();

   InitFailure:

   AfxWinTerm();

  return nReturnCode;

  }

   在上面的代码中,AfxGetThread()返回的是当前界面线程对象的指针,AfxGetApp()返回的是应用程序对象的指针,如果该应用程序 (或进程)只有一个界面线程在运行,那么这两者返回的都是一个全局的应用程序对象指针,这个全局的应用程序对象就是MFC应用框架所默认的theApp对 象(每次使用AppWizard生成一个SDI或MDI应用程序时,AppWizard都会添加CYourApp theApp这条语句,AfxGetApp()返回的就是这个theApp的地址)。

CWinApp::InitApplication(), CWinThread::InitInstance(), CWinThread::ExitInstance()是如何被调用的,从上面的代码一看就知,我不再赘述。下面我们把焦点放在CWinThread:: Run()上。



  MFC的控制中心――CWinThread::Run()

   说CWinThread::Run()是MFC的控制中心,一点也没有夸大。在MFC中,所有来自于消息队列的消息的分派都是在 CWinThread::Run()函数中完成的,同AfxWinMain()一样,这个函数也是对程序员是不可见的,其道理同AfxWinMain() 的一样。

  首先要提的一点是,对每条从消息队列取出来的消息,MFC根据消息的类型,按照某个特定的模式进行分发处理,这个分发模式是 MFC自己定义的。固定的消息分发流程和在这个流程中的可动态改变其行为的虚拟函数就构成了MFC的消息分发模式。应用程序可以通过重载这些虚拟函数,来 局部定制自己的的消息分发模式。正是通过这些虚拟函数,MFC为应用程序提供了足够的灵活性。下面讨论的所有代码都来自于MFC源代码中的 threadcore.cpp文件,它们都是CWinThread的成员。

  CWinThread::Run()的结构

  CWinThread::Run()的代码如下:

  int CWinThread::Run()

  {

   ASSERT_VALID(this);

   // for tracking the idle time state

   BOOL bIdle = TRUE;

   LONG lIdleCount = 0;

   // acquire and dispatch messages until a WM_QUIT message is received.

   for (;;)

   {

    // phase1: check to see if we can do idle work

    while (bIdle &&

       !::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE))

     {

      // call OnIdle while in bIdle state

      if (!OnIdle(lIdleCount++))

      bIdle = FALSE; // assume "no idle" state

      }

    // phase2: pump messages while available

    do{

      // pump message, but quit on WM_QUIT

      if (!PumpMessage()) return ExitInstance();

      // reset "no idle" state after pumping "normal" message

      if (IsIdleMessage(&m_msgCur))

      {

       bIdle = TRUE;

       lIdleCount = 0;

      }

    } while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE));

   }

   ASSERT(FALSE); // not reachable

  }


CWinThread::Run()的处理过程如下

  先根据空闲标志以及消息队列是否为空这两个条件判断当前线程是否处于空闲状态(这个“空闲”的含义同操作系统的含义不同,是MFC自己所谓的“空闲”),如果是,就调用CWinThread::OnIdle(),这也是我们比较熟悉的一个虚拟函数。

  如果不是,从消息队列中取出消息,进行处理,直到消息队列为空。

   在这里,我们发现,MFC不是调用GetMessage()从线程消息队列中取消息,而是调用PeekMessage()。其原因在于, GetMessage()是一个具有同步行为的函数,如果消息队列中没有消息,GetMessage()会一直阻塞,使得线程处于睡眠状态,直到消息队列 中有一条或多条消息,操作系统才会唤醒该线程,GetMessage()才会返回,如果线程处于睡眠状态了,就不会使线程具有MFC所谓的“空闲”状态 了;而PeekMessage()则是一个具有异步行为的函数,如果消息队列中没有消息,它马上返回0,不会导致线程处于睡眠状态。

   在上面的代码中,有两个函数值得探讨,一个是空闲处理函数OnIdle(),另外一个是消息分发处理函数PumpMessage()。不要忽视 CWinThread的OnIdle()函数,它作了很多有意义的事情。下面讨论PumpMessage(),OnIdle()将在后面的章节里讨论。

  CWinThread::Run()的核心――CWinThread::PumpMessage()

  标题强调了PumpMessage()的重要性,Run()是MFC的控制中心,而PumpMessage()又是Run()的核心,所以从MFC的真正控制中心是PumpMessage()。PumpMessage()的代码极其简单:

  BOOL CWinThread::PumpMessage()

  {

   ASSERT_VALID(this);

   if (!::GetMessage(&m_msgCur, NULL, NULL, NULL))

    return FALSE;

   // process this message

   if (m_msgCur.message != WM_KICKIDLE && !PreTranslateMessage(&m_msgCur))

   {

    ::TranslateMessage(&m_msgCur);

    ::DispatchMessage(&m_msgCur);

   }

   return TRUE;

  }

   首先,PumpMessage()调用GetMessage()从消息队列中取一条消息,由于PumpMessage()是在消息队列中有消息的时候才 被调用的,所以GetMessage()会马上返回,根据其返回值,判断当前取出的消息是不是WM_QUIT消息(这个消息一般对是通过调用 PostQuitMessage()放入线程消息队列的),如果是,就返回FALSE,CWinThread::Run()该退出了, CWinThread::Run()直接调用CWinThread::ExitInstance()退出应用程序。在GetMessage()的后面是我 们所熟悉的TranslateMessage()和DispatchMessage()函数。

可以看出,是否调用TranslateMessage()和DispatchMessage()是由一个名称为PreTranslateMessage()函数的返回值决定的,如果该函数返回TRUE,则不会把该消息分发给窗口函数处理。

  就我个人观点而言,正是有了这个PreTranslateMessage(),才使得MFC能够灵活的控制消息的分发模式,可以说,PreTranslateMessage()就是MFC的消息分发模式。


 <三>MFC的特色――PreTranslateMessage()

   经过层层扒皮,终于找到了CWinThread::Run()最具特色的地方,这就是PreTranslateMessage()函数。同前面使用 SDK编写的显示”Hello, world!”程序的消息循环不同的地方在于,MFC多了这个PreTranslateMessage(),PreTranslateMessage() 最先获得了应用程序的消息处理权!下面我们对PreTranslateMessage()进行剥皮式分析。同前面一样,首先看看实际的 PreTranslateMessage()的代码:

  BOOL CWinThread::PreTranslateMessage(MSG* pMsg)

  {

   ASSERT_VALID(this);

   // if this is a thread-message, short-circuit this function

   if (pMsg->hwnd == NULL && DispatchThreadMessageEx(pMsg)) return TRUE;

   // walk from target to main window

   CWnd* pMainWnd = AfxGetMainWnd();

   if (CWnd::WalkPreTranslateTree(pMainWnd->GetSafeHwnd(), pMsg)) return TRUE;

   // in case of modeless dialogs, last chance route through main

   // window's accelerator table

   if (pMainWnd != NULL)

   {

    CWnd* pWnd = CWnd::FromHandle(pMsg->hwnd);

    if (pWnd->GetTopLevelParent() != pMainWnd)

    return pMainWnd->PreTranslateMessage(pMsg);

   }

   return FALSE; // no special processing

  }

  PreTranslateMessage()的处理过程如下:

  首先判断该消息是否是一个线程消息(消息的窗口句柄为空的消息),如果是,交给DispatchThreadMessageEx()处理。我们暂时不管DispatchThreadMessageEx(),它不是我们讨论的重点。

  调用CWnd::WalkPreTranslateTree()对该消息进行处理,注意该函数的一个参数是线程主窗口的句柄,这是PreTranslateMessage()的核心代码,在后面会对这个函数进行详细的分析。

  对于非模式对话框,这特别的、额外的处理。

  下面详细讨论一下CWnd::WalkPreTranslateTree()函数,它的代码很简单:

  BOOL PASCAL CWnd::WalkPreTranslateTree(HWND hWndStop, MSG* pMsg)

  {

   ASSERT(hWndStop == NULL || ::IsWindow(hWndStop));

   ASSERT(pMsg != NULL);

   // walk from the target window up to the hWndStop window checking

   // if any window wants to translate this message

   for (HWND hWnd = pMsg->hwnd; hWnd != NULL; hWnd = ::GetParent(hWnd))

   {

    CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);

    if (pWnd != NULL)

    {

     // target window is a C++ window

     if (pWnd->PreTranslateMessage(pMsg))

      return TRUE; // trapped by target window (eg: accelerators)

    }

    // got to hWndStop window without interest

    if (hWnd == hWndStop)

    break;

   }

   return FALSE; // no special processing

  }

CWnd:: WalkPreTranslateTree()的所使用的策略很简单,拥有该消息的窗口最先获得该消息的处理权,如果它不想对该消息进行处理(该窗口对象 的PreTranslateMessage()函数返回FALSE),就将处理权交给它的父亲窗口,如此向树的根部遍历,直到遇到hWndStop(在 CWinThread::PreTranslateMessage()中,hWndStop表示的是线程主窗口的句柄)。记住这个消息处理权的传递方向, 是由树的某个一般节点或叶子节点向树的根部传递!

  小结

  下面对这一章作一个小结。

  MFC消息控制流最具特色的地方是CWnd类的虚拟函数PreTranslateMessage(),通过重载这个函数,我们可以改变MFC的消息控制流程,甚至可以作一个全新的控制流出来,在下面的一章会对MFC的实现作详细介绍。

  只有穿过消息队列的消息才受PreTranslateMessage()影响,采用SendMessage()或其他类似的方式向窗口直接发送的而不经过消息队列的消息根本不会理睬PreTranslateMessage()的存在

  传给PreTranslateMessage()的消息是未经翻译过的消息,它没有经过TranslateMessage()处理,在某些情况下,要仔细处理,以免漏掉消息。

 

一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.

 

 

线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.

 

进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.

 

从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。

 

线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

 

另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。

 

线程的划分尺度小于进程,使得多线程程序的并发性高。

 

进程和线程都是由操作系统所体会的程序运行的基本单元,系统利用该基本单元实现系统对应用的并发性。进程和线程的区别在于:

简而言之,一个程序至少有一个进程,一个进程至少有一个线程.