C C++中ThreadLocal的实现方式
来源:互联网 发布:mac如何隐藏下面的菜单 编辑:程序博客网 时间:2024/06/05 19:42
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 goto error_return; }else{ //返回线程句柄 return (thdl); }error_return: // Error: data block or thread can not be created _free_crt(ptd); return ((uintptr_t))L);}
通过上面的伪代码,我们了解到调用_beginthreadex 函数为我们在堆区创建了一块数据区域,并且把我们的线程函数和参数放入了这块区域,然后调用CreateThread函数时,传入的并不是我们指定的线程函数,而是另一个函数 _threadstartex,而且参数是分配的data block,而不是我们传入的参数,接下来我们来 看看分配的数据区域相关的代码。这个结构体在mtdll.h文件中
struct _tiddata { unsigned long _tid; /* thread ID */ uintptr_t _thandle; /* thread handle */ int _terrno; /* errno value */ unsigned long _tdoserrno; /* _doserrno value */ unsigned int _fpds; /* Floating Point data segment */ unsigned long _holdrand; /* rand() seed value */ char * _token; /* ptr to strtok() token */ wchar_t * _wtoken; /* ptr to wcstok() token */ unsigned char * _mtoken; /* ptr to _mbstok() token */ /* following pointers get malloc'd at runtime */ char * _errmsg; /* ptr to strerror()/_strerror() buff */ wchar_t * _werrmsg; /* ptr to _wcserror()/__wcserror() buff */ char * _namebuf0; /* ptr to tmpnam() buffer */ wchar_t * _wnamebuf0; /* ptr to _wtmpnam() buffer */ char * _namebuf1; /* ptr to tmpfile() buffer */ wchar_t * _wnamebuf1; /* ptr to _wtmpfile() buffer */ char * _asctimebuf; /* ptr to asctime() buffer */ wchar_t * _wasctimebuf; /* ptr to _wasctime() buffer */ void * _gmtimebuf; /* ptr to gmtime() structure */ char * _cvtbuf; /* ptr to ecvt()/fcvt buffer */ unsigned char _con_ch_buf[MB_LEN_MAX]; /* ptr to putch() buffer */ unsigned short _ch_buf_used; /* if the _con_ch_buf is used */ /* following fields are needed by _beginthread code */ void * _initaddr; /* initial user thread address */ void * _initarg; /* initial user thread argument */ /* following three fields are needed to support signal handling and * runtime errors */ void * _pxcptacttab; /* ptr to exception-action table */ void * _tpxcptinfoptrs; /* ptr to exception info pointers */ int _tfpecode; /* float point exception code */ /* pointer to the copy of the multibyte character information used by * the thread */ pthreadmbcinfo ptmbcinfo; /* pointer to the copy of the locale informaton used by the thead */ pthreadlocinfo ptlocinfo; int _ownlocale; /* if 1, this thread owns its own locale */ /* following field is needed by NLG routines */ unsigned long _NLG_dwCode; /* * Per-Thread data needed by C++ Exception Handling */ void * _terminate; /* terminate() routine */ void * _unexpected; /* unexpected() routine */ void * _translator; /* S.E. translator */ void * _purecall; /* called when pure virtual happens */ void * _curexception; /* current exception */ void * _curcontext; /* current exception context */ int _ProcessingThrow; /* for uncaught_exception */ void * _curexcspec; /* for handling exceptions thrown from std::unexpected */#if defined (_M_X64) || defined (_M_ARM) void * _pExitContext; void * _pUnwindContext; void * _pFrameInfoChain;#if defined (_WIN64) unsigned __int64 _ImageBase; unsigned __int64 _ThrowImageBase;#else /* defined (_WIN64) */ unsigned __int32 _ImageBase; unsigned __int32 _ThrowImageBase;#endif /* defined (_WIN64) */ void * _pForeignException;#elif defined (_M_IX86) void * _pFrameInfoChain;#endif /* defined (_M_IX86) */ _setloc_struct _setloc_data; void * _reserved1; /* nothing */ void * _reserved2; /* nothing */ void * _reserved3; /* nothing */#ifdef _M_IX86 void * _reserved4; /* nothing */ void * _reserved5; /* nothing */#endif /* _M_IX86 */ int _cxxReThrow; /* Set to True if it's a rethrown C++ Exception */ unsigned long __initDomain; /* initial domain used by _beginthread[ex] for managed function */#if defined(_CRT_APP) && !defined(_KERNELX) HANDLE _winRTThreadHandle; /* App CRT WinRT thread handle */#else /* _CRT_APP */ int _initapartment; /* if 1, this thread has initialized apartment */#endif /* _CRT_APP */ _psetloc_downlevel_struct _setloc_downlevel_data;};
接下来我们来分析 调用CreateThread时的传入的函数 _threadstartex的伪代码
static unsigned long WINAPI _threadstartex(void *ptd)
{
// Note:ptd is the address of this thread’s tiddata block.
//Associate the tiddata block with this thread so _getptd() will be able to find it in _callthreadstartex.
TlsSetValue(__tlsindex,ptd);
// Save this thread ID in the _tiddata block.((_ptiddata)ptd)->_tid = GetCurrentThreadId();_callthreadstartex();// We never get here ; the thread dies in _callthreadstartex.return (0L);
}
static void _callthreadstartex(void){
_ptiddata ptd;
//获取data block ,(用TlsSetValue设置的)
ptd = _getptd();
//Wrap desired thread function in SEH frame to handle run-time errors and signal support.__try{//调用线程函数_endthreadex(ptd._initaddr( ptd._initarg ));}__except(_XcpFilter(GetExceptionCode(),GetExceptionInformation())){ _exit(GetExceptionCode());}
}
在这里我们看到了函数是如何使用data block和调用我们的线程函数的,我们也应该知道了,程序会将我们的线程函数以及参数放到datablock中去,并且调用操作系统的TlsSetValue将线程与datablock联系起来了,最后我们再来看一下_endthreadex的伪代码
void __cdecl _endthreadex (unsigned retcode)
{
_ptiddata ptd;// pointer to datablock
//Clean up floating-point support
// Get the address of this thread's tiddata block.ptd = _getptd_noexti();//Free the data blockif(ptd != NULL) _freeptd(ptd);// Terminate thread ExitThread(retcode);
}
到这里为止我们应该知道了线程独立的数据是如何存储的了,下面我们来看一个实例,我们来看看错误代码时如果支持多线程的,来看VC头文件中关于errno的定义
_CRTIMP extern int * __cdecl _errno(void);#define errno (*_errno());int * __cdecl _errno(void){ _ptiddata ptd = _getptd_noexti(); if(!ptd)return &ErrnoNoMem; else return (&ptd->_terrno);}
我们可以看到,VC中将errno定义成了一个方法(用宏来替换的),然后在上面讲的 data block中来获取的errno,所以是线程独立的。
汇总
经过上面的的分析我们在创建线程和结束线程时应该用 _beginthreadstartex和 _endthreadex,因为他会帮我们维护thread local的数据结构,假如有人就直接用CreateThread会怎么样呢?如果线程要用到threadlocal的数据结构,但是由于是使用CreateThread创建的线程,还没有那个data block ,这里C/C++运行库会帮我们创建一个_tiddata的数据结构,这样基本上是可以正常使用的,但是由于没有初始化SEH(异常处理)帧,当我们使用signal函数是,程序会退出整个进程,还有一个问题是,如果退出的时候没有用_endthreadex的话,由于没有释放_tiddata 数据结构,会导致内存泄露。
- C C++中ThreadLocal的实现方式
- 41、C#:C#中对于接口的实现方式
- Objective-C中不同方式实现锁
- Objective-C中不同方式实现锁
- Objective-C中不同方式实现锁
- C的方式实现可变参数函数
- 用 Python实现C的读入方式
- c call java的实现方式
- Object-C Block的实现方式
- PID算法的C语言实现方式
- 一机一码注册方式的实现(C#)
- C/C++中类的存储方式
- Qt中使用C++的方式
- C语言中字符串的处理方式
- C/C++中类的存储方式
- ANSI C 中,IO的缓存方式
- C中内存分配的方式
- C语言中参数的传递方式
- 解决IllegalStateException: Can not perform this action after onSaveInstanceState
- Android详细的对话框AlertDialog.Builder小示例
- 2015 多校联赛 ——HDU5373(模拟)
- Jedis相关操作
- isis dce接收到报文的流程
- C C++中ThreadLocal的实现方式
- 如何在线程中获取spring 管理的bean
- 如何创建圆形头像和圆角图片
- boost------ref的使用(Boost程序库完全开发指南)读书笔记
- LeetCode题解:Valid Anagram
- PhpStrom花括号的设置
- power of two
- uvali5697(DP)
- java 反射机制