VC.【转】采用_beginthread/_beginthreadex函数创建多线程

https://blog.csdn.net/cbnotes/article/details/8331632

还可以看这个网址的内容:【多线程】VC6使用_beginthread开启多线程的方法-技术宅的结界 - Powered by Discuz!.html(https://www.0xaa55.com/technews/201509/00000092.html

网页内容保存:

1、CRT简介:

CRT: (C Runtime Library)即C运行时库,是系统运行的基础,包含了c常用的函数集(如:printf,malloc,strcpy等),为运行main做了初始化环境变量、堆、io等资源,并在结束后清理。

在Windows环境下,VC提供的 C run-time library又分为动态运行时库、静态运行时库、多线程、单线程、调试版本(Debug)、发行版本(Release)等。

 开关

对应的库

版本

   /MD

MSVCRT.LIB

多线程DLL的Release版本

   /MDd

MSCVRTD.LIB

多线程DLL的Debug版本

   /MT

LIBCMT.LIB

多线程静态链接的Release版本

   /MTd

LIBCMTD.LIB

多线程静态链接的Debug版本

   /clr

MSVCMRT.LIB

托管代码和非托管代码混合

   /clr:pure

MSVCURT.LIB

纯托管代码

图1:编译器运行库设置(VS2008)

具体请参考MSDN:

ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.chs/dv_vccrt/html/a889fd39-807d-48f2-807f-81492612463f.htm

2、使用CRT的多线程函数集:两组

序号

函数名

功能

1

_beginthread()

创建一个新线程

2

_endthread()

结束一个线程的执行

3

_beginthreadex()

创建一个新线程

4

_endthreadex()

结束一个线程的执行

5

ResumeThread()

恢复线程的运行

6

SuspendThread()

挂起线程

7

GetExiCodeThread()

得到一个线程的退出码

8

WaitForSingleObject()

等待单个对象

9

WaitForMultipleObjects()

等待多个对象

3、两组函数的说明:

3.1、_beginthread和_endthread
该函数是C Runtime Library中的函数。其原型如下

unsigned long _beginthread(

void( __cdecl *start_address )( void * ),//线程函数的起始地址

unsigned stack_size,//堆栈大小,设置0为系统默认值

void *arglist );//传递给线程函数的参数,没有则为NULL

“该函数被认为是头脑简单的函数”,使用该函数导致无法有效的控制被创建线程,如不能在启动时将该线程挂起,无法为该线程设置优先权等。另外,无法利用这个Handle来等待该线程结束等操作。该函数是早期的C Runtime Library的产物,不提倡使用,后期的改良版本为_beginthreadex。
通过_beginthread启动的线程在应当通过调用_endthread结束,以保证清除与线程相关的资源。_endthread的原型为:

void _endthread(void);

 

3.2、_beginthreadex和_endthreadex
该函数是C Runtime Library中的一个函数,用标准C实现,相比_beginthread,_beginthreadex对线程控制更为有力(比前者多三个参数),是_beginthread的加强版。其原型为:

unsignedlong _beginthreadex(

void *security,//线程函数的安全描述符

unsigned stack_size,// 堆栈大小,设置0为系统默认值

unsigned ( __stdcall *start_address )( void * ),//线程函数的起始地址

void*arglist, //传递给线程函数的参数,没有则为NULL

unsignedinitflag,//初始状态,0为立即执行,CREATE_SUSPEND为创建后挂起

unsigned*thrdaddr );//指向一个32为的变量,存放线程标识符

该函数返回新线程的句柄,通过该句柄可实现对线程的控制。虽然,该函数是用标准C写的(即可不加修改就可以移植到其他系统执行),但是由于它与Windows系统有着紧密的联系(需要手动关闭该线程产生的Handle),因此实现时,往往需要包含windows.h。
通过_beginthreadex启动的线程通过调用_endthreadex做相关清理。该函数比较像CreateThread函数。

_endthreadex函数的原型为:

void _endthreadex(unsigned retVal);

关于这两组函数的详细区别请参考MSDN的说明:

Creates a thread.

uintptr_t _beginthread(

   void( *start_address )( void * ),

   unsigned stack_size,

   void *arglist

);

uintptr_t _beginthreadex(

   void *security,

   unsigned stack_size,

   unsigned ( *start_address )( void * ),

   void *arglist,

   unsigned initflag,

   unsigned *thrdaddr

);

Parameters

start_address

Start address of a routine that begins execution of a new thread.For _beginthread, the calling convention is either__cdecl or __clrcall; for _beginthreadex, it iseither __stdcall or __clrcall.

stack_size

Stack size for a new thread or 0.

arglist

Argument list to be passed to a new thread or NULL.

security

Pointer to a SECURITY_ATTRIBUTES structure that determines whetherthe returned handle can be inherited by child processes. If NULL, the handlecannot be inherited. Must be NULL for Windows 95 applications.

initflag

Initial state of a new thread (0 forrunning or CREATE_SUSPENDED for suspended); useResumeThread to execute the thread.

thrdaddr

Points to a 32-bit variable that receives the thread identifier.Might be NULL, in which case it is not used.

Return Value

If successful, each of these functions returns a handle to thenewly created thread; however, if the newly created thread exits too quickly, _beginthread might not return a valid handle (see thediscussion in the Remarks section). _beginthread returns -1L on an error, in which case errno is set to EAGAIN if thereare too many threads, to EINVAL if the argument isinvalid or the stack size is incorrect, or to EACCESin the case of insufficient resources (such as memory). _beginthreadexreturns 0 on an error, in which case errno and _doserrno are set.

If startaddress is NULL, the invalid parameter handler is invoked, asdescribed in Parameter Validation. If execution is allowed to continue, these functions set errno to EINVAL and return -1.

For more information about these and other return codes, see_doserrno, errno, _sys_errlist, and _sys_nerr.

For more information about uintptr_t,see Standard Types.

Remarks

The _beginthread function creates athread that begins execution of a routine at start_address.The routine at start_address must use the __cdecl calling convention and should have no returnvalue. When the thread returns from that routine, it is terminatedautomatically. For more information about threads, see Multithreading.

_beginthreadex resembles the Win32 CreateThread API more closely than _beginthread does. _beginthreadexdiffers from _beginthread in the following ways:

·        _beginthreadex has three additional parameters: initflag,security, and threadaddr.The new thread can be created in a suspended state, with a specified security(Windows NT only), and can be accessed using thrdaddr,which is the thread identifier.

·        The routine at start_address passed to _beginthreadexmust use the __stdcall calling convention and mustreturn a thread exit code.

·        _beginthreadex returns 0 on failure, rather than -1L.

·        A thread created with _beginthreadex is terminated by a call to _endthreadex.

The _beginthreadex function gives you morecontrol over how the thread is created than _beginthreaddoes. The _endthreadex function is also moreflexible. For example, with _beginthreadex, you canuse security information, set the initial state of the thread (running orsuspended), and get the thread identifier of the newly created thread. You arealso able to use the thread handle returned by _beginthreadexwith the synchronization APIs, which you cannot do with _beginthread.

It is safer to use _beginthreadex than _beginthread. If the thread generated by _beginthread exits quickly, the handle returned to thecaller of _beginthread might be invalid or, worse,point to another thread. However, the handle returned by _beginthreadexhas to be closed by the caller of _beginthreadex, soit is guaranteed to be a valid handle if _beginthreadexdid not return an error.

You can call _endthread or _endthreadex explicitly to terminate athread; however, _endthread or _endthreadexis called automatically when the thread returns from the routine passed as aparameter. Terminating a thread with a call to endthreador _endthreadex helps to ensure proper recovery ofresources allocated for the thread.

_endthread automatically closes the thread handle (whereas _endthreadex does not). Therefore, when using _beginthread and _endthread, donot explicitly close the thread handle by calling the Win32 CloseHandle API.This behavior differs from the Win32 ExitThread API.

Note:

For an executable file linked with Libcmt.lib , do not call the Win32 ExitThread API; this prevents the run-time system from reclaiming allocated resources. _endthread and _endthreadex reclaim allocated thread resources and then call ExitThread.

The operating system handles the allocation of the stack wheneither _beginthread or _beginthreadexis called; you do not need to pass the address of the thread stack to either ofthese functions. In addition, the stack_sizeargument can be 0, in which case the operating system uses the same value asthe stack specified for the main thread.

arglist is a parameter to be passed to the newly created thread.Typically it is the address of a data item, such as a character string. arglist can be NULL if it isnot needed, but _beginthread and _beginthreadex must be provided with some value to pass tothe new thread. All threads are terminated if any thread calls abort, exit, _exit, or ExitProcess.

The locale of the new thread is inherited from its parent thread.If per thread locale is enabled by a call to _configthreadlocale (eitherglobally or for new threads only), the thread can change its localeindependently from its parent by calling setlocaleor _wsetlocale. For more information, see Locale.

For mixed and pure code, _beginthreadand _beginthreadex both have two overloads, onetaking a native calling-convention function pointer, the other taking a __clrcall function pointer. The first overload is notapplication domain-safe and never will be. If you are writing mixed or purecode you must ensure that the new thread enters the correct application domainbefore it accesses managed resources. You can do this, for example, by usingcall_in_appdomain Function. The second overload is application domain-safe; thenewly created thread will always end up in the application domain of the callerof _beginthread or _beginthreadex.

Requirements

Routine

Required header

_beginthread

<process.h>

_beginthreadex

<process.h>

For more compatibility information, see Compatibility in theIntroduction.

Libraries

Multithreaded versions of the C run-time libraries only.

3.3、线程函数的定义:

_beginthread()和_beginthreadex()的线程执行函数的定义是不一样的。

对于_beginthread()创建的线程,其线程函数定义为:

void ThreadPro(void * pArguments );

对于_beginthreadex()创建的线程,其线程函数定义为:

unsigned __stdcallThreadFunc( void* pArguments )

4、注意事项:

(1)两者创建线程函数方式不同,_beginthreadex()的线程函数必须使用__stdcall调用方式,而且必须返回一个unsigned型的退出码。

(2)_beginthreadex()在创建线程失败时返回0,而_beginthread()在创建线程失败时返回-1,这一点在检测返回结果时必须注意。

(3)如果是调用_beginthread()创建线程,并相应地调用_endthread()结束线程时,系统将自动关闭线程句柄。而调用_beginthreadex()创建线程,并相应地调用_endthreadex()结束线程时,系统不能自动关闭线程句柄。

(4)由于_beginthread()创建线程参数比较简单,不能控制线程的初始启动状态,且不返回创建的线程句柄,也不能调用

WaitForSingleObject()/WaitForMultipleObjects()函数。所以一般不常用,而_beginthreadex()与CreatThread()函数比较相似。能方便控制线程。

5、多线程实例:

5.1、实例1:

实例说明:该实例主要使用_beginthread()函数,创建尽可能多的线程,知道系统不能再创建为止。注意实时数据的显示和参数的传递,如果想传递多个参数,则可以使用结构体。

主要函数如下:

//在.h文件中:

#define WM_ADD WM_USER+10//定义消息

void ThreadFunc1(void *pArg);//线程函数1:开始创建

void ThreadFunc2(void *pArg);//线程函数2:

LRESULT OnMyMsg(WPARAM, LPARAM);//消息函数  

//在.cpp文件中:

//全局变量,一般在cpp中定义,不要在.h定义,否则编译出错:变量重复定义

bool g_bRun = false;

long g_nCount = 0;

//开始按钮函数

void C_beginthreadDlg::OnBnClickedButtonRun()

{

    // TODO: 在此添加控件通知处理程序代码

    //创建线程:主要是用来创建执行创建线程池的线程

    if (_beginthread(ThreadFunc1,0,&m_hWnd) != -1 )

    {//成功

       GetDlgItem(IDC_BUTTON_RUN)->EnableWindow(FALSE);

    }

}

 

//创建线程池的线程函数

void ThreadFunc1(void *pArg)

{

    HWND *hWnd = (HWND*)pArg;//得到窗口句柄

    g_nCount= 0;//初始化变量

    g_bRun = true;

    while (g_bRun)

    {//开始创建尽可能多的线程

       if (_beginthread(ThreadFunc2,0,hWnd) == -1)

       {//失败则结束

           g_bRun= false;

           break;

       }

    }

    //退出

    ::SendMessage(*hWnd,WM_ADD,1,0); 

}

 

//线程函数

void ThreadFunc2(void *pArg)

{

    HWND *hWnd = (HWND*)pArg;

    g_nCount++;

    ::SendMessage(*hWnd,WM_ADD,0,g_nCount);//显示个数

    while(g_bRun)

    {

       Sleep(1000);

    }

}

 

// WM_ADD消息处理函数

LRESULT C_beginthreadDlg::OnMyMsg(WPARAM wParam, LPARAM lParam)

{

    if (wParam == 1)

    {//结束

       GetDlgItem(IDC_BUTTON_RUN)->EnableWindow(TRUE);

    }

    else

    {//显示更新

       m_nCount= g_nCount;

        UpdateData(FALSE);

    }

    return 0;

}

运行效果:

图2:实例1演示

工程源码下载地址:

http://download.csdn.net/detail/cbnotes/4902781

欢迎大家修改和指正。

注意事项:

(1)由于_beginthread()创建的线程结束后自动关闭线程句柄,所以不能使用WaitForSingleObject()/WaitForMultipleObjects()函数来同步。

(2)上面的程序中,先是创建了线程1(ThreadFunc1),再用该线程1来创建众多线程,而不是直接在OnBnClickedButtonRun()函数中创建众多线程,大家知道这是为什么吗?主要是为了能够实时显示创建的线程数目。如果直接在OnBnClickedButtonRun()函数中创建众多线程,主线程将一直处于while循环中,而不能及时处理消息,所以就不能实时显示创建的线程数目(不信的话大家可以试试)。

(3)注意线程函数参数的传递,和消息的发送。如果要传递多个参数,可以使用结构体传递。

5.2、实例2:

实例说明:该实例主要使用_beginthreadex()函数,多人合作(5人)共同完成一项任务,即平时大家说的分工合作,齐头并进。

主要函数如下:

//.h文件中

//声明线程处理函数

unsigned __stdcall ThreadFunc1( void*pArguments );//合作工作线程函数

unsigned __stdcall ThreadFunc2( void*pArguments );//检测工作完成线程函数

 

//为了传递多个参数,我采用结构体

struct threadInfo

{

    HWND hWnd;       //窗口句柄

    int  nOffset;    //偏移量

    int  nWidth;     //宽度

};

 

struct threadInfo2

{

    HWND   hWnd;           //窗口句柄

    HANDLE *phHandle;     //线程句柄指针

};

// 实现

protected:

    threadInfoInfo[5];

    HANDLE m_hThead[5];    //用于存储线程句柄

    HANDLE hThead;     //用于存储线程句柄

    unsigned  m_dwThreadID[5];//用于存储线程的ID

   threadInfo2Info2;

//在.cpp文件中

//开始按钮函数

void C_beginthreadexDlg::OnBnClickedButtonRun()

{

    // TODO: 在此添加控件通知处理程序代码

    //使能开始按钮:无效

    GetDlgItem(IDC_BUTTON_RUN)->EnableWindow(FALSE);

    CDC *dc = GetDC();

    CRect rt;

    GetClientRect(rt);

    dc->FillSolidRect(0,0,rt.Width(),200,RGB(240,240,240));//刷新背景

    ReleaseDC(dc);

    int nWidth = rt.Width()/5;

    //初始化线程的参数

    Info[0].hWnd = Info[1].hWnd = Info[2].hWnd = Info[3].hWnd = Info[4].hWnd = GetSafeHwnd();

    Info[0].nOffset = 0;Info[1].nOffset = nWidth;Info[2].nOffset= 2*nWidth;Info[3].nOffset = 3*nWidth;Info[4].nOffset= 4*nWidth;

    Info[0].nWidth = Info[1].nWidth = Info[2].nWidth = Info[3].nWidth = Info[4].nWidth = nWidth;

    //创建个模拟线程

    for (int i = 0;i<5;i++)

    {  

       m_hThead[i] = (HANDLE)_beginthreadex(NULL,0,ThreadFunc1,&Info[i],CREATE_SUSPENDED,&m_dwThreadID[i]); 

    }

    GetDlgItem(IDC_STATIC1)->SetWindowText("进行中...");

    //开始运行

    for (int i = 0;i<5;i++)

    {  

       ResumeThread(m_hThead[i]); 

    }

    //开始运行监测结果线程

    Info2.hWnd = m_hWnd;

    Info2.phHandle = m_hThead;

    hThead =(HANDLE)_beginthreadex(NULL,0,ThreadFunc2,&Info2,0,NULL);

}

 

//合作工作线程函数

unsigned __stdcall ThreadFunc1( void*pArguments )

{

    threadInfo*info = (threadInfo*)pArguments;

    CDC *dc = CWnd::FromHandle(info->hWnd)->GetDC();

    for (int i=info->nOffset;i<info->nOffset+info->nWidth;i++)

    {

       for (int j =0 ;j<200;j++)

       {  

           dc->SetPixel(i,j,RGB(0,0,0));

       }

    }

    DeleteObject(dc);

    return 0;

}

 

//等待所有的工作结束

unsigned __stdcall ThreadFunc2( void*pArguments )

{

    threadInfo2*info = (threadInfo2*)pArguments;

    //等待个线程中的一个完成

    DWORD dwRet = WaitForMultipleObjects(5,info->phHandle,TRUE,INFINITE);

    if (dwRet == WAIT_FAILED)

    {//出错啦

       ::SendMessage(info->hWnd,WM_GAMEOVER,1,0);

       return 0;

    }

    //完成结束

    ::SendMessage(info->hWnd,WM_GAMEOVER,0,0);

    return 0;

}

 

 

//消息处理函数

LRESULT C_beginthreadexDlg::OnGameOver(WPARAMwParam, LPARAMlParam)

{

    if (wParam ==1)

    {//出错

       GetDlgItem(IDC_STATIC1)->SetWindowText("出错啦!");

    }

    else

    {//成功

       //显示结果

       GetDlgItem(IDC_STATIC1)->SetWindowText("任务完成!");

       //闪动显示

       CDC *dc = GetDC();

       CRect rt;

       GetClientRect(rt);

       for (int i=0;i<8;i++)

       {

           dc->Draw3dRect(0,0,rt.Width(),200,RGB(240,240,240),RGB(240,240,240));//刷新背景

           dc->Draw3dRect(1,1,rt.Width()-1,199,RGB(240,240,240),RGB(240,240,240));//刷新背景

           Sleep(100);

           dc->Draw3dRect(0,0,rt.Width(),200,RGB(255,0,0),RGB(255,0,0));//刷新背景

           dc->Draw3dRect(1,1,rt.Width()-1,199,RGB(255,0,0),RGB(255,0,0));//刷新背景

           Sleep(100);

       }

       ReleaseDC(dc);

    }

    //关闭句柄

    for (int i=0;i<5;i++)

    {

       CloseHandle(m_hThead[i]);

    }

    CloseHandle(hThead);

    //使能开始按钮,以便可以开始下一次比赛

    GetDlgItem(IDC_BUTTON_RUN)->EnableWindow(TRUE);

    return 0;

}

运行效果:

工程源码下载地址:

http://download.csdn.net/detail/cbnotes/4905693

欢迎大家修改和指正。

注意事项:

(1)由于_beginthreadex()创建的线程结束后自动调用_endthreadex()函数,但是不会关闭线程句柄,所以必须手动关闭句柄,但能使用同步函数,如:

WaitForSingleObject()/WaitForMultipleObjects()函数来同步。

(2)通过该实例还可以稍加改进,就可以统计5个线程的各自的工作时间,并排序。欢迎大家多多练习。

(3)由_beginthreadex()函数创建线程,能灵和控制和管理线程,所以推荐使用该方式。

=================================================转载请标明出处,谢谢.http://blog.csdn.net/cbNotes

Z

原文地址:https://www.cnblogs.com/cppskill/p/9024535.html