zoukankan      html  css  js  c++  java
  • C++多线程学习笔记 (CreateThread()与 _beginthreadex() )

     1 //最简单的多线程实例
     2 #include <stdio.h>
     3 #include<Windows.h>
     4 //子线程函数
     5 DWORD WINAPI ThreadFun(LPVOID pM)
     6 {
     7     //DWORD: double word,每个word 2个字节,则DWORD有4个字节,每个字节8位,故1个DWORD占32位。
     8     //WINAPI: WINDOWS API 
     9     //LPVOID: 这表示一个无类型的指针,你可以将任何类型的指针赋值给LPVOID类型的指针(常作为参数传递),在使用时再转换回来
    10 
    11     printf("子线程的线程ID号为:%d
    子线程输出Hello World
    ", GetCurrentThreadId());  
    12     return 0;
    13 }
    14 
    15 int main()
    16 {
    17     HANDLE handle = CreateThread(NULL, 0, ThreadFun, NULL, 0, NULL);
    18     WaitForSingleObject(handle, INFINITE);
    19     return 0;
    20 }
    最简单的多线程实例

    运行该实例结果如下:

    下面细讲某些函数:

    第一个 CreateThread

    函数功能:创建线程

    函数原型:

    HANDLE
    WINAPI
    CreateThread(
        __in_opt  LPSECURITY_ATTRIBUTES lpThreadAttributes,
        __in      SIZE_T dwStackSize,
        __in      LPTHREAD_START_ROUTINE lpStartAddress,
        __in_opt __deref __drv_aliasesMem LPVOID lpParameter,
        __in      DWORD dwCreationFlags,
        __out_opt LPDWORD lpThreadId
        );

    函数说明:

    第一个参数表示线程内核对象的安全属性,一般传入NULL表示使用默认设置。

    第二个参数表示线程栈空间大小。传入0表示使用默认大小(1MB)。

    第三个参数表示新线程所执行的线程函数地址,多个线程可以使用同一个函数地址。

    第四个参数是传给线程函数的参数。

    第五个参数指定额外的标志来控制线程的创建,为0表示线程创建之后立即就可以进行调度,如果为CREATE_SUSPENDED则表示线程创建后暂停运行,这样它就无法调度,直到调用ResumeThread()

    第六个参数将返回线程的ID号,传入NULL表示不需要返回该线程ID号。

    函数返回值:

    成功返回新线程的句柄,失败返回NULL 

     

    第二个 WaitForSingleObject

    函数功能:等待函数 – 使线程进入等待状态,直到指定的内核对象被触发。

    函数原形:

    DWORD
    WINAPI
    WaitForSingleObject(
        __in HANDLE hHandle,
        __in DWORD dwMilliseconds
        );

    函数说明:

    第一个参数为要等待的内核对象。

    第二个参数为最长等待的时间,以毫秒为单位,如传入5000就表示5秒,传入0就立即返回,传入INFINITE表示无限等待。

    因为线程的句柄在线程运行时是未触发的,线程结束运行,句柄处于触发状态。所以可以用WaitForSingleObject()来等待一个线程结束运行。

    函数返回值:

    在指定的时间内对象被触发,函数返回WAIT_OBJECT_0。超过最长等待时间对象仍未被触发返回WAIT_TIMEOUT。传入参数有错误将返回WAIT_FAILED

    意思就是等待第一参数所指向的线程函数的返回。

    CreateThread()函数是Windows提供的API接口,在C/C++语言另有一个创建线程的函数_beginthreadex(),在很多书上(包括《Windows核心编程》)提到过尽量使用_beginthreadex()来代替使用CreateThread(),这是为什么了?下面就来探索与发现它们的区别吧。

    首先要从标准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++运行库函数_beginthreadex()来负责的。下面列出_beginthreadex()函数的源代码(作者在这份代码中增加了一些注释)以便读者更好的理解_beginthreadex()函数与CreateThread()函数的区别。

    //_beginthreadex源码整理By MoreWindows( http://blog.csdn.net/MoreWindows )
    _MCRTIMP uintptr_t __cdecl _beginthreadex(
        void *security,
        unsigned stacksize,
        unsigned (__CLR_OR_STD_CALL * initialcode) (void *),
        void * argument,
        unsigned createflag,
        unsigned *thrdaddr
    )
    {
        _ptiddata ptd;          //pointer to per-thread data 见注1
        uintptr_t thdl;         //thread handle 线程句柄
        unsigned long err = 0L; //Return from GetLastError()
        unsigned dummyid;    //dummy returned thread ID 线程ID号
        
        // validation section 检查initialcode是否为NULL
        _VALIDATE_RETURN(initialcode != NULL, EINVAL, 0);
    
        //Initialize FlsGetValue function pointer
        __set_flsgetvalue();
        
        //Allocate and initialize a per-thread data structure for the to-be-created thread.
        //相当于new一个_tiddata结构,并赋给_ptiddata指针。
        if ( (ptd = (_ptiddata)_calloc_crt(1, sizeof(struct _tiddata))) == NULL )
            goto error_return;
    
        // Initialize the per-thread data
        //初始化线程的_tiddata块即CRT数据区域 见注2
        _initptd(ptd, _getptd()->ptlocinfo);
        
        //设置_tiddata结构中的其它数据,这样这块_tiddata块就与线程联系在一起了。
        ptd->_initaddr = (void *) initialcode; //线程函数地址
        ptd->_initarg = argument;              //传入的线程参数
        ptd->_thandle = (uintptr_t)(-1);
        
    #if defined (_M_CEE) || defined (MRTDLL)
        if(!_getdomain(&(ptd->__initDomain))) //见注3
        {
            goto error_return;
        }
    #endif  // defined (_M_CEE) || defined (MRTDLL)
        
        // Make sure non-NULL thrdaddr is passed to CreateThread
        if ( thrdaddr == NULL )//判断是否需要返回线程ID号
            thrdaddr = &dummyid;
    
        // Create the new thread using the parameters supplied by the caller.
        //_beginthreadex()最终还是会调用CreateThread()来向系统申请创建线程
        if ( (thdl = (uintptr_t)CreateThread(
                        (LPSECURITY_ATTRIBUTES)security,
                        stacksize,
                        _threadstartex,
                        (LPVOID)ptd,
                        createflag,
                        (LPDWORD)thrdaddr))
            == (uintptr_t)0 )
        {
            err = GetLastError();
            goto error_return;
        }
    
        //Good return
        return(thdl); //线程创建成功,返回新线程的句柄.
        
        //Error return
    error_return:
        //Either ptd is NULL, or it points to the no-longer-necessary block
        //calloc-ed for the _tiddata struct which should now be freed up.
        //回收由_calloc_crt()申请的_tiddata块
        _free_crt(ptd);
        // Map the error, if necessary.
        // Note: this routine returns 0 for failure, just like the Win32
        // API CreateThread, but _beginthread() returns -1 for failure.
        //校正错误代号(可以调用GetLastError()得到错误代号)
        if ( err != 0L )
            _dosmaperr(err);
        return( (uintptr_t)0 ); //返回值为NULL的效句柄
    }
    _beginthreadex()源码

    讲解:

    1. _ptiddata ptd;中的_ptiddata是个结构体指针。在mtdll.h文件被定义:

          typedef struct _tiddata * _ptiddata

    微软对它的注释为Structure for each thread's data这是一个非常大的结构体,有很多成员。本文由于篇幅所限就不列出来了。

    2_initptd(ptd_getptd()->ptlocinfo);

    微软对这一句代码中的getptd()的说明为:

          /* return address of per-thread CRT data */

          _ptiddata __cdecl_getptd(void);

    _initptd()说明如下:

          /* initialize a per-thread CRT data block */

          void__cdecl_initptd(_Inout_ _ptiddata _Ptd,_In_opt_ pthreadlocinfo _Locale);

    注释中的CRT C Runtime Library)即标准C运行库。

    由上面的源代码可知,_beginthreadex()函数在创建新线程时会分配并初始化一个_tiddata块。这个_tiddata块自然是用来存放一些需要线程独享的数据。事实上新线程运行时会首先将_tiddata块与自己进一步关联起来。然后新线程调用标准C运行库函数如strtok()时就会先取得_tiddata块的地址再将需要保护的数据存入_tiddata块中。这样每个线程就只会访问和修改自己的数据而不会去篡改其它线程的数据了。因此,如果在代码中有使用标准C运行库中的函数时,尽量使用_beginthreadex()来代替CreateThread()

  • 相关阅读:
    398. Random Pick Index
    382. Linked List Random Node
    645. Set Mismatch
    174. Dungeon Game
    264. Ugly Number II
    115. Distinct Subsequences
    372. Super Pow
    LeetCode 242 有效的字母异位词
    LeetCode 78 子集
    LeetCode 404 左叶子之和
  • 原文地址:https://www.cnblogs.com/elenno/p/Thread.html
Copyright © 2011-2022 走看看