Windows API 多线程 CreateThread、_beginthread、_beginthreadex、AfxBeginThread

1. 概述

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

  • _beginthread_beginthreadex

    • MS对C Runtime库的扩展SDK函数,首先针对C Runtime库做了一些初始化的工作,以保证C Runtime库工作正常,
    • 然后,调用CreateThread真正创建线程。
    • _beginthread_beginthreadex的功能子集
    • 虽然_beginthread内部是调用_beginthreadex,但它屏蔽了安全特性这样的功能
      • 例如,如果使用_beginthread,就无法创建带有安全属性的新线程,无法创建暂停的线程,也无法获得线程的ID值。
    • _beginthreadCreateThread不是同等级别,_beginthreadexCreateThread在功能上完全可替代
  • AfxBeginThread:MFC中线程创建的MFC函数,它简化了操作或让线程能够响应消息,即可用于界面线程,也可以用于工作者线程,但要注意尽量不要在一个MFC程序中使用_beginthreadex()或CreateThread()。

  • AfxBeginThread_beginThread_beginThreadex实际上是编译器对CreateThread的封装

1. 编程的时候如何选择各个函数

  • MFC程序选择AfxBeginThread

  • 如果不使用Microsoft的Visual C++编译器,你的编译器供应商有它自己的CreateThred替代函数

2. 尽量不调用CreateThread

  • 尽量不要调用CreateThread。相反,应该使用Visual C++运行期库函数_beginthreadex,原因如下:

    • 考虑标准C运行时库的一些变量和函数,如errno,这是一个全局变量。全局变量用于多线程会出现资源竞争问题,需要进行同步互斥。故必须存在一种机制,使得每个线程能够引用它自己的errno变量,又不触及另一线程的errno变量,_beginthreadex就为每个线程分配自己的tiddata内存结构:
      • 该结构保存了许多像errno这样的变量和函数的值、地址。
      • 通过线程局部存储将tiddata与线程联系起来。具体实现在Threadex.c中有。
      • 结束线程使用函数_endthreadex函数,释放掉线程的tiddata数据块。
    • (以下主要介绍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就行。 大多的CRT函数都可以在CreateThread线程中使用,看资料说只有signal()函数不可以,会导致进程终止。但其他大多数函数可以用并不是说没有问题, 有些CRT的函数malloc(), fopen(), _open(), strtok(), ctime(), 或localtime()等函数需要专门的线程局部存储的数据块,这个数据块通常需要在创建线程的时候就建立,如果使用CreateThread,这个数据块就没有建立,在这样的线程中还是可以使用这些函数而且没有出错,实际上函数发现这个数据块的指针为空时,会自己建立一个,然后将其与线程联系在一起,这意味着如果你用CreateThread来创建线程,然后使用这样的函数,会有一块内存在不知不觉中创建,遗憾的是,这些函数并不将其删除,而CreateThreadExitThread也无法知道这件事,于是就会有Memory leak,在线程频繁启动的软件中(比如某些服务器软件),迟早会让系统的内存资源耗尽! _beginthreadex_endthreadex就对这个内存块做了处理,所以没有问题。

    • 如果在除主线程之外的任何线程中进行一下操作,你就应该使用多线程版本的C runtime library,并使用_beginthreadex_endthreadex

      1. 使用malloc()free(),或是newdelete
      2. 使用stdio.hio.h里面声明的任何函数
      3. 使用浮点变量或浮点运算函数
      4. 调用任何一个使用了静态缓冲区的runtime函数,比如:asctime(),strtok()rand()

      Handle的问题,_beginthread的对应函数_endthread自动的调用了CloseHandle,而_beginthreadex的对应函数_endthreadex则没有,所以CloseHandle无论如何都是要调用的不过_endthread可以帮你执行自己不必写,其他两种就需要自己写!(Jeffrey Richter强烈推荐尽量不用显式的终止函数,用自然退出的方式,自然退出当然就一定要自己写CloseHandle)

3. 注意

1)C++主线程的终止,同时也会终止所有主线程创建的子线程,不管子线程有没有执行完毕。所以如果不调用WaitForSingleObject,则子线程可能并没有执行完毕或根本没有执行。
2)如果某线程挂起,然后有调用WaitForSingleObject等待该线程,就会导致死锁。所以上面的代码如果不调用resumethread,则会死锁。

4. _beginthreadex用法

  • 头文件: process.h

  • 函数原型:

unsigned long _beginthreadex( 
    void *security, 
    unsigned stack_size, 
    unsigned ( __stdcall *start_address )( void * ), 
    void *arglist, 
    unsigned initflag, 
    unsigned *thrdaddr 
);

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

5.示例

  • 示例1:
#include <iostream>

#include <string>

#include <process.h>

#include <Windows.h>

using namespace std;



class ThreadX{

private:

    int loopStart;

    int loopEnd;

    int dispFrequency;



public:

    string threadName;



    ThreadX(int startVal, int endVal, int frequency){

        this->loopStart = startVal;

        this->loopEnd = endVal;

        this->dispFrequency = frequency;

    }

    static unsigned __stdcall ThreadStaticEntryPoint(void * pThis){

        ThreadX* pThX = (ThreadX*) pThis;

        pThX->ThreadEntryPoint();

        return 1;

    }

