C++ 之 _beginThreadex的用法 与 createThread 多线程的概念区别 (二)

首先在此感谢 MoreWindows 秒杀多线程面试题系列让我成长和学习!

在此再一次真心的感谢!


在学校研究室的时候,刚刚做嵌入式的时候,导师们让我接触多线程的时候,都是使用CreateThread,也许很多朋友和我一样。

最近自己看书的时候却出现显了一些疑问?

引申阅读: 
关于_beginthreadex和CreateThread的区别

先来谈谈概念吧!例子这个东西稍后附上!

下面是关于_beginthreadex的一些要点:


•每个线程均获得由C/C++运行期库的堆栈分配的自己的tiddata内存结构。(tiddata结构位于Mtdll.h文件中的VisualC++源代码中)。

•传递给_beginthreadex的线程函数的地址保存在tiddata内存块中。传递给该函数的参数也保存在该数据块中。

•_beginthreadex确实从内部调用CreateThread,因为这是操作系统了解如何创建新线程的唯一方法。

•当调用CreatetThread时,它被告知通过调用_threadstartex而不是pfnStartAddr来启动执行新线程。还有,传递给线程函数的参数是tiddata结构而不是pvParam的地址。

•如果一切顺利,就会像CreateThread那样返回线程句柄。如果任何操作失败了,便返回NULL。

4) _endthreadex的一些要点:
•C运行期库的_getptd函数内部调用操作系统的TlsGetValue函数,该函数负责检索调用线程的tiddata内存块的地址。

•然后该数据块被释放,而操作系统的ExitThread函数被调用,以便真正撤消该线程。当然,退出代码要正确地设置和传递。

5)虽然也提供了简化版的的_beginthread和_endthread,但是可控制性太差,所以一般不使用。

6)线程handle因为是内核对象,所以需要在最后closehandle。

7)更多的API:HANDLE GetCurrentProcess();HANDLE GetCurrentThread();DWORD GetCurrentProcessId();DWORD GetCurrentThreadId()。DWORD SetThreadIdealProcessor(HANDLE hThread,DWORD dwIdealProcessor);BOOL SetThreadPriority(HANDLE hThread,int nPriority);BOOL SetPriorityClass(GetCurrentProcess(),  IDLE_PRIORITY_CLASS);BOOL GetThreadContext(HANDLE hThread,PCONTEXT pContext);BOOL SwitchToThread();

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



关于_beginthreadex和CreateThread的区别我就不做说明了,这个很 容易找到的。我们只要知道一个问题:_beginthreadex是一个C运行时库的函数,CreateThread是一个系统API函 数,_beginthreadex内部调用了CreateThread。只所以所有的书都强调内存泄漏的问题是因为_beginthreadex函数在创 建线程的时候分配了一个堆结构并和线程本身关联起来,我们把这个结构叫做tiddata结构,是通过线程本地存储器TLS于线程本身关联起来。我们传入的 线程入口函数就保存在这个结构中。tiddata的作用除了保存线程函数入口地址之外,还有一个重要的作用就是:C运行时库中有些函数需要通过这个结构来 保存和获取一些数据,比如说errno之类的线程全局变量。这点才是最重要的。

当一个线程调用一个要求tiddata结构的运行时库函数的时候,将发生下面的情况:

运行时库函数试图TlsGetv alue获取线程数据块的地址,如果没有获取到,函数就会 现场分配一个 tiddata结构,并且和线程相关联,于是问题出现了,如果不通过_endthreadex函数来终结线程的话,这个结构将不会被撤销,内存泄漏就会出 现了。但通常情况下,我们都不推荐使用_endthreadex函数来结束线程,因为里面包含了ExitThread调用。

 

找到了内存泄漏的具体原因,我们可以这样说:只要在创建的线程里面不使用一些要求tiddata结构的运行时库函数,我们的内存时安全的。所以,前面说的那句话应该这样说才完善:

“绝对不要调用系统自带的CreateThread函数创建新的线程,而应该使用_beginthreadex,除非你在线程中绝不使用需要tiddata结构的运行时库函数”

 

这个需要tiddata结构的函数有点麻烦了,在侯捷的《win32多线程程序设计》一书中这样说到:

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

1 使用malloc()和free(),或是new和delete

2 使用stdio.h或io.h里面声明的任何函数

3 使用浮点变量或浮点运算函数

4 调用任何一个使用了静态缓冲区的runtime函数,比如:asctime(),strtok()或rand()

 

那么概念先介绍到这里了,下面将进行小Demo的演示,未完待续!敬请等待!

那么我们先来一个createThrea示例

函数功能:创建线程

函数原型:

HANDLEWINAPICreateThread(

 LPSECURITY_ATTRIBUTESlpThreadAttributes,

 SIZE_TdwStackSize,

  LPTHREAD_START_ROUTINElpStartAddress,

 LPVOIDlpParameter,

 DWORDdwCreationFlags,

 LPDWORDlpThreadId

);

函数说明:

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

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

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

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

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

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

函数返回值:

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

 MSDN 参考示例:

 

#include <windows.h> <WINDOWS.H>
#include <strsafe.h> <STRSAFE.H>
#include <stdio.h><STDIO.H>

#define BUF_SIZE 255

