-多线程-Windows多线程编程API及比较

 AfxBeginThread、BeginThread和BeginThreadex实际上是编译器对CreateThread的封装。

一、CreateThread:

  Windows的API函数(SDK函数的标准形式,直截了当的创建方式,任何场合都可以使用),提供操作系统级别的创建线程的操作,且仅限于工作者线程。

//@线程创建成功返回新线程的句柄,失败返回NULL
    HANDLE WINAPI CreateThread(
//表示线程内核对象的安全属性,一般传入NULL表示使用默认设置
      _In_opt_  LPSECURITY_ATTRIBUTES  lpThreadAttributes,   
//表示线程栈空间大小。传入0表示使用默认大小(1MB)
      _In_      SIZE_T                 dwStackSize,
//表示新线程所执行的线程函数地址,多个线程可以使用同一个函数地址。
      _In_      LPTHREAD_START_ROUTINE lpStartAddress,
//是传给线程函数的参数
      _In_opt_  LPVOID                 lpParameter,
//指定额外的标志来控制线程的创建,为0表示线程创建之后立即就可以进行调度,如果为CREATE_SUSPENDED则表示线程创建后暂停运行,这样它就无法调度,直到调用ResumeThread()。
      _In_      DWORD                  dwCreationFlags,
//将返回线程的ID号,传入NULL表示不需要返回该线程ID号。
      _Out_opt_ LPDWORD                lpThreadId
    );

 

 

/* 创建第一个线程。主进程结束,则撤销线程。 */
#include<Windows.h>
#include<stdio.h>
DWORD WINAPI ThreadFunc(LPVOID);

void main()
{
    HANDLE hThread;
    DWORD  threadId;
    hThread = CreateThread(NULL, 0, ThreadFunc, 0, 0, &threadId); // 创建线程
    printf("我是主线程, pid = %d\n", GetCurrentThreadId());  //输出主线程pid
    Sleep(2000);
}

DWORD WINAPI ThreadFunc(LPVOID p)
{   
    printf("我是子线程, pid = %d\n", GetCurrentThreadId());   //输出子线程pid
    return 0;
}

 

  在实际使用中尽量使用_beginthreadex()来创建线程,在博客中使用 CreateThread()l来创建线程其实是一种不太好的方法,不过这里只做原理分析,不用在实际项目中。

 二、WaitForSingleObject / WaitForMultipleObjects

  需要等待某一线程完成了特定的操作后再继续做其他事情,要实现这个目的,可以使用Windows API函数WaitForSingleObject,或者WaitForMultipleObjects。这两个函数都会等待Object被标为有信号(signaled)时才返回。只要是Windows创建的Object都会被赋予一个状态量。如果Object被激活了,或者正在使用,那么该Object就是无信号,也就是不可用;另一方面,如果Object可用了,那么它就恢复有信号了。

 

/*返回值:
WAIT_ABANDONED 0x00000080:当hHandle为mutex时,如果拥有mutex的线程在结束时没有释放核心对象会引发此返回值。
WAIT_OBJECT_0 0x00000000 :指定的对象出有有信号状态
WAIT_TIMEOUT 0x00000102:等待超时
WAIT_FAILED 0xFFFFFFFF :出现错误,可通过GetLastError得到错误代码
*/
DWORD WINAPI WaitForSingleObject(
/*参数1:
对象的句柄,可以是以下几种:
Change notification
Console input
Event
Memory resource notification
Mutex
Process
Semaphore
Thread
Waitable timer
*/
    _In_ HANDLE hHandle,
/*参数2:
等待时间,以毫秒为单位。参数dwMilliseconds有两个具有特殊意义的值:0和INFINITE。若为0,则该函数立即返回;若为INFINITE,则线程一直被挂起,直到hHandle所指向的对象变为有信号状态时为止。
*/
    _In_ DWORD dwMilliseconds
    );

 

