Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1590826
  • 博文数量: 441
  • 博客积分: 20087
  • 博客等级: 上将
  • 技术积分: 3562
  • 用 户 组: 普通用户
  • 注册时间: 2006-06-19 15:35
文章分类

全部博文(441)

文章存档

2014年(1)

2012年(1)

2011年(8)

2010年(16)

2009年(15)

2008年(152)

2007年(178)

2006年(70)

分类: C/C++

2008-04-17 15:30:31

进程和线程在字面上看起来颇为相近,两者又是息息相关的,所以往往给初学者造成混淆,其实从英文

原文来看,进程(Process)和线程(Thread)是完全不同的。

进程是正在执行中的应用程序,磁盘上存储的可执行文件只能称之为文件而不能称为进程,内存中正在

执行的文件才叫做进程。一个进程是一个执行中的文件使用资源的总和,包括虚拟地址空间、代码、数

据、对象句柄、环境变量和执行单元等。当一个应用程序同时被多次执行时,产生的是多个进程,因为

虽然它们由同一个文件执行而来,但是它们的地址空间等资源是互相隔离的,这与不同文件在执行的情

况是一样的。

进程是不“活泼”的,要使进程中的代码被真正运行起来,它必须拥有在这个环境中运行代码的“执行单元

”,这就是线程,线程是操作系统分配处理器时间的基本单位,一个线程可以看成是一个执行单元,它负

责执行包含在进程地址空间中的代码。当一个进程被建立的时候,操作系统会自动为它建立一个线程,

这个线程从程序指定的入口地址开始执行(对于Windows编程,就是WinMain了),通常把这个线程

称为主线程,当主线程执行完最后一句代码的时候,进程也就没有继续存在的理由了,这时操作系统会

撤销进程拥有的地址空间和其他资源,对我们来说,这就意味着程序的终止。

在主线程中,程序可以继续建立多个线程来“同时”执行进程地址空间中的代码,这些线程被称为子线程

。操作系统为每个线程保存单独的寄存器环境和单独的堆栈,但是它们共享进程的地址空间、对象句柄

、代码和数据等其他资源,它们可以执行相同的代码,可以对相同的数据进行操作,也可以使用相同的

句柄。你可以把一个进程中的多个线程看成是进程范围内的“多任务”。

进程和线程的关系可以看做是“容器”和“内容物”的关系,进程是线程的容器,线程总是在某个进程的环

境中被创建,它不可以脱离进程单独存在,而且线程的整个生命周期都存在于进程中,如果进程被结束

,其中的线程也就自然结束了。

系统中可以同时存在多个进程,每个进程中同时又可以有多个线程在执行,为了使所有进程中的线程都

能够“同时”运行,操作系统为每个线程轮流分配时间片,当轮到一个线程执行的时候,系统将线程的寄

存器值恢复回去并开始执行,当时间片结束的时候,系统打断线程的执行,将线程当前的寄存器环境保

存下来并切换到另一个线程中去执行,如此循环。当切换到的线程和上一个时间片的线程并不属于同一

个进程的时候,操作系统同时切换物理内存到线性地址空间的映射关系,这样线程存取的就是自己所属

的进程中的代码和数据。

对于单处理器的计算机来说,不同线程实际上是在轮流使用同一个处理器,一个程序的运行速度并不会

因为建立多个线程而加快,因为线程多了以后每个线程等待的时间也就越长,但是对安装了多个处理器

的计算机来说,操作系统可以将不同的线程安排到不同的处理器中去执行,这样一个进程中的多个线程

就会真正获得多个时间片而加快整个进程的运行速度。当然这个过程还需要操作系统的支持。Windows

9x系统不支持多处理器,即使系统中安装有多个处理器,所有线程还是被安排在同一个处理器上运行,

其他的处理器则处于空闲状态。Windows NT系统支持多处理器。

虽然大部分的个人计算机是单处理器的计算机,在应用程序中使用多线程并不能提高程序的运行速度,

但多线程编程的出发点并不仅仅是为了使用多处理器,更多的是用来解决一些实际问题。下面先看一个

有问题的程序:

这个程序很简单,就是一个计数器,点击“计数”按钮就开始计数,点击“暂停/恢复”就暂停或者恢复计数



新建一个VC++6的win32应用程序,代码如下:

// counter.c

