Windows Via C/C++: CreateThread函数

来源:互联网 发布:java 实参和形参 编辑:程序博客网 时间:2024/05/18 03:42

CreateThread函数

前面讲过,进程的主线程会在CreateProcess调用时自动创建。假如你要手工创建线程,你可以调用CreateThread函数:

HANDLE CreateThread(  PSECURITY_ATTRIBUTES psa,  DWORD cbStackSize,  PTHREAD_START_ROUTINE pfnStartAddr,  PVOID pvParam,  DWORD dwCreateFlags,  PDWORD pdwThreadID);

系统在调用CreateThread时创建一个线程内核对象,线程内核对象并非线程本身,它是操作系统用来管理线程的数据结构,其中包含线程的统计信息。这与进程和进程内核对象的关系是类似的。

系统从进程的地址空间中为线程栈分配空间。新的线程和创建它的线程运行在同样的进程上下文中,因此新线程可以访问进程的句柄表、地址空间以及其它处在同一进程上下文中的所有线程的堆栈,这使得同一进程内的多个线程相互通信变得极为简单。

注意 CreateThread函数是用来创建线程的Windows API函数。假如你使用C/C++代码创建新线程,你应该避免使用CreateThread,而用微软C++运行时库函数_beginthreadex代替。本章稍后会讲到_beginthreadex函数并解释用它代替CreateThread的原因。

下面我们来看看CreateThread的参数。

psa

psa是SECURITY_ATTRIBUTES结构的指针,向其传递NULL表示创建的线程内核对象使用默认的安全属性。如果想让新线程内核对象的句柄能被子进程继承,你应该为其传递一个SECURITY_ATTRIBUTES结构的指针,并将bInheritHandle域设置为TRUE,第3章对此有详细的说明。

cbStackSize

[关于cbStackSize的解释参考了MSDN,与原文有出入,参阅http://msdn.microsoft.com/en-us/library/ms686774%28VS.85%29.aspx ]

每个线程的堆栈由预留(reverse)空间和系统最初为其提交(commiteted)的页面空间组成。预留空间的大小表示系统在进程的虚拟地址空间中为线程栈分配的总大小,因此预留空间的大小受进程地址空间大小的制约。初始提交的页面空间使用时才会载入内存,如果线程运行时所需的空间超过初始提交的页面空间大小,系统会从线程保留空间中为其继续提交页面文件,直到线程的预留空间仅剩下一页大小,这一页作为“哨兵”用来防止线程栈的溢出,不能在程序中使用。

当线程函数退出或调用ExitProcess时,相应的线程栈会被销毁,如果线程是由其它线程终止的,其线程栈会一直存在直至进程终止。

线程预留空间和初始提交页面的大小是在可执行文件的头部定义的。默认的预留空间大小是1MB,初始提交页面大小为8K。手动指定预留空间或提交页面大小时,可以使用链接器选面的/STACK开关,或在应用模块的.def文件中以STACK语句声明。手动指定预留空间大小时,系统实际分配的大小是指定值向上舍入到64KB的最小倍数,比如/STACK值设置为100K,则系统会实际分配64*2=128KB的预留空间。这里的64KB被称为系统分配粒度,可以调用GetSystemInfo获得当前的系统分配粒度大小。

dwStackSize参数用来改变线程初始提交页面的大小,系统实际提交的页面大小是dwStackSize向上舍入到页面大小(通常是4K)的整数倍,dwStackSize通常并不影响线程保留空间的大小,但是如果dwStackSize的值大于线程的预留空间大小,系统将会调整预留空间大小,使之变为dwStackSize向上舍入的1MB的整数倍。

pfnStartAddr和pvParam

pafnStartAddr是指向线程入口点函数的指针,pvParam将传递给线程函数的pvParam参数,该参数提供了一种向线程入口点函数传递初始值的方法,pvParam可以是指向任意数据结构的指针。

你可以为多个线程指定同一个入口点函数,这完全合法并且有时候是非常有用的。比如你可以开发一个WEB服务器并对每个客户端请求都创建一个新线程来处理,这些线程的入口点函数是相同的,但你可以为其传递不同的pvParam参数,以区别不同的客户端请求。

Windows是基于优先级的多任务操作系统,这意味着新线程和创建它的线程可以同时执行,这有时会引发一些问题,比如下面的代码:

DWORD WINAPI FirstThread(PVOID pvParam){  int x = 0;  DOWRD dwThreadId;    HANDLE hThread = CreateThread(NULL, 0, SecondThread, (PVOID)&x, 0, &dwThreadId);  CloseHandle(hThread);  return 0;}DWORD WINAPI SecondThread(PVOID pvParam){  *((int *)pvParam) = 5;  return 0;}

线程函数FirstThread创建了线程hThread,并将其局部变量x的地址传递给hThread的入口点函数SecondThread,接着FirstThread返回,系统会销毁FirstThread所属线程的线程栈,包括其中的局部变量x。新线程在其入口点函数SecondThread中试图引用pvParam既局部变量x,并向其赋值时,x可能已经被销毁,这时会产生内存读写错误。为了解决这一问题,可以将x声明为static类型,static类型的变量位于程序的数据区而非线程栈上,声明它的线程结束时也不会被销毁。然而,在发多线程同时读写同一静态/全局变量时,可能会引起脏读等一系列共享问题。第8章和第9章将通过线程/内核对象同步技术来解决这一问题。

dwCreateFlags

dwCreateFlags可以指定创建线程时的附加参数,它可以取0或CREATE_SUSPENDED,取0时线程创建后会被马上调度,否则,线程创建完成后会挂起。

pdwThreadID

pdwThreadID是DWORD类型的指针,它用返回新创建的线程的ID,可以向其传递NULL。