//------------------------------------------
// A function to Display the message
// indicating in which tread we are
//------------------------------------------
void DisplayMessage (HANDLE hScreen, 
     char *ThreadName, int Data, int Count)
{

    TCHAR msgBuf[BUF_SIZE];
    size_t cchStringSize;
    DWORD dwChars;

    // Print message using thread-safe functions.
    StringCchPrintf(msgBuf, BUF_SIZE, 
       TEXT("Executing iteration %02d of %s" 
       " having data = %02d \n"), 
       Count, ThreadName, Data); 
    StringCchLength(msgBuf, BUF_SIZE, &cchStringSize);
    WriteConsole(hScreen, msgBuf, cchStringSize, 
                 &dwChars, NULL);
    Sleep(1000);
}

//-------------------------------------------
// A function that represents Thread number 1
//-------------------------------------------
DWORD WINAPI Thread_no_1( LPVOID lpParam ) 
{

    int     Data = 0;
    int     count = 0;
    HANDLE  hStdout = NULL;
    
    // Get Handle To screen.
    // Else how will we print?
    if( (hStdout = 
         GetStdHandle(STD_OUTPUT_HANDLE)) 
         == INVALID_HANDLE_VALUE )  
    return 1;

    // Cast the parameter to the correct
    // data type passed by callee i.e main() in our case.
    Data = *((int*)lpParam); 

    for (count = 0; count <= 4; count++ )
    {
       DisplayMessage (hStdout, "Thread_no_1", Data, count);
    }
    
    return 0; 
} 

//-------------------------------------------
// A function that represents Thread number 2
//-------------------------------------------
DWORD WINAPI Thread_no_2( LPVOID lpParam ) 
{

    int     Data = 0;
    int     count = 0;
    HANDLE  hStdout = NULL;
    
    // Get Handle To screen. Else how will we print?
    if( (hStdout = 
         GetStdHandle(STD_OUTPUT_HANDLE)) == 
         INVALID_HANDLE_VALUE )  
    return 1;

    // Cast the parameter to the correct
    // data type passed by callee i.e main() in our case.
    Data = *((int*)lpParam); 

    for (count = 0; count <= 7; count++ )
    {
      DisplayMessage (hStdout, "Thread_no_2", Data, count);
    }
    
    return 0; 
} 

//-------------------------------------------
// A function that represents Thread number 3
//-------------------------------------------
DWORD WINAPI Thread_no_3( LPVOID lpParam ) 
{
    int     Data = 0;
    int     count = 0;
    HANDLE  hStdout = NULL;

    // Get Handle To screen. Else how will we print?
    if( (hStdout = 
         GetStdHandle(STD_OUTPUT_HANDLE)) 
         == INVALID_HANDLE_VALUE )  
    return 1;

    // Cast the parameter to the correct
    // data type passed by callee i.e main() in our case.
    Data = *((int*)lpParam); 

    for (count = 0; count <= 10; count++ )
    {
     DisplayMessage (hStdout, "Thread_no_3", Data, count);
    }
    
    return 0; 
} 

 
void main()
{
    // Data of Thread 1
    int Data_Of_Thread_1 = 1;
    // Data of Thread 2
    int Data_Of_Thread_2 = 2;
    // Data of Thread 3
    int Data_Of_Thread_3 = 3;
    // variable to hold handle of Thread 1
    HANDLE Handle_Of_Thread_1 = 0;
    // variable to hold handle of Thread 1 
    HANDLE Handle_Of_Thread_2 = 0;
    // variable to hold handle of Thread 1
    HANDLE Handle_Of_Thread_3 = 0;
    // Aray to store thread handles 
    HANDLE Array_Of_Thread_Handles[3];

    // Create thread 1.
    Handle_Of_Thread_1 = CreateThread( NULL, 0, 
           Thread_no_1, &Data_Of_Thread_1, 0, NULL);  
    if ( Handle_Of_Thread_1 == NULL)
        ExitProcess(Data_Of_Thread_1);
    
    // Create thread 2.
    Handle_Of_Thread_2 = CreateThread( NULL, 0, 
           Thread_no_2, &Data_Of_Thread_2, 0, NULL);  
    if ( Handle_Of_Thread_2 == NULL)
        ExitProcess(Data_Of_Thread_2);
    
    // Create thread 3.
    Handle_Of_Thread_3 = CreateThread( NULL, 0, 
           Thread_no_3, &Data_Of_Thread_3, 0, NULL);  
    if ( Handle_Of_Thread_3 == NULL)
        ExitProcess(Data_Of_Thread_3);


    // Store Thread handles in Array of Thread
    // Handles as per the requirement
    // of WaitForMultipleObjects() 
    Array_Of_Thread_Handles[0] = Handle_Of_Thread_1;
    Array_Of_Thread_Handles[1] = Handle_Of_Thread_2;
    Array_Of_Thread_Handles[2] = Handle_Of_Thread_3;
    
    // Wait until all threads have terminated.
    WaitForMultipleObjects( 3, 
        Array_Of_Thread_Handles, TRUE, INFINITE);

    printf("Since All threads executed" 
           " lets close their handles \n");

    // Close all thread handles upon completion.
    CloseHandle(Handle_Of_Thread_1);
    CloseHandle(Handle_Of_Thread_2);
    CloseHandle(Handle_Of_Thread_3);
}
 
 
beginthreadex Demo
 
class MyClass
{
    _ beginthreadex(NULL,0, function, 0,0,0);      
}
 
int main ()
{
   MyClass RunOldMain;
   return;
}
void  function(LPWSTR lpParam )
{
 // function is plain 'C' 
 return;
}


你可能感兴趣的:(thread,多线程,beginthreadex)