#include
#include "resource.h"

HWND        hWinMain = NULL;
HWND        hWinCount = NULL;
HWND        hWinPause = NULL;
DWORD        dwOption = 0;

#define        F_PAUSE        0x01
#define        F_STOP        0x02
#define        F_COUNTING    0x04

char        szStop[] = TEXT("停止计数");
char        szStart[] = TEXT("计数");

void Counter();
LRESULT CALLBACK DialogProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM

lParam);

int WINAPI WinMain(IN HINSTANCE hInstance, IN HINSTANCE hPrevInstance, IN

LPSTR lpCmdLine, IN int nShowCmd )
{
    DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL,

DialogProc, 0);
    return 0;
}

LRESULT CALLBACK DialogProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM

lParam)
{
    switch ( uMsg )
    {
    case WM_COMMAND:
        if ( LOWORD(wParam) == IDOK )
        {
            if ( dwOption & F_COUNTING )
                dwOption |= F_STOP;
            else
            {  
                                                           Counter();
            }
        }
        else if ( LOWORD(wParam) == IDC_PAUSE )
        {
            dwOption ^= F_PAUSE;
        }
        break;
    case WM_CLOSE:
        EndDialog(hWnd, 0);
        break;
    case WM_INITDIALOG:
        hWinMain = hWnd;
        hWinCount = GetDlgItem(hWnd, IDOK);
        hWinPause = GetDlgItem(hWnd, IDC_PAUSE);
        break;
    default:
        return FALSE;
    }
    return TRUE;
}

void Counter()
{
    DWORD dwNum = 0;
    
    dwOption |= F_COUNTING; // 正在计数标志
    dwOption &= ~(F_STOP | F_PAUSE); // 清除停止和暂停标志

    SetWindowText(hWinCount, szStop);
    EnableWindow(hWinPause, TRUE);

    while ( !(dwOption & F_STOP) ) // 不是停止,暂停或者正在进行
    {
        if ( !(dwOption & F_PAUSE ) ) // 不是暂停,那就是进行中
        {
            ++dwNum;
            SetDlgItemInt(hWinMain, IDC_COUNTER, dwNum,

FALSE);
        }
        // 暂停的时候,就是空循环,直到停止或者继续
    }

    SetWindowText(hWinCount, szStart);
    EnableWindow(hWinPause, FALSE);
    dwOption &= ~(F_STOP | F_PAUSE | F_COUNTING);

}

// resource.h
#define IDD_MAIN                        101
#define IDC_COUNTER                     1000
#define IDC_PAUSE                       1002
#define IDC_STATIC                      -1


// Counter.rc
#include "resource.h"
#include "afxres.h"
//
// Dialog
//

IDD_MAIN DIALOG DISCARDABLE  0, 0, 177, 62
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "计数器"
FONT 10, "宋体"
BEGIN
    LTEXT           "计数值:",IDC_STATIC,7,7,33,8
    EDITTEXT        IDC_COUNTER,48,7,102,14,ES_AUTOHSCROLL | ES_READONLY
    PUSHBUTTON      "计数",IDOK,26,38,50,14
    PUSHBUTTON      "暂停/恢复",IDC_PAUSE,101,38,50,14
END

编译后运行,你会发现程序像“死”了一样,根本动不了,连对话框都移动不了,点击任何按钮都没有作用,最后你不得不用任务管理器结束这个进程。

为什么会这样呢?这是因为主线程自从开始进入计数循环以后,就一直在那里“埋头苦干”,忙于计数工作,以至于把WM_COMMAND消息的处理抛到脑后去了,WM_COMMAND消息没有返回,对话框内部的消息循环就停留在DispatchMessage函数里面,以至于消息队列中的后续消息堆积在那里无法处理,这样不管用户按动“停止计数”按钮也好,移动对话框也好,这些动作虽然会被Windows检测到并转换成相应的消息放入消息循环中去,但是这些消息堆积在那里无法处理,所以就看不到对话框有任何的响应。

程序进入了一个怪圈:停止或暂停循环的条件是设置标志位,标志位是在按动“停止计数”或“暂停/恢复”按钮的WM_COMMAND消息中设置的,而WM_COMMAND消息被堆积在消息队列中无法处理,结果标志位永远不可能被设置,程序也就永远无法动弹了。虽然在程序一动不动的背后计数工作还在进行,显示计数值的SetDlgItemInt函数也不停地被调用,但是刷新对话框的WM_PAINT消息也同样没有被处理,所以编辑框中的计数值也无法被显示出来。

