如果在代码中有使用标准C运行库中的函数时,尽量使用_beginthreadex()来代替CreateThread()。
本文会通过源码来说明,这个是为什么?
下面先由一个创建线程的小代码来体验一下线程是什么感觉。代码中的函数在下方会有讲解,代码的大概意思就是先创建一个函数,之后在主函数中创建一个线程,调用这个函数。
//最简单的创建多线程实例
#include <stdio.h>
#include <windows.h>
//子线程函数
DWORD WINAPI ThreadFun(LPVOID pM)
{
printf("子线程的线程ID号为:%d\n子线程输出Hello World\n", GetCurrentThreadId());
return 0;
}
//主函数,所谓主函数其实就是主线程执行的函数。
int main()
{
printf(" 最简单的创建多线程实例\n");
HANDLE handle = CreateThread(NULL, 0, ThreadFun, NULL, 0, NULL);
WaitForSingleObject(handle, INFINITE);
return 0;
}
CreateThread函数就是创建线程的函数,下面我们来说明一下各个参数的含义。函数返回值:成功返回新线程的句柄,失败返回NULL。
HANDLEWINAPICreateThread(
LPSECURITY_ATTRIBUTESlpThreadAttributes,//<span style="font-size:18px;">线程内核对象的安全属性,一般传入<span style="font-family:Times New Roman;">NULL</span>表示使用默认设置</span>
SIZE_TdwStackSize,//<span style="font-size:18px;">线程栈空间大小。传入<span style="font-family:Times New Roman;">0</span>表示使用默认大小(<span style="font-family:Times New Roman;">1MB</span>)。</span>
LPTHREAD_START_ROUTINElpStartAddress,//<span style="font-size:18px;">新线程所执行的线程函数地址,多个线程可以使用同一个函数地址。</span>
LPVOIDlpParameter,//<span style="font-size:18px;">传给线程函数的参数。</span>
DWORDdwCreationFlags,//<span style="font-size:18px;">指定额外的标志来控制线程的创建</span>,<span style="font-size:18px;">为<span style="font-family:Times New Roman;">0</span>表示线程创建之后立即就可以进行调度</span>。
LPDWORDlpThreadId//<span style="font-size:18px;">将返回线程的<span style="font-family:Times New Roman;">ID</span>号,传入<span style="font-family:Times New Roman;">NULL</span>表示不需要返回该线程<span style="font-family:Times New Roman;">ID</span>号。</span>
);
WaitForSingleObject函数就是等待函数 – 使线程进入等待状态,直到指定的内核对象被触发。因为线程的句柄在线程运行时是未触发的,线程结束运行,句柄处于触发状态。所以可以用WaitForSingleObject()来等待一个线程结束运行。
在指定的时间内对象被触发,函数返回WAIT_OBJECT_0。超过最长等待时间对象仍未被触发返回WAIT_TIMEOUT。传入参数有错误将返回WAIT_FAILED
DWORDWINAPIWaitForSingleObject(
HANDLEhHandle,//<span style="font-size:18px;">为要等待的内核对象。</span>
DWORDdwMilliseconds//<span style="font-size:18px;">为最长等待的时间,以毫秒为单位,如传入<span style="font-family:Times New Roman;">5000</span>就表示<span style="font-family:Times New Roman;">5</span>秒,
//传入<span style="font-family:Times New Roman;">0</span>就立即返回,传入<span style="color:#a000a0;"><span style="font-family:Times New Roman;">INFINITE</span></span>表示无限等待。</span>
);
GetCurrentThreadId()函数就是获取当前线程一个唯一的线程标识符。
下面开始讲到我们今天的重点那就是为什么提倡使用_beginthreadex()来创建线程,因为在70年代的时候编写标准库的人没有想到未来会有多线程,因为那个时候的电脑配置都很低,比如说吧有一个全局变量,可能会出现数据库中脏读的现象,就是在一个线程读取一个全局变量的时候另一个线程可以修改这个全局变量导致结果不同。
于是微软想到了一个解决办法,就是为每一个线程都申请一块内存空间,它只能更改自己的变量,每一个线程都有一套自己的变量,下面我们可以看一下_beginthreadex()函数的源代码,其实大概的意思就是在源码中它申请了一块内存会分配并初始化一个_tiddata块,这个块里面存放的就是一些线程需要的独享的数据。事实上新线程运行时会首先将_tiddata块与自己进一步关联起来。然后新线程调用标准C运行库函数如strtok()时就会先取得_tiddata块的地址再将需要保护的数据存入_tiddata块中。这样每个线程就只会访问和修改自己的数据而不会去篡改其它线程的数据了。
_beginthreadex()创建线程的代码如下:
//创建多子个线程实例
#include <stdio.h>
#include <process.h>
#include <windows.h>
//子线程函数
unsigned int __stdcall ThreadFun(PVOID pM)
{
printf("线程ID号为%4d的子线程说:Hello World\n", GetCurrentThreadId());
return 0;
}
//主函数,所谓主函数其实就是主线程执行的函数。
int main()
{
printf(" 创建多个子线程实例 \n");
const int THREAD_NUM = 5;
HANDLE handle[THREAD_NUM];
for (int i = 0; i < THREAD_NUM; i++)
handle[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun, NULL, 0, NULL);
WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);
return 0;
}
_beginthreadex()源代码在http://blog.youkuaiyun.com/morewindows/article/details/7421759的博客中可以看到,但是源代码还是比较晦涩难懂的,其实你只要知道源代码它做了什么,做了什么createthread没有做的事情就OK了。