C C++中ThreadLocal的实现方式
我们之前讲了Windows的线程创建方式,在创建线程时应该使用_beginthreadex函数,而不是CreateThread函数,结束线程应该用_endthreadex,当然最好是不要主动去结束,而是应该用return来结束线程。
C是不支持多线程的,因为在C开发出来的时候,多任务操作系统还没有开发出来,我们现在的处理器都是多线程的,所以现在的程序都要支持多线程,多线程程序中有一个概念是很重要的,那就是threadlocal,我们都知道在C中有一个errno全局变量,可以让我们通过GetLastError()方法来获取错误代码,但是如果是多线程的程序的话,很多线程都同时在改这一个变量,很可能我们获取到的就是其他线程设置的errno了,threadlocal就是要解决这种问题的,有了threadlocal每个线程中的errno都是他自己的,其他线程无法更改你的errno。
ThreadLocal实现原理
上面我们了解了ThreadLocal的作用,就是要保存每个线程自己的变量副本,每个线程都有一个自己的线程运行栈(是用户空间还是内核空间呢?待考察–),堆空间是所有线程共有的进程的用户空间。
如果我们创建线程时用_beginthreadex函数的话,这个函数内部会为我们创建一个 _tiddata结构,如果是我们可以将需要作为线程独立的变量放在这个区域,这就可以使用TlsGetValue来获取threadlocal变量。
我们来看看_beginthreadex的伪代码,其实这个函数的源码是可以看到的,在VC的crt/src/Threadex.c中,这里的伪代码更加的结构更加清晰
uintptr_r __cdecl _beginthreadex
(
void *psa,
unsigned cbStackSize;
unsigned (__stdcall * pfnStartAddr)(void*),
void *pvParam,
unsigned dwCreateFlags,
unsigned *pwdThreadID
)
{
_ptiddata ptd; // pointer to thread's data block
uintptr_r thdl; //Thread's HANDLE
//Allocate data block for new thread
//为新线程分配数据区域
if((ptd=(_ptiddata)_calloc_crt(1,sizeof(struct _tiddata))) == NULL) goto error_return;
// Initilize the data block
initptd(ptd);
//Save the desired thread function and the parameter we want to get in the data block
//将线程的函数和参数放入这块数据区域
ptd->_initaddr = (void *)pfnStartAddr;
ptd->_initarg = pvParam;
ptd->_thandle = (uintptr_t)(-1);
//Create the thread
thdl = (uintptr_t)CreateThread((LPSECURITY_ATTRIBUTES)pas,cbStackSize,
_threadstartex,(PVOID)ptd,dwCreateFlags,pwdThreadID);
if(thdl == 0){
// thread can not be created