Windows 编程/多任务
当前版本的 Windows 是多任务操作系统。在本章中,我们将讨论一些与多任务、线程和同步相关的工具和 API 函数,这些函数可用于 Windows。
首先,让我们解释一下术语。一个进程是一个单一的程序,具有单个入口点和单个出口点。一个线程是进程的一部分。一个进程至少有 1 个线程;但可以有多个线程。创建时,进程和线程会自动运行,并且由调度程序以循环方式分配执行时间片段。操作系统可以随时激活和停用任何线程或进程。因此,我们需要控制对程序资源的访问,例如全局内存和输出设备。
如果多个进程一起工作,则生成的组被称为作业。作业也可以由 Windows 管理。
- CreateProcess 等
在处理线程时,会使用一些函数,例如CreateThread
、ResumeThread
、SuspendThread
和TerminateThread
。
如果线程的易变性令人不安,Windows 还提供了一个名为纤程的执行对象,它只在父线程激活时运行。
CreateThread
函数接受几个参数
- 指向要在线程中执行的函数的指针。
- 指向要传递给线程函数的变量的指针。
CreateThread 函数为进程创建一个新线程。创建线程必须指定新线程要执行的代码的起始地址。通常,起始地址是程序代码中定义的函数的名称。此函数接受单个参数并返回 DWORD 值。一个进程可以拥有多个线程同时执行相同的函数。
以下示例演示如何创建一个执行本地定义的函数 ThreadFunc 的新线程。
DWORD WINAPI ThreadFunc( LPVOID lpParam )
{
char szMsg[80];
wsprintf( szMsg, "ThreadFunc: Parameter = %d\n", *lpParam );
MessageBox( NULL, szMsg, "Thread created.", MB_OK );
return 0;
}
VOID main( VOID )
{
DWORD dwThreadId, dwThrdParam = 1;
HANDLE hThread;
hThread = CreateThread(
NULL, // no security attributes
0, // use default stack size
ThreadFunc, // thread function
&dwThrdParam, // argument to thread function
0, // use default creation flags
&dwThreadId); // returns the thread identifier
// Check the return value for success.
if (hThread == NULL)
ErrorExit( "CreateThread failed." );
CloseHandle( hThread );
}
为简单起见,此示例将指向 DWORD 值的指针作为参数传递给线程函数。这可以是指向任何类型的数据或结构的指针,也可以通过传递 NULL 指针并在 ThreadFunc 中删除对参数的引用来完全省略。如果创建线程在新的线程退出之前退出,传递局部变量的地址是有风险的,因为指针将变得无效。相反,要么传递指向动态分配的内存的指针,要么让创建线程等待新线程终止。数据也可以使用全局变量从创建线程传递到新线程。对于全局变量,通常有必要通过多个线程同步访问。
单个进程可以(通常)生成 2000 个线程。这是因为链接器分配的默认堆栈大小为每个线程 1MB。1MB x 2000 大约是 2GB,这是用户进程可以访问的最大值。以下是生成许多线程直到达到限制的示例代码
// Threads.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <process.h>
#include <conio.h>
#include <windows.h>
unsigned int __stdcall myThread (LPVOID params) {
printf ("Inside thread");
Sleep (INFINITE);
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
int i = 0;
unsigned int dwThreadId = 0;
while (true) {
HANDLE h = (HANDLE) _beginthreadex (NULL, 0, myThread, NULL, 0, &dwThreadId);
if (h == NULL) break;
++i;
}
printf ("\n\nI spawned %d threads", i);
Sleep (10000);
return 0;
}
线程的命名,一项特殊功能,仅适用于少数 Windows 调试器,对于调试线程非常有用,尤其是在调试具有大量线程的程序时。它包括生成特殊的运行时异常 (0x406D1388),这使程序能够将线程的名称传递给调试器。
//! NameThread - Names a threads for the debugger.
//!
//! Usage: NameThread( -1, "MyThreadIsNowNamed" );
//!
void NameThread( unsigned long a_ThreadID, char* a_ThreadName )
{
typedef struct tagTHREADNAME_INFO
{
unsigned long m_Type;// must be 0x1000
char* m_Name;// pointer to name (in user addr space)
unsigned long m_ThreadID;// thread ID (-1=caller thread)
unsigned long m_Flags;// reserved, must be zero
} THREADNAME_INFO;
THREADNAME_INFO info;
info.m_Type = 0x1000;
info.m_Name = a_ThreadName;
info.m_ThreadID = a_ThreadID;
info.m_Flags = 0;
__try
{
RaiseException( 0x406D1388, 0, sizeof(info)/sizeof(unsigned long),
(unsigned long*) &info );
}
__except( EXCEPTION_CONTINUE_EXECUTION )
{
}
}