Part1:

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

Part2:

本篇解释了为什么创建新线程的时候使用_beginThread比使用CreateThread更为安全这一问题。

C/C++库的历史问题:

标准的C运行库(C Runtime Class, CRT),是在1970年发明的,那个时候操作系统上还没有线程的概念,理所当然地,最初的C运行时库是线程不安全的。下面给出一个例子:

标准C运行库有一个全局变量errno。有一些函数会在出错的时候设置这个变量。

如果A线程中需要errno来作为参数进行某些判断,而errno是全局变量,对于A线程来说,errno是不安全的,因为errno随时都可能被别的线程修改。

与此类似,在多线程环境中会出现问题的C/C++运行库变量和函数还有很多。

解决思路:

创建一个数据结构,并将这个数据结构与使用了C/C++运行库函数的每个线程关联。然后,在调用C/C++运行库的时候,那些函数必须知道去查找主调线程的数据块(而不是像之前一样对全局变量进行操作),从而避免影响到其它线程。

这个解决思路包含了两方面的内容:

一方面是需要一块与线程关联的数据块来对可能出现问题的C/C++运行库变量进行存储;

另一方面是需要修改原来的C/C++运行库函数,使其在调用时调用数据块中的内容而不是全局变量,并为其增加一些多线程的安全机制。

_beginthreadex:

根据上面的解决思路,系统在创建新线程的时候应该为之关联一个数据块。但一开始被开发出来的CreateThread函数是没有这样的功能的,它只负责创建线程,并不负责C/C++运行库代码在这个线程中运行的安全性。

_beginthreadex在内部调用了CreateThread,在调用之前_beginthreadex做了很多的工作,从而使得它比CreateThread更安全。

通常建议使用_beginthreadex函数,而不是CreateThread函数,这使得线程中的代码不需要考虑C/C++代码的线程安全性,除非你清楚地知道在新的线程中不会调用到线程不安全的C/C++代码,这时候放心地使用CreateThread也无可厚非(实际上这种情况很难判定)。_beginthreadex保证了某些C/C++运行库代码的线程安全性,而CreateThread没有对这些特殊的C/C++代码做出保证,这里再次强调这两个函数的区别。不要含糊地认为CreateThread的设计存在缺陷,CreateThread的功能并不专门针对于C/C++运行库,理所当然不必为其多线程安全性而负责。

下面先给出_beginthreadex的伪代码,之后对其内部操作做出解释:

unsignedlong __cdecl _beginthreadex (
     void *psa,
     unsigned cbStack,
     unsigned (__stdcall * pfnStartAddr) (void *),
     void * pvParam,
     unsigned fdwCreate,
     unsigned *pdwThreadID)
{
     _ptiddata ptd;         // 指向线程数据块_tiddata的指针
     unsignedlong thdl;   // Thread's handle
     // 为线程数据块_tiddata分配空间,这块控件是从C/C++运行库的堆上分配的
     if ((ptd = calloccrt(1, sizeof(struct tiddata))) == NULL)
         goto errorreturn;
     // 初始化线程数据块
     initptd(ptd);
     // Save the desired thread function and the parameter
     // we want it to get in the data block
     ptd->_initaddr = (void *) pfnStartAddr;
     ptd->_initarg = pvParam;
     // 调用CreateThread创建新线程,注意其线程函数并不是pfnStartAddr
     // 参数也不是pvParam,而是线程数据块的指针ptd,pfnStartAddr和
     // pvParam也被存储在这个数据块里。
     thdl = (unsigned long) CreateThread(psa, cbStack,_threadstartex,
                     (PVOID) ptd, fdwCreate, pdwThreadID);
     if (thdl == NULL) {
         // Thread couldn't be created, cleanup and return failure
         goto error_return;
     }
     // Create created OK, return the handle
     return(thdl);
error_return:
     // Error: data block or thread couldn't be created
     _free_crt(ptd);
     return((unsignedlong)0L);
}