//@返回值类型同上
DWORD WINAPI WaitForMultipleObjects(
//为等待的内核对象个数,可以是0到MAXIMUM_WAIT_OBJECTS(64)中的一个值
  _In_       DWORD   nCount,
//为一个存放被等待的内核对象句柄的数组
  _In_ const HANDLE  *lpHandles,
//是否等到所有内核对象为已通知状态后才返回,如果为TRUE,则只有当等待的所有内核对象为已通知状态时函数才返回,如果为FALSE,则只要一个内核对象为已通知状态,则该函数返回。
  _In_       BOOL    bWaitAll,
//为等待时间,和WaitForSingleObject中的dwMilliseconds参数类似
  _In_       DWORD   dwMilliseconds
);

 

、beginthread / beginthreadex:

  MS对C-Runtime库的扩展SDK函数,首先针对C Runtime库做了一些初始化的工作,以保证C Runtime库工作正常,然后,调用CreateThread真正创建线程。beginthread是_beginthreadex的功能子集,虽然_beginthread内部是调用_beginthreadex但他屏蔽了象安全特性这样的功能,例如,如果使用_beginthread,就无法创建带有安全属性的新线程,无法创建暂停的线程,也无法获得线程的ID值。_beginthread与CreateThread不是同等级别,_beginthreadex和CreateThread在功能上完全可替代 。

 

注意:尽量不要调用CreateThread。相反,应该使用Visual C++运行期库函数_beginthreadex,原因如下:  考虑标准C运行时库的一些变量和函数,如errno,这是一个全局变量。必须存在一种机制,使得每个线程能够引用它自己的errno变量,又不触及另一线程的errno变量._beginthreadex就为每个线程分配自己的tiddata内存结构。该结构保存了许多像errno这样的变量和函数的值、地址,通过线程局部存储将tiddata与线程联系起来。具体实现在Threadex.c中有,结束线程使用函数_endthreadex函数,释放掉线程的tiddata数据块。对于线程的支持是后来的事! 这也导致了许多CRT的函数在多线程的情况下必须有特殊的支持,不能简单的使用CreateThread。

  CRT的函数库在线程出现之前就已经存在,所以原有的CRT不能真正支持线程,这导致我们在编程的时候有了CRT库的选择,在MSDN中查阅CRT的函数时都有:   

  Libraries   
  LIBC.LIB   Single   thread   static   library,   retail   version     
  LIBCMT.LIB   Multithread   static   library,   retail   version     
  MSVCRT.LIB   Import   library   for   MSVCRT.DLL,   retail   version     
  大多的CRT函数都可以在CreateThread线程中使用,看资料说只有signal()函数不可以,会导致进程终止!但可以用并不是说没有问题!   
  有些CRT的函数象malloc(),   fopen(),   _open(),   strtok(),   ctime(),   或localtime()等函数需要专门的线程局部存储的数据块,这个数据块通常需要在创建线程的时候就建立,如果使用CreateThread,这个数据块就没有建立,然后会怎样呢?在这样的线程中还是可以使用这些函数而且没有出错,实际上函数发现这个数据块的指针为空时,会自己建立一个,然后将其与线程联系在一起,这意味着如果你用CreateThread来创建线程,然后使用这样的函数,会有一块内存在不知不觉中创建,遗憾的是,这些函数并不将其删除,而CreateThread和ExitThread也无法知道这件事,于是就会有Memory leak,在线程频繁启动的软件中(比如某些服务器软件),迟早会让系统的内存资源耗尽!   
  _beginthreadex和_endthreadex就对这个内存块做了处理,所以没有问题!(不会有人故意用CreateThread创建然后用_endthreadex终止吧,而且线程的终止最好不要显式的调用终止函数,自然退出最好!)   如果在除主线程之外的任何线程中进行一下操作,你就应该使用多线程版本的C runtime library,并使用_beginthreadex和_endthreadex: 