这个“问题程序”是Win32编程中“1/10秒规则”的一个极端例子,1/10秒规则指窗口过程处理任何一条消息的时间都不应该超过1/10秒,否则会因为消息的堆积造成程序对用户动作的响应变得迟缓。如果一条消息的处理时间超过1/10秒,那么就最好采取别的方法来完成,可以在消息循环中使用PeekMessage来获取空闲时间的方法就是一种,另一种方法是使用定时器在指定的时间间隔中每次完成一小部分工作,但对于这两种方法,程序必须将一个长时间的工作划分成多个小的部分,每部分的操作时间应该少于1/10秒。

显然,这两种方法也不是很好的办法,因为在不同主频的计算机中,1/10秒时间内可以处理的工作量是不同的,如果按照300 MHz处理器设计每小部分工作的工作量,那么到1GHz处理器上运行时,空出来的时间就被浪费了。实际上,解决1/10秒问题的最好办法就是使用多线程编程技术,程序可以建立一个新的线程来完成时间可能超过1/10秒的工作。

下面来看一看改过的程序:

LRESULT CALLBACK DialogProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    HANDLE hThread = NULL;
    switch ( uMsg )
    {
    case WM_COMMAND:
        if ( LOWORD(wParam) == IDOK )
        {
            if ( dwOption & F_COUNTING )
                dwOption |= F_STOP;
            else
            {   // 创建一个线程来计数, 这里是改过的
                hThread = CreateThread(NULL, 0, Counter, NULL, 0, NULL);
                CloseHandle(hThread);
            }
        }
        else if ( LOWORD(wParam) == IDC_PAUSE )
        {
            dwOption ^= F_PAUSE;
        }
        break;
    case WM_CLOSE:
        EndDialog(hWnd, 0);
        break;
    case WM_INITDIALOG:
        hWinMain = hWnd;
        hWinCount = GetDlgItem(hWnd, IDOK);
        hWinPause = GetDlgItem(hWnd, IDC_PAUSE);
        break;
    default:
        return FALSE;
    }
    return TRUE;
}

// Counter()函数的原型变了(需要和线程过程原型保持一致),其它都没有变
DWORD WINAPI Counter(LPVOID lpParameter)
{
    DWORD dwNum = 0;
   
    dwOption |= F_COUNTING; // 正在计数标志
    dwOption &= ~(F_STOP | F_PAUSE); // 清除停止和暂停标志

    SetWindowText(hWinCount, szStop);
    EnableWindow(hWinPause, TRUE);

    while ( !(dwOption & F_STOP) ) // 不是停止,暂停或者正在进行
    {
        if ( !(dwOption & F_PAUSE ) ) // 不是暂停,那就是进行中
        {
            ++dwNum;
            SetDlgItemInt(hWinMain, IDC_COUNTER, dwNum, FALSE);
        }
        // 暂停的时候,就是空循环,直到停止或者继续
    }

    SetWindowText(hWinCount, szStart);
    EnableWindow(hWinPause, FALSE);
    dwOption &= ~(F_STOP | F_PAUSE | F_COUNTING);

    return 0; // 添加了返回值
}

这回你再重新编译,运行,程序是不是“活”了起来?在这里,计数被放在了子线程中,主线程在WM_COMMAND中创建完了子线程之后就返回了,继续自己的消息循环,这样就能够响应用户的请求,所以,你才能看到正确的运行结果。

这样看起来是不是很简单?就是一个CreateThread函数调用,然后把你要做的工作放在它的线程函数里面,如果不需要线程句柄了就用CloseHandle将其关闭。应该说,这是最简单的多线程应用,就是一个主线程,然后一个子线程,没有涉及到线程的同步问题。如果想看看线程同步问题,请关注后续文章。

需要说明的是,本程序中的理论讲解部分来源于罗云彬的32位汇编语言一书,程序也是由他那本书里面的32位汇编源程序改写而来的,想了解汇编的实现,就请看他的那本书。

转载请注明出处。
author: cnhnyu
e-mail: cnhnyu@gmail.com
qq: 94483026
阅读(2157) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~