对于_beginthreadex函数,需要注意以下几点:

1.每个线程都有自己专有的_tiddata内存块,它们是从C/C++运行库的堆中分配的。

2._beginthreadex在内部调用了CreateThread,但传入的线程函数地址是_threadstartex而不是pfnStartAddr。

3._beginthreadex函数体内做的工作只是创建了用于线程关联的_tiddata数据块,但关联这个数据块的任务是由_threadstartex函数在新线程被创建之初完成的。

下面给出_threadstartex函数的伪代码,并对其做出解释:

static unsignedlong WINAPI _threadstartex (void* ptd) {
    // 将_tiddata数据块的指针与这个线程关联起来,可以调用_getptd()函数来得到这个指针
    TlsSetValue(__tlsindex, ptd);
    // Save this thread ID in the tiddata block
    ((_ptiddata) ptd)->_tid = GetCurrentThreadId();
     
    // Initialize floating-point support (code not shown)
    // 调用_callthreadstartex
    _callthreadstartex();
    // We never get here, the thread dies in this function
    return(0L);
}
static void _callthreadstartex(void) {
    // 之前已经将_tiddata与线程关联,所以可以调用_getptd得到线程的_tiddata数据块
    _ptiddata ptd = _getptd();
    // Wrap desired thread function in SEH frame to
    // handle runtime errors and signal support
    // 将要执行的线程函数以SEH帧包裹起来,从而为运行时错误和signal函数提供支持
    __try {
        // 调用传给_beginthreadex的线程函数pfnStartAddr,线程函数执行结束之后,
        // 返回代码将传递给_endthreadex
        _endthreadex(
          ( (unsigned (WINAPI *)(void *))(((_ptiddata)ptd)->_initaddr) )
              ( ((_ptiddata)ptd)->_initarg ) ) ;
    }
    __except(_XcptFilter(GetExceptionCode(), GetExceptionInformation()){
        // The C-Runtime's exception handler deals with runtime errors
        // and signal support, we should never get it here.
        _exit(GetExceptionCode());
    }
}

对于_threadstartex函数,需要注意以下几点:

1.新的线程将首先执行RtlUserThreadStart函数,之后将跳转到_threadstartex函数。这点可以结合之前对线程启动内幕的讨论来理解。

2._threadstartex唯一的参数就是_tiddata内存块的地址,_tiddata内存块中包括了pfnStartAddr和pvParam。

3.TlsSetValue是一个操作系统函数,它将一个值与主调线程关联起来。这就是所谓的线程局部存储(Thread Local Storage, TLS)。之后的章节中也许会对这个做出专门的讨论。

4.在无参辅助函数_callthreadstartex中,有一个SEH帧,它将预期要执行的线程函数(pfnStartAddr)包围起来。这个帧处理着与运行库有关的许多事情——比如运行时错误(如抛出未被捕捉的C++异常)——和C++运行库的signal函数。这一点相当重要,如果调用CreateThread函数创建了一个线程,然后调用C/C++的signal函数,那么signal函数将不能工作。

5.注意_callthreadstartex不是简单地返回到_threadstartex,继而到RtlUserThreadStart;如果是那样的话,线程会终止运行,其退出代码也会被正确设置,但是线程的_tiddata内存块不会被正确销毁。所以_callthreadstartex在线程函数退出后调用了_endthreadex函数。

下面给出_endthreadex函数的伪代码,并对其做出解释:

void __cdecl _endthreadex (unsigned retcode) {
     // Cleanup floating-point support (code not shown)
     // Get the address of this thread's tiddata block
     _ptiddata ptd = _getptd();
     // Free the tiddata block
     _freeptd(ptd);
     // Terminate the thread
     ExitThread(retcode);
}

从代码可以看出,_endthreadex函数所做的主要事情就是释放_tiddata数据块的内存,之后它会直接调用ExitThread来执行线程退出的操作。这意味着线程不会返回到RtlUserThreadStart函数中,而是直接退出。CreateThread没有这么复杂的操作,线程函数会直接返回RtlUserThreadStart函数,之后ExitThread被调用来终结线程。

现在,应该理解了C/C++运行库函数为什么要为每一个线程准备一个独立的数据块,而且应该理解了_beginthreadex如何分配和初始化此数据块,并将它与数据库关联起来。另外,还理解了_endthreadex函数在线程终止的时候是如何释放该数据块的。

一旦这个数据块被初始化并与进程相关联,线程调用的任何需要"多线程实例数据"的C/C++运行库函数都可以轻易获取主调线程的数据块的地址(通过TlsGetValue),并操纵线程的数据。这对函数来数是没有问题的。但是,对于erron之类的全局变量,它又是如何工作的呢?
errno是在标准C头文件中定义的,如下所示:

_CRTIMPextern int * __cdecl _errno(void);
#define errno (*_errno())
int* __cdecl _errno(void) {
    _ptiddata ptd = _getptd_noexit();
    if(!ptd){
        return &ErrnoNoMem;
    }else {
        return (&ptd->_terrno);
    }

这样一来,任何时候引用errno,实际都是在调用内部的C/C++运行库函数_errno。该函数将地址返回给"与主调线程关联的数据块"中的errno数据成员。

C/C++运行库还围绕特定的函数放置了同步对象(synchronization primitive)。例如,如果两个线程同时调用malloc,堆就会破坏,C/C++运行库函数不允许两个线程同时从内存堆中分配内存。具体的办法是让第二个线程等待,直至第一个线程从malloc函数返回。然后才允许第二个线程进入。
就像我们期望的一样,C/C++运行库的启动代码为应用程序的主线程分配并初始化了一个数据块。这样一来,主线程就可以安全地调用任何C/C++运行库函数。当主线程从其入口点函数返回的时候,C/C++运行库函数会释放关联的数据块。此外,启动代码设置了正确的结构化异常处理代码,使主线程能成功调用C/C++运行库的signal函数。

用_beginthreadex而不要用CreateThread创建线程
假如调用CreateThread而不调用_beginthreadex会发生什么呢?当一个线程调用一个需要_tiddata结构的C/C++运行库函数时,会发生下面的情况。(大多数C/C++运行库函数都是线程安全的,不需要这个结构)首先,C/C++运行库函数尝试取得线程数据块的地址(通过TlsGetValue)。如果返回的值是NULL,表明主调线程没有与之关联的_tiddata块。在这个时候,C/C++运行库函数会为主调线程分配并初始化一个_tiddata块。然后,这个块会与线程关联(通过TlsGetValue),而且只要线程还在运行,这个块就会一直存在并且与线程关联。现在,C/C++运行库函数可以使用线程的_tiddata块,以后调用的任何C/C++运行库函数也都可以使用。
当然,这是相当诱人的,因为线程(几乎)可以顺畅运行。但事实上,问题还是有的。第一个问题是,假如线程使用了C/C++的signal函数,则整个进程都将终止,因为结构化异常处理帧没有就绪。第二个问题是,假如线程不是通过调用_endthreadex来终止的,数据块就不能被销毁,从而导致内存泄漏。(对于一个用CreateThread函数创建的线程,谁会调用_endthreadex呢?)

转载自:http://www.cnblogs.com/zuibunan/archive/2012/07/20/2600900.html

C++多线程函数_beginthread/_beginthreadex/CreateThread相关推荐

  1. 采用_beginthread/_beginthreadex函数创建多线程

    1.CRT简介: CRT: (C Runtime Library)即C运行时库,是系统运行的基础,包含了c常用的函数集(如:printf,malloc,strcpy等),为运行main做了初始化环境变 ...

  2. 关于_beginthreadex、_beginthread和CreateThread

    关于_beginthreadex._beginthread和CreateThread 在微软的 Programming Techniques 说明文件中有一句看似悲惨的警告: 警告:如果你在一个与 L ...

  3. 秒杀多线程第二篇 多线程第一次亲密接触 CreateThread与_beginthreadex本质区别

    本文将带领你与多线程作第一次亲密接触,并深入分析CreateThread与_beginthreadex的本质区别,相信阅读本文后你能轻松的使用多线程并能流畅准确的回答CreateThread与_beg ...

  4. CreateThread()函数及_beginthreadex()函数

    CreateThread():创建一个线程可以调用进程的虚拟地址空间内执行.创建的线程终止运行后,线程对象仍然在系统中,必须通过CloseHandle函数来关闭该线程对象. 函数原型: HANDLE ...

  5. c语言createthread函数用法,C++多线程函数CreateThread如何使用?

    C++ CreateThread函数如何传递this指针作为参数 C++多线程函数CreateThread如何使用? #include #include using namespace std; /* ...

  6. _beginthread和CreateThread 创建线程

    建立一个线程. unsigned long beginthread(void(cdecl *startaddress)(void*),unsigned stacksize, void *arglist ...

  7. MFC thread _beginthreadex CreateThread AfxBeginThread 对比

    CreateThread CreateThread函数是Windows的一个API函数 操作系统级别的创建线程的操作,且仅限于工作者线程.不调用MFC和RTL的函数时,可以用CreateThread, ...

  8. CreateThread() 多线程函数

    // multi_thread.cpp #include "multi_thread.h" using namespace std; // 线程函数 DWORD WINAPI Th ...

  9. c++类成员函数中调用多线程函数_beginthreadex()

    #include "stdafx.h" #include #include #include using namespace std; class A {public:int n; ...

  10. Creatthread _Beginthread _Beginthreadex

    在 Win32 API 中,创建线程的基本函数是 CreateThread,而 _beginthread(ex) 是 C++ 运行库的函数.为什么要有两个呢?因为C++ 运行库里面有一些函数使用了全局 ...

最新文章

  1. mysql 5.6 与5.7 区别_mysql5.6和5.7的区别
  2. 通过SQL Server 2008 访问MySQL
  3. Matlab中配置LibSVM 总结
  4. 【MySQL随手记】一个踩坑记录:在安全更新模式下进行数据的修改与删除
  5. 华夏银行北京分行签约第四范式智能决策平台
  6. Android开发之品牌机型不同setMargins属性无效的bug
  7. Java Dictionary elements()方法与示例
  8. Qt之C语言有符号数与无符号数运算
  9. 软件毕业设计文档流程与UML图之间的关系
  10. 16位伪指令汇编程序查看内存
  11. 对接融云记录几点问题
  12. PMP-全书知识重点图
  13. 第五次网页前端培训(JS的基本使用)
  14. PPT实现倒计时功能(VBA实现)
  15. ruby + watir 自动化上传图片文件解决方案
  16. 中国行政村边界数据、乡镇街道边界
  17. python 小于号大于号是什么意思_大于号和小于号怎么区别
  18. JDK8中Lambda 表达式语法糖脱糖[非原创]
  19. 即时通讯-Netty篇
  20. python爬虫——爬取小说

热门文章

  1. PyCharm快捷键
  2. UDS诊断服务(0x10)
  3. 【非标自动化】2017年的最NB的非标自动化内容都在这了
  4. Docker(2) Windows10安装教程(DockerToolbox)
  5. Solaris系统下修改IP地址
  6. Scikit-learn_回归算法_支持向量机回归
  7. 使用a标签下载文件,解决页面跳转的问题
  8. 基于springboot的校园竞赛报名管理系统
  9. 波司登杯2013微软office应用创意大赛烟台大学校园赛参赛历程
  10. 嵌入式linux应用开发完全手册(一)