Windows Embedded Compact 7中的进程和线程(上)
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源. 一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.本章主要讲解Windows Embedded Compact 7中的进程和线程。
6.1 Windows Embedded Compact 7中的进程
6.1.1 进程概述
应用程序通常指的是保存在磁盘上的一个可执行文件,是静态的。当应用程序被加载到内存中并运行后,就成为一个或多个进程。在操作系统中,进程通常是指一个程序的运行实例,进程是资源分配的基本单位,所以如果用户使用写字板同时打开8个不同的文件,通过任务管理器将看到8个不同的进程。进程由两个组件构成:一个内核对象,操作系统用它来管理进程,以及一个虚拟地址空间,操作系统的虚拟地址管理保证一个进程不能非法地访问另一个进程的虚拟地址空间 (否则将导致段错误出现)。每个进程在操作系统中都被分配一个唯一的标识,称为ProcessId,在Windows CE7中是一个32位的整数,用于区分不同的进程。新的进程创建后,还将得到进程句柄 (handle),所有对进程对象的操作都通过进程句柄来完成。
每个进程又可以包含一个或多个线程,所有的线程都是在它的地址空间中运行,共享进程的系统资源。其中一个比较特殊的是主线程,系统在创建进程的时候将自动创建它的主线程,主线程负责进程的初始化并根据需要创建其它的线程。线程是系统CPU调度和分派的基本单位,操作系统会采用动态优先级调度策略为每个线程调度一些CPU时间,为线程分配时间片,使所有线程“并发”运行。Windows CE系统是基于多线程的操作系统。
Windows CE中的进程与桌面Windows系统中的进程相比,包含更少的状态信息,Windows CE中的进程不支持环境变量,没有当前目录,不支持安全属性,不支持句柄继承,因此在Windows CE的创建进程函数中,与上下文相关的参数大都被设置为NULL或0。
6.1.2 创建进程
在Windows CE7系统中,函数CreateProcess()用于创建一个新的进程以及它的主线程,这个进程将运行参数指定的可执行文件(程序),这个函数的原型如下:
BOOL CreateProcess(
LPCWSTR pszImageName,
LPCWSTR pszCmdLine,
LPSECURITY_ATTRIBUTES psaProcess,
LPSECURITY_ATTRIBUTES psaThread,
BOOL fInheritHandles,
DWORD fdwCreate,
LPVOID pvEnvironment,
LPWSTR pszCurDir,
LPSTARTUPINFOW psiStartInfo,
LPPROCESS_INFORMATION pProcInfo
);
这是一个比较复杂的函数,需要多达10个参数,下面分别介绍这些参数的涵义:
l pszImageName: 指定要加载的应用程序名,这里既可以使用应用程序的完整路径,也可以使用相对路径。如果使用相对路径,则将按照下面的顺序查找目录:(1)系统的Windows目录(\windows); (2)设备的根目录(\); (3)一个OEM指定的目录。这是一个指向字符串的指针,在Windows CE中,这个参数不能指定为NULL,这是因为Windows CE不支持Windows桌面系统下的将pszCmdLine参数中的第一个命令作为应用程序名的方法。应用程序名可以不指定文件扩展名,如果没有系统将自动补上EXE扩展。
l pszCmdLine: 指定启动应用程序的命令行。该参数可以指定为NULL,表示应用程序不带参数,此时将单单使用参数pszImageName作为命令行;如果不为NULL,那么参数pszImageName指定应用程序名,pszCmdLine指定除应用程序名外的命令行参数,注意不要将应用程序名作为pszCmdLine的第一个命令传递。对于C运行时的进程,可以使用argc和argv来访问命令行参数。在Windows CE下,这个参数总是以Unicode字符串传递。
l psaProcess: 指定进程的安全属性,Windows CE不支持,设置为NULL。
l psaThread: 指定线程的安全属性,Windows CE不支持,设置为NULL。
l fInheritHandles: 指定新创建的进程是否继承父进程的句柄,Windows CE不支持,设置为NULL。
l fdwCreate: 指定控制进程优先级以及创建过程的标志,表6-1列出了Windows CE支持的值,这些值可以组合使用(使用“|”运算符)。
表6-1 Windows CE支持的参数fdwCreate可取值及说明
值
说明
CREATE_NEW_CONSOLE
为新的进程创建一个新的控制台,而不继承父进程的控制台
CREATE_SUSPENDED
新进程的主线程将被初始化为挂起状态,直到对应的ResumeThread()函数被调用才进入可运行状态
DEBUG_PROCESS
新创建进程将被调用进程调试,系统将把新创建进程的所有调试信息通知调用进程。此时新创建进程的子进程也将被调用进程调试。
DEBUG_ONLY_THIS_PROCESS
与DEBUG_PROCESS标志同时使用,只负责调试新创建进程,它的子进程不被调试
INHERIT_CALLER_PRIORITY
指定新创建进程继承调用进程的优先级
l pvEnvironment: 指定新创建进程的环境块,Windows CE不支持,设置为NULL。
l pszCurDir: 指定进程的当前目录,Windows CE不支持,设置为NULL。
l psiStartInfo: 指定进程的启动信息,Windows CE不支持,设置为NULL。
l pProcInfo: 这是输出参数,用于返回新创建进程的标识信息,这是一个指向结构体LPPROCESS_INFORMATION的指针,如果用户不需要对进程及主线程的句柄进行处理,可以给这个参数设置为NULL。结构体LPPROCESS_INFORMATION的定义如下:
typedef struct _PROCESS_INFORMATION {
HANDLE hProcess;
HANDLE hThread;
DWORD dwProcessId;
DWORD dwThreadId;
} PROCESS_INFORMATION;
其中,hProcess为返回的进程句柄,所有对进程对象的操作都通过这个进程句柄完成; hThread为返回的进程主线程的句柄,所有对主线程对象的操作都通过这个句柄完成; dwProcessId为系统分配的全局进程标识(Id),为一个32位的整数,进程标识在进程的生命期内有效,一旦进程结束,系统将自动回收,进程标识的另一个作用是作为OpenProcess()函数的参数来打开进程的句柄;dwThreadId为全局主线程标识。
当用户提供一个PROCESS_INFORMATION结构体接收新创建进程及其主线程句柄的副本时,加上进程内部维护的句柄,此时系统内该进程句柄的引用计数将为2,所以一定要记得在使用完进程句柄后关闭句柄。如果用户不需要进程句柄,应该直接传一个NULL进来,这样系统就不会产生句柄的副本。另外一个比较常见的作法是在进程创建成功后,立即关闭得到的句柄,这样保证新创建进程结束后能够正常释放,防止内存泄漏。
当系统成功创建进程,CreateProcess()函数返回非0,创建失败则返回0。通过GetLastError()函数能够得到关于错误的说明信息。
下面的例子演示如何使用CreateProcess()函数创建一个进程,该进程将启动Windos CE7下的任务管理器。
(1) 新建一个项目,项目类型为VC++下的智能设备,使用MFC只能设备应用程序向导创建一个基于对话框的应用程序,输入工程名,这里使用CreateProcess。
(2) 在对话框内添加一个列表框,和一个按钮,如图6.1所示。在列表框内添加几个Windows CE下常用的应用,如命令行,IE浏览器,写字板。注意在Windows CE下,IE浏览器的应用程序名变为”iesample.exe”,而不是之前我们熟知的”iexplore.exe”。用户首先选择一个应用,然后单击下面的”Create a Process”按钮,就将新开一个进程,启动指定的应用。
图6.1 创建进程运行结果
首先需要在对话框内的初始化函数内,对列表框进行初始化:
BOOL CCreateProcessDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// Set the icon for this dialog. The framework does this automatically
// when the application's main window is not a dialog
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon
// TODO: Add extra initialization here
//向列表框内添加应用程序名称
m_ListProcs.AddString(_T("control")); //打开控制面板
m_ListProcs.AddString(_T("pword")); //打开写字板
m_ListProcs.AddString(_T("cmd")); //打开命令行
m_ListProcs.AddString(_T("iesample")); //注意在WINCE7中为iesample,而不是ieplore
m_ListProcs.SetCurSel(0);
return TRUE; // return TRUE unless you set the focus to a control
}
然后实现”Create a Process”按钮的单击事件处理函数:
void CCreateProcessDlg::OnBnClickedButtonCreateproc()
{
// TODO: Add your control notification handler code here
CString procName;
m_ListProcs.GetText(m_ListProcs.GetCurSel(),procName);
procName.Format(_T("\\Windows\\%s.exe"),procName);
AfxMessageBox(procName);
PROCESS_INFORMATION processInfo; //
if (!CreateProcess(procName, NULL, NULL, NULL, NULL
, CREATE_NEW_CONSOLE
, NULL, NULL, NULL, &processInfo))
{
MessageBox(_T("Failed to create a new process"));
return;
}
CloseHandle(processInfo.hThread);
CloseHandle(processInfo.hProcess);
}
这里在创建进程时运行指定的应用程序,在运行之前将应用程序的完整路径补上。调用CreateProcess()函数后,我们需要检查返回值,如果创建进程成功,那么立即关闭返回的进程句柄和主线程句柄,因为我们不需要用到它们。
6 1.3 终止进程
进程都是有生命周期,当进程完成所有的操作后,从主线程的入口函数返回之后,这个进程就会被终止,这是一种比较理想的终止进程的方式,这样能够保证主线程的资源被正确清理。除了这个常规的终止进程的方式,下面介绍几种“非常规”的终止进程的方式:
1) 进程中的某一个线程(不一定是主线程)调用ExitProcess()函数。这个函数的原型如下:
VOID ExitProcess(
UINT uExitCode
);
参数uExitCode指定进程同时也是它的所有线程的退出码,可以分别通过GetExitCodeProcess()及GetExitCodeThread()函数获取指定进程及线程的退出码。
2) 一个进程调用TerminateProcess()函数终止另一个进程,当然这个首先必须获取被终止进程的句柄,比如我们上面介绍的CreateProcess()函数就会向父进程返回新创建子进程的句柄。TerminateProcess()函数的原型如下:
BOOL TerminateProcess(
HANDLE hProcess,
DWORD uExitCode
);
参数hProcess指定将被终止进程的句柄,实际上如果知道进程的ProcessId就可以通过调用OpenProcess()函数获取进程的句柄。参数uExitCode指定进程以及它的所有线程的退出码。
如果终止进程成功,将返回非0;如果失败就返回0。如果系统非常忙,那么TerminateProcess()可能会由于超时而失败,这时GetLastError将返回ERROR_BUSY。
3) 终止进程的主线程。一旦主线程被终止,那么进程以及进程包含的所有线程都会被终止,终止主线程的方法有:调用ExitThread()函数或调用TerminateThread()函数。
有一点需要注意的是终止一个进程并不总意味着系统将销毁进程的内核对象,只有当进程内核对象的引用计数都为0时,才会被删除。
6.1.4 其他相关函数
除了上面介绍的几个用于创建,终止进程的函数外,Windows CE7还提供了其它的几个操作进程的函数,下面分别介绍。
l GetCommandLine()函数返回当前进程的命令行字符串
l GetCurrentProcess()函数返回当前进程的句柄,当进程内部需要用到自己的句柄时,这个函数就比较有用
l GetCurrentProcessId()函数返回当前进程的标识ProcessId。
l GetExitCodeProcess()函数获取指定进程的终止状态,这个函数的原型如下:
BOOL GetExitCodeProcess(
HANDLE hProcess,
LPDWORD lpExitCode
);
参数hProcess指定进程的句柄,参数lpExitCode用于返回进程的终止状态。如果指定的进程还没有被终止,那么进程的终止状态将返回STILL_ACTIVE,这可以用于判断一个进程是否存活。如果进程已经终止,那么退出状态可以是如下的几种值:(1)如果进程被“非常规”终止,此时将是在上面介绍过的ExitProcess()或TerminateProcess()函数内指定的进程的退出码;(2)如果进程从主线程的入口函数 (main或WinMain) 正常返回,此时将是主线程入口函数的返回值(return value);(3)如果进程由于未处理的异常而被终止,此时将是异常值。
如果获取进程的终止状态成功,返回非0,否则返回0。
l OpenProcess()函数用于获取指定进程对象的句柄,这个函数的原型如下:
HANDLE OpenProcess(
DWORD fdwAccess,
BOOL fInherit,
DWORD IDProcess
);
参数fdwAccess指定对进程对象的访问权限,这个在Widows CE中不支持,设置为0;参数fInherit指定是否继承句柄,Windows CE中也不支持,设置为FALSE;参数IDProcess指定进程的标识ProcessId。OpenProcess()函数使用进程的ProcessId获取进程句柄,如果失败,将返回NULL。
l ReadProcessMemory()用于读取指定进程的地址空间,函数原型如下:
BOOL ReadProcessMemory(
HANDLE hProcess,
LPCVOID lpBaseAddress,
LPVOID lpBuffer,
DWORD nSize,
LPDWORD lpNumberOfBytesRead
);
参数hProcess指定被访问进程的句柄;参数lpBaseAddress指定访问的起始地址;参数lpBuffer用于接收从进程内读出的数据;参数nSize指定读取的数据量,单位为字节;参数lpNumberOfBytesRead返回实际读出的数据量,单位为字节,这个可以设置为NULL,表示不关心实际读回的数据量。如果从指定进程读回数据成功,函数返回非0,否则将返回0。
l WriteProcessMemory()函数则用于向进程空间内写入数据,这个函数的原型如下:
BOOL WriteProcessMemory(
HANDLE hProcess,
LPVOID lpBaseAddress,
LPVOID lpBuffer,
DWORD nSize,
LPDWORD lpNumberOfBytesWritten
);
这几个参数的涵义与ReadProcessMemory类似。
6.2 Windows Embedded Compact 7中的线程
6.2.1 线程概述
Windows CE是支持优先级的多任务操作系统,允许一个进程创建一个或多个线程。线程是进程中的一个实体,是系统独立调度以及分配CPU的基本单位。同一个进程内的所有线程共享进程的资源,包括进程的地址空间,每个线程对句柄都拥有相同的访问权限,包括同步对象句柄,文件句柄以及内存对象句柄等。每个线程都具有一个独立的栈,包含各自的CPU寄存器状态,这些也称之为线程的上下文。当线程被调度换出时,需要将这些上下文信息保存到线程状态中;当线程被调度执行时,再将上下文信息恢复,继续往下执行。
Windows CE中的线程具有多种状态,可以为:运行(Running),挂起(Suspend),睡眠(Sleeping),阻塞(Blocked),终止(Terminated)。当所有线程都处于阻塞状态时,Windows CE将进入空闲模式(Idle mode),这时CPU消耗的能耗将很小。
线程的优先级
Windows CE采用一种基于优先级的时间片调度算法来调度线程的执行,每个线程都被分配一个优先级,由于Windows CE不支持进程的优先类别,线程的优先级不受进程的影响,可以直接设置线程的优先级,优先级比较高的线程总是比优先级低的线程优先被调度,而不像Windows桌面系统那样设置线程在进程内的相对优先级。一个线程每次被调度执行最多占用CPU一个时间片,在线程的时间片用完之后,如果该线程不是一个运行到完成类型的线程,那么这个线程将被挂起,调度另一个线程运行。如果线程提前完成任务,可以请求放弃自己的时间片。
在Windows CE 7中,可以支持256种不同的线程优先级,取值范围为[0, 255],值越小表示优先级越高,其中0代表最高优先级,255代表最低优先级。前面248个优先级(0 ~ 247)代表实时优先级, 一般用于设备驱动或系统级线程,应用程序一般使用最低的8个优先级(248 ~ 255),表6-2列出了这些优先级。
表6-2 Windows CE中应用程序使用的线程优先级
优先级
说明
THREAD_PRIORITY_TIME_CRITICAL
比正常优先级高3
THREAD_PRIORITY_HIGHEST
比正常优先级高2
THREAD_PRIORITY_ABOVE_NORMAL
比正常优先级高1
THREAD_PRIORITY_NORMAL
正常优先级,值为251,创建线程时使用的默认优先级
THREAD_PRIORITY_BELOW_NORMAL
比正常优先级低1
THREAD_PRIORITY_LOWEST
比正常优先级低2
THREAD_PRIORITY_ABOVE_IDLE
比正常优先级低3
THREAD_PRIORITY_IDLE
比正常优先级低4
在Windows CE中,调度程序不使用任何老化算法,这就意味着高优先级的线程总是比低优先级的线程优先被调度,因此可能出现低优先级线程饥饿的现象(长时间不被调度),所以用户需要谨慎设置线程的优先级。相同优先级的线程采用轮换(Round-Robin)算法来调度,一旦线程时间片用完或者因为等待资源而被阻塞,都会被唤出。只有当优先级较高的线程都被阻塞的时候,一个优先级比较低的线程才能被调度执行,这听起来好像比较低效,但其实这是针对Windows CE的特殊环境而设计的,因为线程在多数情况下总是被阻塞的,为了等待一些资源(如等待同步对象)。
另外Windows CE采用抢占式的调度策略,就是说一旦系统新创建一个更高优先级的线程或者某个更高优先级的线程从阻塞中返回,那么调度器会立即将当前的低优先级的线程挂起,调度高优先级的线程运行。
在大多数情况下,线程的优先级是保持不变的(没有老化),除非用户进行显示设置。但是有一个例外,当一个低优先级的线程占有一个高优先级等待请求的资源,那么内核就会通过优先级继承的方式临时将低优先级的线程继承高优先级线程的优先级,从而保证它以较高的优先级运行,直到它释放所占用的资源,才恢复回它原来的优先级,这样高优先级线程就能抢占执行,这称为优先级反转。
6.2.2 创建线程
在Windows CE中,创建线程的函数是CreateThread(),线程将在调用进程的地址空间内运行,这个函数的原型如下:
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpsa,
DWORD cbStack,
LPTHREAD_START_ROUTINE lpStartAddr,
LPVOID lpvThreadParam,
DWORD fdwCreate,
LPDWORD lpIDThread
);
l 参数lpsa指定线程的安全属性,在Windows CE中不支持,必须设置为NULL。
l 参数cbStack指定为新创建线程保留的虚拟内存大小,这个参数实际上指定了线程栈能够增长的最大空间。参数cbStack只有在当fdwCreate参数的STACK_SIZE_PARAM_IS_A_RESERVATION标记被设置时才有效;否则将被忽略,此时线程的将使用默认的栈大小。
l 参数lpStartAddr指定线程启动函数的入口地址。
l 参数lpvThreadParam为指向传递给线程函数的参数的指针,可以传递任意类型的参数,一般的用法是将线程函数需要的多个参数放到一个自定义的结构体内,再将lpvThreadParam赋值为指向参数结构体的指针。
l 参数fdwCreate指定创建线程的标记,可以取的值为0,CREATE_SUSPENDED和STACK_SIZE_PARAM_IS_A_RESERVATION。如果CREATE_SUSPENDED标记被设置,那么线程将被创建为挂起状态,直到对应该线程的ResumeThread()函数被调用,这个线程才能恢复运行;否则线程将在被创建好后直接运行。默认为新创建线程保留的栈大小为64KB,如果用户希望增加线程可用的最大栈大小,就设置STACK_SIZE_PARAM_IS_A_RESERVATION标记,并通过cbStack参数指定栈的大小。
l 参数lpIDThread为输出参数,用于返回新创建线程的标识(ID)。如果用户不需要获取线程标识,可以将这个参数设置为NULL。
如果创建线程成功,这个函数将返回新创建线程的句柄,否则返回NULL。返回的线程句柄被创建为THREAD_ALL_ACCESS,使用这个句柄可以对线程进行操作。一旦不再使用线程句柄,就应该将其关闭。
新创建的线程从传递进来的线程函数的入口地址开始执行(属于进程的地址空间),可以认为线程函数用于指定任务,线程函数的原型必须为这样:
DWORD WINAPI ThreadProc(
LPVOID lpParameter
);
l 参数lpParameter为指向传递给线程函数的参数的指针。注意不要给线程函数传递一个分配在栈上的结构体的指针,因为栈上的内容会在CreateThread()返回后被回收,导致线程无法访问。
对线程函数,返回非0表示执行成功,返回0表示失败。通过调用GetExitCodeThread()函数可以获取线程函数的返回值(如果其作为线程的退出码),函数原型如下:
BOOL GetExitCodeThread(
HANDLE hThread,
LPDWORD lpExitCode
);
l 参数hThread指定线程的句柄。
l 参数lpExitCode为输出参数,返回线程的退出码。
如果函数调用成功,返回非0(TRUE),否则返回0(FALSE)。如果线程仍在运行中,还没有终止,那么lpExitCode中将返回STILL_ACTIVE。如果线程已经终止,线程的退出码可能为如下情况:(1)如果线程是被ExitThread()或TerminateThread()终止,将为这两个函数指定的退出值; (2)如果线程由于线程函数返回被终止,为线程函数的返回值;(3)如果线程由于所属进程被终止而被终止,那么此时为进程的退出码。
从线程退出码的情况,读者应该能够理解线程被终止的方式,下面分别介绍ExitThread()和TerminateThread()函数。ExitThread()函数用于在线程内部终止自己,它的原型分别为:
VOID ExitThread(
DWORD dwExitCode
);
l 参数dwExitCode指定线程的退出码。
TerminateThread()函数用于终止其它指定的线程,函数原型如下:
BOOL TerminateThread(
HANDLE hThread,
DWORD dwExitCode
);
l 参数hThread指定线程的句柄。
l 参数dwExitCode指定将被终止线程的退出码。
如果终止线程成功,返回非0,否则返回0。TerminateThread()是一个比较危险的函数,因为在目标线程退出时无法执行用户态的代码(比如析构函数),只有在极端情况下才使用。
如果在创建线程时指定一个无效的入口地址,将导致抛出异常,线程将被终止。
6 2.3 设置线程的优先级
在Windows CE中,设置一般程序的线程优先级使用SetThreadPriority()函数:
BOOL SetThreadPriority(
HANDLE hThread,
int nPriority
);
l 参数hThread指定线程句柄
l 参数nPriority指定设置的线程优先级值,可以表6-2列出的值之一。
如果设置线程优先级成功,函数返回非0,否则返回0。在Windows CE中默认新创建线程的优先级为THREAD_PRIORITY_NORMAL(251),不到万不得已,不要随意更改线程的优先级,避免高优先级的线程总是占用CPU事件,导致系统响应变得迟缓。
而CeSetThreadPriority()函数则用于设置实时线程的优先级:
BOOL CeSetThreadPriority(
HANDLE hThread,
int nPriority
);
这里的参数nPriority可以取0 ~ 255中的任意值,Windows CE还提供了下面的几个常量优先级可供使用,注意不要跟表6-2中的值混淆:
· CE_THREAD_PRIO_256_TIME_CRITICAL
· CE_THREAD_PRIO_256_HIGHEST
· CE_THREAD_PRIO_256_ABOVE_NORMAL
· CE_THREAD_PRIO_256_NORMAL
· CE_THREAD_PRIO_256_BELOW_NORMAL
· CE_THREAD_PRIO_256_LOWEST
· CE_THREAD_PRIO_256_ABOVE_IDLE
· CE_THREAD_PRIO_256_IDLE
如果设置优先级的线程当前正在被阻塞,那么提高优先级,将马上生效,而降低优先级只能等到线程从阻塞中返回后才生效。
下面的例子演示如何创建多个线程,并设置线程的优先级,程序的运行界面如图6.2所示。
在这个程序中,我们将创建4个线程,左边的4个组合框用于选择每个线程的优先级,这里我们只提供Normal和Below_Normal两种优先级,切记谨慎将线程优先级设置成Normal之上,很容易导致系统无法响应。右边按钮”Start”用于启动线程,”Stop”按钮终止线程的执行。这里的线程函数很简单,不断对一个计数器加一,通过计数器最终的值,我们能够推断出线程被调度的频次。
图6.2 设置线程优先级的运行结果
对话框类的头文件为:
// SetThreadPriorityDlg.h : header file
//
#pragma once
#include "afxwin.h"
// CSetThreadPriorityDlg dialog
class CSetThreadPriorityDlg : public CDialog
{
// Construction
public:
CSetThreadPriorityDlg(CWnd* pParent = NULL); // standard constructor
// Dialog Data
enum { IDD = IDD_SETTHREADPRIORITY_DIALOG };
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
// Implementation
protected:
HICON m_hIcon;
// Generated message map functions
virtual BOOL OnInitDialog();
#if defined(_DEVICE_RESOLUTION_AWARE) && !defined(WIN32_PLATFORM_WFSP)
afx_msg void OnSize(UINT /*nType*/, int /*cx*/, int /*cy*/);
#endif
DECLARE_MESSAGE_MAP()
public:
CComboBox m_ComboThread1;
CComboBox m_ComboThread2;
CComboBox m_ComboThread3;
CComboBox m_ComboThread4;
CButton m_ButtonStart;
CButton m_ButtonStop;
afx_msg void OnBnClickedButtonStart();
afx_msg void OnBnClickedButtonStop();
static DWORD ThreadProc(PVOID pArg);
protected:
int m_threadID[4];
HANDLE m_hThread[4];
int m_ListThreadPriorities[3];
public:
CEdit m_EditOutput;
afx_msg void OnCbnSelchangeComboThread1();
};
其中线程函数为ThreadProc(),这里设置为静态函数:
public:\
static DWORD ThreadProc(PVOID pArg);
添加三个辅助变量:m_threadID为线程的ID数组,通过将其传入线程函数,实现每个线程增加对应的计数器;m_hThread用于存放新建的4个线程的句柄;m_ListThreadPriorities存放线程的优先级,根据用户的选择来分别设置每个线程的优先级。
protected:
int m_threadID[4];
HANDLE m_hThread[4];
int m_ListThreadPriorities[3];
接着在对话框类的开始位置定义连个全局变量:g_stopRunning为标识,如果这个值被设置为FALSE,那么线程将退出,后面我们将看到,线程函数使用这个标识作为判断是否循环结束;g_threadCount为每个线程的计数值,值越大,说明线程被调度得越多。
bool g_stopRunning;
__int64 g_threadCount[4];
在对话框的初始化函数中为每个组合框进行初始化:
BOOL CSetThreadPriorityDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// Set the icon for this dialog. The framework does this automatically
// when the application's main window is not a dialog
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon
// TODO: Add extra initialization here
//添加线程优先级选项
//注意:这里如果允许设置Above_Normal优先级,系统将很可能进入无法响应状态
m_ComboThread1.AddString(_T("Normal")); //Normal优先级
m_ComboThread1.AddString(_T("Below_Normal")); //低于Normal
//m_ComboThread1.AddString(_T("Above_Normal")); //高于Normal
m_ComboThread1.SetCurSel(0);
m_ComboThread2.AddString(_T("Normal")); //Normal优先级
m_ComboThread2.AddString(_T("Below_Normal")); //低于Normal
//m_ComboThread2.AddString(_T("Above_Normal")); //高于Normal
m_ComboThread2.SetCurSel(0);
m_ComboThread3.AddString(_T("Normal")); //Normal优先级
m_ComboThread3.AddString(_T("Below_Normal")); //低于Normal
//m_ComboThread3.AddString(_T("Above_Normal")); //高于Normal
m_ComboThread3.SetCurSel(0);
m_ComboThread4.AddString(_T("Normal")); //Normal优先级
m_ComboThread4.AddString(_T("Below_Normal")); //低于Normal
//m_ComboThread4.AddString(_T("Above_Normal")); //高于Normal
m_ComboThread4.SetCurSel(0);
//辅助线程优先级数组,根据列表框的下标快速获取优先级值
m_ListThreadPriorities[0] = THREAD_PRIORITY_NORMAL;
m_ListThreadPriorities[1] = THREAD_PRIORITY_BELOW_NORMAL;
//m_ListThreadPriorities[2] = THREAD_PRIORITY_ABOVE_NORMAL;
m_ButtonStart.EnableWindow();
m_ButtonStop.EnableWindow(false);
g_stopRunning = false;
int i;
for(i = 0; i < 4; i++)
{
g_threadCount[i] = 0;
m_threadID[i] = i;
}
return TRUE; // return TRUE unless you set the focus to a control
}
线程函数的实现比较简单,判断g_stopRunning是否为TRUE,如果是,线程退出;否则不断增加自己负责的计数值:
DWORD CSetThreadPriorityDlg::ThreadProc(PVOID pArg)
{
//获取线程标识,用于更新指定下标的计数
int tid = *((int*)pArg);
//不断计数,直到线程停止标识被设置为true
while(!g_stopRunning)
{
g_threadCount[tid]++;
}
return tid;
}
下面是”Start”按钮的单击事件处理函数,首先将g_stopRunning设置为false,然后创建线程:
void CSetThreadPriorityDlg::OnBnClickedButtonStart()
{
// TODO: Add your control notification handler code here
//禁止Start按钮,启用Stop按钮
m_ButtonStart.EnableWindow(false);
m_ButtonStop.EnableWindow(true);
//设置终止线程运行标识为false
g_stopRunning = false;
//初始化每个线程对应的计数器为0
int i;
for(i = 0; i < 4; i++)
{
g_threadCount[i] = 0;
}
int successThreadNum = 0;
//创建4个线程,分别开始计数
for( i = 0; i < 4; i++ )
{
m_hThread[i] = CreateThread(
NULL, //不支持安全属性
0, //线程使用默认栈大小
ThreadProc, //线程入口函数
&m_threadID[i], //传递给线程的参数,这里指定为操作的下标
CREATE_SUSPENDED, //挂起线程,将在设置线程优先级之后,再启动线程
NULL); //不需要获取线程标识
if(!m_hThread[i])
{
CString failedMsg;
failedMsg.Format(_T("Thread %d create failed"),i);
AfxMessageBox(failedMsg);
}
else{
successThreadNum++;
}
}
//为每个线程设置指定的优先级,需要判断线程是否创建成功
if(m_hThread[0])
{
SetThreadPriority(m_hThread[0], m_ListThreadPriorities[m_ComboThread1.GetCurSel()]);
}
if(m_hThread[1])
{
SetThreadPriority(m_hThread[1], m_ListThreadPriorities[m_ComboThread2.GetCurSel()]);
}
if(m_hThread[2])
{
SetThreadPriority(m_hThread[2], m_ListThreadPriorities[m_ComboThread3.GetCurSel()]);
}
if(m_hThread[3])
{
SetThreadPriority(m_hThread[3], m_ListThreadPriorities[m_ComboThread4.GetCurSel()]);
}
//正式启动每个线程开始运行
for( i = 0; i < 4; i++ )
{
if(m_hThread[i])
{
ResumeThread(m_hThread[i]);
}
}
CString runMsg;
runMsg.Format(_T("%d threads running..."),successThreadNum);
m_EditOutput.SetWindowTextW(runMsg);
}
注意在调用CreateThread()函数创建线程时,我们指定为挂起状态,这样保证线程从开始就以指定的优先级运行:
m_hThread[i] = CreateThread(
NULL, //不支持安全属性
0, //线程使用默认栈大小
ThreadProc, //线程入口函数
&m_threadID[i], //传递给线程的参数,这里指定为操作的下标
CREATE_SUSPENDED, //挂起线程,将在设置线程优先级之后,再启动线程
NULL); //不需要获取线程标识
接着就可以调用SetThreadPriority()函数为每个线程设置优先级,为了防止线程创建失败,我们都进行判断线程句柄是否创建成功,如对0号线程:
if(m_hThread[0])
{
SetThreadPriority(m_hThread[0],
m_ListThreadPriorities[m_ComboThread1.GetCurSel()]);
}
设置好线程的优先级属性后就可以启动线程了,调用ResumeThread()函数实现:
//正式启动每个线程开始运行
for( i = 0; i < 4; i++ )
{
if(m_hThread[i])
{
ResumeThread(m_hThread[i]);
}
}
“Stop”按钮负责终止线程的执行,这通过将g_stopRunning设置为true实现,然后将每个线程的计数值输出到右下方的文本框中,最后记住调用CloseHandle()关闭每个线程句柄:
void CSetThreadPriorityDlg::OnBnClickedButtonStop()
{
// TODO: Add your control notification handler code here
g_stopRunning = true; //终止线程运行
Sleep(50); //等待线程结束
//格式化输出每个不同优先级线程的计数值
CString result = _T("");
int i;
for( i = 0; i < 4; i++)
{
result.Format(_T("%s thread %d count=%I64d;\r\n"),result, i, g_threadCount[i]);
}
m_EditOutput.SetWindowTextW(result);
//关闭线程句柄
for( i = 0; i < 4; i++)
{
CloseHandle(m_hThread[i]);
}
//设置按钮状态
m_ButtonStart.EnableWindow();
m_ButtonStop.EnableWindow(false);
}
图6.2演示的是4个线程都设置成Normal优先级的运行结果,可以看到,四个线程的计数器值几乎一样,说明它们的调度频次是一样的。而图6.3则演示将其中两个线程设置成Normal,另外两个线程设置成Below_Normal的运行结果。可以看到,设置成Below_Normal的线程的计数器为0,说明它们从没有被调度执行过,而两个Normal线程的计数器值几乎相等。
图6.3 线程设置成不同优先级的运行结果
6.2.4 查询线程的优先级别
与设置线程优先级对应,有两个不同的查询线程优先级的方式:GetThreadPriority()函数用于查询一般线程的基本优先级,CeGetThreadPriority()用于查询实时线程的优先级。
int GetThreadPriority(
HANDLE hThread
);
int CeGetThreadPriority(
HANDLE hThread
);
参数hThread指定线程的优先级。如果查询线程优先级成功,函数返回指定线程的优先级,如果失败将返回THREAD_PRIORITY_ERROR_RETURN,通过调用GetLastError()函数进一步获取错误信息。如果线程因为优先级反转而导致优先级临时变化,那么此时查询得到的优先级将与之前默认得到或设置的优先级别不同。