    void ThreadEntryPoint(){

        for (int i = loopStart; i <= loopEnd; i++){

            cout << threadName << " i = " << i << endl;

            Sleep(100);

        }

    }

};



int main(){

    ThreadX * pThX = new ThreadX(0, 10, 2000);

    HANDLE hth1 = NULL;

    unsigned uiThread1ID;

    hth1 =(HANDLE) _beginthreadex(NULL,

        0,

        ThreadX::ThreadStaticEntryPoint,

        pThX,

        CREATE_SUSPENDED,

        &uiThread1ID);

    if (hth1 == NULL){

        cout << "failed to create thread 1" << endl;



    }

    DWORD dwExitCode;

    GetExitCodeThread(hth1, &dwExitCode); // shoule be STILL_ACTIVE

    cout << "init thread 1 exit code id = " << dwExitCode << endl;

    pThX->threadName = "yang1";



    ThreadX* pThX2 = new ThreadX(0, 10, 2000);

    HANDLE hth2 = NULL;

    unsigned uiThread2ID;

    hth2 = (HANDLE) _beginthreadex(NULL,

        0,

        ThreadX::ThreadStaticEntryPoint,

        pThX2,

        CREATE_SUSPENDED,

        &uiThread2ID);

    if (hth2 == NULL){

        cout << "create thread 2 failed" << endl;



    }

    GetExitCodeThread(hth2, &dwExitCode);

    cout << "init thread 2 exit code id = " << dwExitCode << endl;

    pThX2->threadName = "yang2";



    ResumeThread(hth1);

    ResumeThread(hth2);



    WaitForSingleObject(hth1, INFINITE);

    WaitForSingleObject(hth2, INFINITE);



    GetExitCodeThread(hth1, &dwExitCode);

    cout << "thread 1 exited with exit code " << dwExitCode << endl;

    GetExitCodeThread(hth2, &dwExitCode);

    cout << "thread 2 exited with exit code " << dwExitCode << endl;



    CloseHandle(hth1);

    CloseHandle(hth2);



    delete pThX;

    delete pThX2;

    pThX = NULL;

    pThX2 = NULL;

    cout << "end program  " << endl;



    return 0;

}
  • 示例2:
#include<string>
 #include<iostream>
 #include<process.h>
 #include<windows.h>
 using namespace std;

struct Arg//用来传参给线程函数
{
    double d_;
    string str_;
    Arg(double dd, string ss):d_(dd), str_(ss){}
};

//线程绑定的函数返回值和参数是确定的,而且一定要__stdcall
unsigned __stdcall threadFun(void *)
{
    for(int i = 0; i < 10; i++)
        cout<<i<<endl;
    return 1;
}

//可以通过结构体来传入参数
unsigned __stdcall threadFunArg(void *arglist)
{
    Arg *p = (Arg *)arglist;
    cout<<p->d_<<endl;
    cout<<p->str_<<endl;
    return 2;
}

//简单的线程类
class ThreadClass
{
private:
    string str_;
    int i_;
public:
    ThreadClass(string s, int i):str_(s), i_(i){}
    static unsigned __stdcall threadStaic(void *arg)
    {
        ThreadClass *p = (ThreadClass *)arg;
        p->threadfun();
        return 3;
    }
    void threadfun()
    {
        cout<<str_<<endl;
        cout<<i_<<endl;
    }
};

int main()
{
    unsigned int thID1, thID2, thID3, thID4;
    HANDLE hth1, hth2, hth3, hth4;
    Arg arg(3.14, "hello world");
    ThreadClass tclass("welcom", 999);

    //注意的是_beginthreadex是立即返回的,系统不会等线程函数执行完毕,因此要保证
    //局部arg变量 在线程函数执行完毕前不会释放,更安全的是使用new来构造arg
    hth1 = (HANDLE)_beginthreadex(NULL, 0, threadFun, NULL, 0, &thID1);
    hth2 = (HANDLE)_beginthreadex(NULL, 0, threadFun, NULL, 0, &thID2);
    hth3 = (HANDLE)_beginthreadex(NULL, 0, threadFunArg, &arg, 0, &thID3);
    hth4 = (HANDLE)_beginthreadex(NULL, 0, ThreadClass::threadStaic, &tclass, 0,
                                   &thID4);

    //主线程一定要等待子线程结束
    WaitForSingleObject(hth1, INFINITE);
    WaitForSingleObject(hth2, INFINITE);
    WaitForSingleObject(hth3, INFINITE);
    WaitForSingleObject(hth4, INFINITE);

    DWORD exitCode1, exitCode2, exitCode3, exitCode4;
    GetExitCodeThread(hth1, &exitCode1);
    GetExitCodeThread(hth2, &exitCode2);
    GetExitCodeThread(hth3, &exitCode3);
    GetExitCodeThread(hth4, &exitCode4);
    cout<<endl<<"exitcode::"<<exitCode1<<" "<<exitCode2<<" "<<exitCode3<<" "
        <<exitCode4<<endl;
    cout<<"ID:"<<thID1<<" "<<thID2<<" "<<thID3<<" "<<thID4<<endl;

    //一定要记得关闭线程句柄
    CloseHandle(hth1);
    CloseHandle(hth2);
    CloseHandle(hth3);
    CloseHandle(hth4);
}

猜你喜欢

转载自blog.csdn.net/VonSdite/article/details/81230388
今日推荐