线程由以下两部分构成。
进程是惰性的。从来不执行任何东西,它只是一个线程的容器。线程必然是某个进程的上下文中创建的,而且会在这个进程内部“终其一生”。这意味着线程要在其进程的地址空间内执行代码和处理数据。假如一个进程上下文中有两个以上的线程运行,这些线程将共享同一个地址空间。这些线程可以执行同样的代码,可以处理相同的数据。这些线程还共享内核对象句柄,因为句柄表示针对每一个进程的,而不是针对线程。
调用CreateThread时,系统会创建一个线程内核对象。这个线程内核对象不是线程本身,而是一个较小的数据结构,操作系统用这个结构来管理线程。可以把线程内核对象想象为一个由线程统计信息构成的小型数据结构。这与进程和进程内核对象之间的关系是相同的。
HANDLE CreateThread(
[in, optional] LPSECURITY_ATTRIBUTES lpThreadAttributes,
[in] SIZE_T dwStackSize,
[in] LPTHREAD_START_ROUTINE lpStartAddress,
[in, optional] __drv_aliasesMem LPVOID lpParameter,
[in] DWORD dwCreationFlags,
[out, optional] LPDWORD lpThreadId
);
示例:在线程中做加法。
DWORD WINAPI threadFun(PVOID pvParm)
{
int i = 0;
int x = *((int*)pvParm);
i += x;
printf("i = %d\n", i);
return 0;
}
DWORD threadID;
int x = 2;
HANDLE hThread = CreateThread(NULL, 0, threadFun, (PVOID)&x, 0, &threadID);
有以下4中方式终止:
3.1线程函数返回
让线程函数返回,可以确保以下正确的应用程序清理工作都得以执行。
3.2ExitThread函数
为了强迫线程终止运行,可以调用ExitThread。
该函数终止线程的运行,并导致操作系统清理该线程使用的所有操作系统资源。但是C/C++资源不会被销毁。和ExitProcess函数一样。
3.3TerminateThread函数
不同于ExitThread总是杀死主调线程,TerminateThread能杀死任何线程。
3.4包含线程的进程终止运行
ExitProcess和TerminateProcess可用于终止线程的运行。这些函数会使终止运行的进程中的所有线程全部终止。同时,由于整个进程都会关闭,所以它使用的所有资源肯定会被清理。包括线程的堆栈。但是C++对象析构函数不会被调用等。
如果你正在编写C/C++代码,决不应该调用CreateThread。相反应该使用_beginthreadex,退出也应该使用_endthreadex。
原因:首先要从标准C运行库与多线程的矛盾说起,标准C运行库在1970年被实现了,由于当时没任何一个操作系统提供对多线程的支持。因此编写标准C运行库的程序员根本没考虑多线程程序使用标准C运行库的情况。
比如标准C运行库的全局变量errno,很多运行库中的函数在出错时会将错误代号赋值给这个全局变量。
if (system("notepad.exe readme.txt") == -1)
{
switch(errno)
{
...//错误处理代码
}
}
假设某个线程A在执行上面的代码,该线程在调用system()之后且尚未调用switch()语句时另外一个线程B启动了,这个线程B也调用了标准C运行库的函数,不幸的是这个函数执行出错了并将错误代号写入全局变量errno中。这样线程A一旦开始执行switch()语句时,它将访问一个被B线程改动了的errno。这种情况必须要加以避免!因为不单单是这一个变量会出问题,其它像strerror()、strtok()、tmpnam()、gmtime()、asctime()等函数也会遇到这种由多个线程访问修改导致的数据覆盖问题。
为了解决这个问题,Windows操作系统提供了这样的一种解决方案——创建了一个数据结构,并使之与使用了C/C++运行库函数的每个线程关联。然后,在调用C/C++运行库函数时,那些函数必须知道去查找主调线程的数据块,从而避免影响到其他线程。而且这块内存区域的创建就是由C/C++运行库函数_beginthreadex()来负责的。
_beginthreadex会在内部调用CreateThread。
在线程内核对象中有一个值表示线程的挂起计数。调用CreateThread时,系统将创建线程内核对象,并把挂起计数初始化为1.这样,就不会给这个线程调度CPU了。
CreateThread函数调用时,查看是否有CREATE_SUSPENDED标志传入。如果有,函数会返回并让新的线程处于挂起状态。如果没有,函数会将线程的挂起计数递减为0。当线程的挂起计数为0时,线程就称为可调度的了,除非它还在等待某个事件的发生(例如键盘输入)。
调用ResumeThread恢复。
ResumeThread成功,返回线程的前一个挂起计数;否则,它将返回0xFFFFFFFF。
一个线程可以被多次挂起,如果一个线程被挂起三次,则在它有资格让系统为它分配CPU之前必须恢复三次。除了在创建线程时使用CREATE_SUSPENDED标志,还可以使用SuspendThread来挂起线程。
线程还可以告诉系统,在一段时间内自己不需要调度了,可以调用Sleep实现。
void Sleep(
[in] DWORD dwMilliseconds
);
这个函数将线程自己挂起dwMilliseconds长的时间,以毫秒为单位。