首先在此感谢 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;
}