1 使用malloc()和free(),或是new和delete 
2 使用stdio.h或io.h里面声明的任何函数 
3 使用浮点变量或浮点运算函数 
4 调用任何一个使用了静态缓冲区的runtime函数,比如:asctime(),strtok()或rand() 
Handle的问题,_beginthread的对应函数_endthread自动的调用了CloseHandle,而_beginthreadex的对应函数_endthreadex则没有,所以CloseHandle无论如何都是要调用的不过_endthread可以帮你执行自己不必写,其他两种就需要自己写!(Jeffrey   Richter强烈推荐尽量不用显式的终止函数,用自然退出的方式,自然退出当然就一定要自己写CloseHandle)

 

//头文件:process.h
//函数原型:
unsigned long _beginthreadex(
//安全属性,NULL为默认安全属性 
void *security, 
//指定线程堆栈的大小。如果为0,则线程堆栈大小和创建它的线程的相同。一般用0 
unsigned stack_size, 
//指定线程函数的地址,也就是线程调用执行的函数地址(用函数名称即可,函数名称就表示地址,注意的是函数访问方式一定是__stdcall,函数返回值一定是unsigned,函数参数一定是void*) 
unsigned ( __stdcall *start_address )( void * ), 
//传递给线程的参数的指针,可以通过传入对象的指针,在线程函数中再转化为对应类的指针 
void *arglist, 
//线程初始状态,0:立即运行;CREATE_SUSPEND:悬挂(如果出事状态定义为悬挂,就要调用ResumeThread(HANDLE) 来激活线程的运行) 
unsigned initflag, 
//用于记录线程ID的地址
unsigned *thrdaddr 
);

 

四、AfxBeginThread:

  MFC中线程创建的MFC函数,它简化了操作或让线程能够响应消息,即可用于界面线程,也可以用于工作者线程,但要注意尽量不要在一个MFC程序中使用_beginthreadex()或CreateThread()。

 

//用户界面线程的AfxBeginThread的原型如下:
CWinThread* AFXAPI AfxBeginThread(
//从CWinThread派生的RUNTIME_CLASS类
  CRuntimeClass* pThreadClass,
//指定线程优先级,如果为0,则与创建该线程的线程相同;
  int nPriority,
//指定线程的堆栈大小,如果为0,则与创建该线程的线程相同;
  UINT nStackSize,
//一个创建标识,如果是CREATE_SUSPENDED,则在悬挂状态创建线程,在线程创建后线程挂起,否则线程在创建后开始线程的执行。
  DWORD dwCreateFlags,
//线程的安全属性,NT下有用
  LPSECURITY_ATTRIBUTES lpSecurityAttrs)

 

 

//工作者线程的AfxBeginThread的原型如下:
//@功时返回一个指向新线程的线程对象的指针,否则NULL
CWinThread* AfxBeginThread(
// 线程的入口函数,声明一定要如下: UINT MyThreadFunction(LPVOID pParam),不能设置为NULL
     AFX_THREADPROC pfnThreadProc,
//传递入线程的参数,注意它的类型为:LPVOID,所以我们可以传递一个结构体入线程.
  LPVOID lParam,
// 线程的优先级,一般设置为 0 .让它和主线程具有共同的优先级.
  int nPriority = THREAD_PRIORITY_NORMAL,
//指定新创建的线程的栈的大小.如果为 0,新创建的线程具有和主线程一样的大小的栈
  UINT nStackSize = 0,
// 指定创建线程以后,线程有怎么样的标志.可以指定两个值:
CREATE_SUSPENDED : 线程创建以后,会处于挂起状态,直到调用:ResumeThread 0 : 创建线程后就开始运行.
  DWORD dwCreateFlags = 0,
// 指向一个 SECURITY_ATTRIBUTES 的结构体,用它来标志新创建线程的安全性.如果为 NULL,
那么新创建的线程就具有和主线程一样的安全性.
如果要在线程内结束线程,可以在线程内调用 AfxEndThread.
  LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL
  );//用于创建工作者线程

 

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值