创建一个线程

来源:互联网 发布:linux nfs配置 编辑:程序博客网 时间:2024/05/21 19:46

主线程:

         主线程是属于线程中比较重要的线程了。每个进程有且只有一个线程。

在每次初始化一个进程的时候,系统都会自动的创建一个主线程。对于使用vs编译器生成的应用程序,这个线程首先会执行C/C++运行库的启动代码,这个启动代码又会调用我们的入口函数(main函数或者winmain函数),直到入口函数运行完毕,调用ExitProcess函数结束应用程序。

默认情况下,主线程的入口函数必须为main,wmain,WinMain或者wWinMain,但是我们也可以通过指定/ENTRY:链接器选项来指定另一个函数作为入口点的函数

 

Windows 的线程API:

CreateThread 创建线程

函数原型

 HANDLE CreateThread(

PSECURITY_ATTRIBUTES                        psa,

DWORD                                             cbStackSize,

PTHREAD_START_ROUTINE          pfnStartAddr,

PVOID                                                 pvParam,

DWORD                                             dwCreateFlags,

PDWORD                                                    pdwThreadID);


psa参数:

         psa参数指向一个SECURITY_ATTRIBUTES结构的指针,该参数指定了函数的安全属性。

如果想使用线程内核对象的默认安全属性,可以向此参数传入NULL。如果希望所以子进程都继承这个线程对象的句柄,必须指定一个SECURITY_ATTRIBUTES结构,并将此结构的bInheritHandle成员初始化为TRUE.

 

cbStackSize 参数:

         cbStackSizie参数指定线程可以指定此线程本身的线程栈大小(有多少地址空间),线程栈设置了大小,这样才能捕获代码中的无穷递归BUG

 

PfnStartAddr pvParam参数:

         PfnStartAddr参数指定希望新线程执行的线程函数的地址。pvParam参数则为线程函数的实参,只是这个参数是一个void*类型的指针,通过这个指针,我们可以传入一个数值,或者一个结构体等等。

         需要注意的一点是,windows是一个抢占式的多线程系统,这意味着新的线程和调用CreateThread函数的线程可以同时执行。因为两个线程是同时运行的,所以可能出现问题

,这个时候一般可以通过正确的使用线程同步技术解决。

 

dwCreateFlags参数:

         dwCreateFlags参数指定线程的创建方式,它可以是两个值之一,如果值为0,线程创建后就立刻进行调度。如果值为CREATE_SUSPENDED标识,系统将创建并初始化线程,但是会暂停线程的允许,直到你唤醒线程。

 

pdwThreadID参数:

         pdwThreadID是用来存储系统分配给线程的ID,如果想获取线程的值,此指针必须不能为空,反之,应当为NULL

 回调函数原型:

         DWORD  WINAPI  FunctionName (PVOID  pvParam);

C++运行库的线程API

此函数在process.h头文件中被定义

函数原型

unsigned long_beginthreadex(

                                  void*              security,

                                  unsigned         stack_size,

                                  unsigned         (*start_address)(void*),

                                  void*               arglist,

                                  unsigned         initflag,

                                  unsigned*       thrdaddr

);

回调函数原型:

                  unsigned   __stdcall   FunctionName (void* pParam);

_beginthreadex函数的参数列表与CreateThread函数完全一样,但是参数名称和类型并不完全一样,这是因为C++运行库函数对windows数据类型完全不依赖。正是因为如此,所以一般情况下需要将函数的返回值强制转换为HANDLE.

那么既然有了API函数为什么还需要运行库函数呢?

原因是在设计运行库的时候,所有的操作系统都没有支持多线程编程,因此开发者根本没有考虑多线程开发时运行库会产生的BUG

例如在使用_errno之类的全局变量,有的函数会在出错时设置该变量,当一个线程中的函数出错设置了此变量,而并没有及时的处理便进入了另一个线程,而这个线程刚好检测了这个变量,此时变量反映的就不是他应该拥有的值,为了解决这个问题必须让每一个线程都有一份独立的地方存放存放这些值。

我们通过查看运行库代码来知道(运行库附有源代码),在_beginthreadex函数被调用后,它有创建一个名为tiddata的数据结构,这个数据结构就是用来解决这个问题的

这样处理后大部分情况下都不会有异常了,但是还有个别问题:

1.     如果某个线程使用了signed函数则整个进程都会结束,因为SHE没有就绪。

2.     如果线程不通过_endthreadex来终止,tiddata数据块就不会被销毁,导致内存泄漏。

PS:包括《WINDOWS核心编程》在内的很多书籍都强烈建议使用_beginthreadex而不是CreateThread来创建函数

 

除此之外运行库还包括_beginthread函数:

unsigned long _beginthread(

                          void(_cdecl *start_address)(void*),

                          unsigned                  stack_size,

                          void*                        arglist

)

这个函数参数比较少,因此功能就比较少,而在《windows核心编程》还提出了一个关于它的结束函数_endthread()函数的问题,详情可以参阅《windows核心编程》中的解释。


eg:

#include<iostream>#include<Windows.h>#include<process.h>DWORD WINAPI WinCallBack(LPVOID pParam){std::cout << "这是一个通过Windows API创建的线程" << std::endl;return 0;}unsigned __stdcall CCallBack(void *pParam){std::cout << "这是一个通过C/C++运行库API创建的线程" << std::endl;return 0;}int main(){HANDLE hHandle[2] = { 0 };/*使用默认安全属性,默认栈空间,WinCallBack这个回掉函数,不传入参数,创建后线程开始调度,不需要线程id*/hHandle[0] = CreateThread(NULL, 0, WinCallBack, NULL, 0, NULL);hHandle[1] = (HANDLE) _beginthreadex(NULL, 0, CCallBack, NULL, 0, NULL);//这是一个等待函数,等待上面的两个线程运行完毕WaitForMultipleObjects(2, hHandle, true, INFINITE);if (0 != GetLastError()){std::cout << GetLastError() << std::endl;;}return 0;}


1 0
原创粉丝点击