程序员自我修养-CRT中的多线程
来源:互联网 发布:pandorabox域名过滤 编辑:程序博客网 时间:2024/05/22 00:11
线程的访问权限:
多线程运行库:
1. c语言必须提供多线程的API
2. 有些函数之前设计并不适合多线程,需要改进
Errno,strtok,malloc,new,printf,异常处理等等多线程都是不安全的
在多线程中CRT的改进:
CRT改进:
1. 使用TLS,比如errno在单线程版本中直接返回全局的errno,但是在多线程中返回的是线程的私有变量
2. 加锁:在多线程版本中,线程不安全的函数内部会自动进行加锁,比如printf,malloc等函数,以及异常处理都已经实现加锁
3. 改进函数的调用方式:
char *strtok(char *strToken,constchar *strDelimit)
char *strtok_schar*strToken,const char *strDelimit,char **context)
改进后的strtok添加了一个参数,因为原来的strtok将分割后的字符串,放在一个静态变量中,现在通过context返回。
线程的局部变量存储
在多线程中线程私有的数据有线程的栈,寄存器,但是如果线程想使用全局变量,但是权限是该线程私有的全局变量,不是所有线程共享的。
TLS(线程局部存储):
对于GCC:__thread intnumber; 这个时候这个number就是线程私有的全局变量
对于MSVC:__declspec(thread) int number;
在windows中TLS的实现:
在一般情况下如果一个变量是全局变量,会被放到.bss或者.data中,但是如果被定义成一个tls变量,会被放到.tls段中。当系统启动一个新的线程时,它会从进程的堆中分配一个空间,然后把.tls段中的内容copy到这块空间中,相当于每个线程都有自己独立的.tls副本。对于__declspec(thread)定义的全局变量在每个线程中地址是不一样的。当然还要对每个TLS变量初始化,最后线程结束的时候进行析构。
在PE的数据目录结构中:
有一项为IMAGE_TLS_DIRECTORY_TLS,这里保存了所有的TLS变量的构造函数和析构函数。
线程时如何访问自己的TLS数据的?
每个线程有一个TEB线程环境块,这里保存了线程的堆栈信息,线程ID等,当然还有一个TLS数组,它在TEB中的偏移是0X2C。对于每个线程来说,在x86结构下,FS寄存器保存该线程的TEB地址,所以要得到一个线程的TLS数组只要访问 FS:[0X2C]。
在TLS数组中第一个元素指向tls副本的位置,所以要访问tls变量,通过FS:[0X2C]访问得到TLS数组,然后通过tls变量在TLS数组中的偏移得到tls变量。
显示TLS:
使用关键字__thread和__declspec(thread)定义的TLS全局变量是隐式的方法,因为程序员不需要关心TLS变量的申请,分配赋值,释放。
显示的TLS:程序员必须自己申请TLS变量,每个访问的时候都要调用相应的函数得打变量的地址,最后自己手工释放。
在windows中,TlsAlloc(),TlsGetValue(),TlsSetValue(),TlsFree()这4个函数用来控制tls变量的申请,取值,赋值,释放
相应的Linux中,pthread_key_create(),pthread_getspecific(), pthread_setspecific(), pthread_key_delete().
显示的Tls实现:
上面提到的TLS数组,一般线程大小是64个元素,第一个上面已经知道了指向.tls的副本,其他的可以用来保存显示的TLS,如果64个不够用了,还可以申请1024个,所以最多TLS可以放下1088个元素。显示的Tls分配在堆上,但是用TLS指向它。
但是总体来说,显示的TLS并不推荐使用。
在windwos中:createthreadCreateThread() 和 beginthread()的区别:
beginthread函数封装了CreateThread。看下面源代码,是msvc中的thread.c:
_CRTIMP uintptr_t __cdecl _beginthread ( void (__cdecl * initialcode) (void *), unsigned stacksize, void * argument ){ _ptiddata ptd; /* pointer to per-thread data ,这个空间在堆上,但是由显示的TLS保存ptd指针*/ uintptr_t thdl; /* thread handle */ unsigned long err = 0L; /* Return from GetLastError() */ /* validation section */ _VALIDATE_RETURN(initialcode != NULL, EINVAL, -1); /* Initialize FlsGetValue function pointer */ __set_flsgetvalue(); /* * Allocate and initialize a per-thread data structure for the to- * be-created thread. 注意这里为_ptiddata在堆上分配了空间 */ if ( (ptd = (_ptiddata)_calloc_crt(1, sizeof(struct _tiddata))) == NULL ) { goto error_return; }//_tiddata这里主要包含一个线程的ID,线程的句柄,erron,strtok前一次调用的位置,异常处理等一些线程私有的信息。 /* * Initialize the per-thread data */ _initptd(ptd, _getptd()->ptlocinfo);//将ptd传入,进行初始化 ptd->_initaddr = (void *) initialcode; ptd->_initarg = argument;#if defined (_M_CEE) || defined (MRTDLL) if(!_getdomain(&(ptd->__initDomain))) { goto error_return; }#endif /* defined (_M_CEE) || defined (MRTDLL) */ /* * Create the new thread. Bring it up in a suspended state so that * the _thandle and _tid fields are filled in before execution * starts. */ if ( (ptd->_thandle = thdl = (uintptr_t) CreateThread( NULL,//这里才是真的创建thread stacksize, _threadstart, (LPVOID)ptd, CREATE_SUSPENDED, (LPDWORD)&(ptd->_tid) )) == (uintptr_t)0 ) { err = GetLastError(); goto error_return; } /* * Start the new thread executing */ if ( ResumeThread( (HANDLE)thdl ) == (DWORD)(-1) ) { err = GetLastError(); goto error_return; } /* * Good return */ return(thdl); /* * Error return */error_return: /* * Either ptd is NULL, or it points to the no-longer-necessary block * calloc-ed for the _tiddata struct which should now be freed up. */ _free_crt(ptd); //free ptd,防止内存泄露 /* * Map the error, if necessary. */ if ( err != 0L ) _dosmaperr(err); return( (uintptr_t)(-1) );}void __cdecl _endthread ( void ){ _ptiddata ptd; /* pointer to thread's _tiddata struct */ ptd = _getptd_noexit(); if (ptd) { /* * Close the thread handle (if there was one) */ if ( ptd->_thandle != (uintptr_t)(-1) ) (void) CloseHandle( (HANDLE)(ptd->_thandle) ); /* * Free up the _tiddata structure & its subordinate buffers * _freeptd() will also clear the value for this thread * of the FLS variable __flsindex. */ _freeptd(ptd); //free ptd,防止内存泄露 } /* * Terminate the thread */ ExitThread(0);}
分析:为什么通过CreateThread,调用strtok函数不会出错,按理说使用CreateThread并没有像Beginthread函数一样初始化一个struct_tiddata。实际上在strtok函数本身,一开始会调用一个_getptd得到一个线程的struct _tiddata,所以其实我们不必担心struct _tiddata有没有在createthread中有没有创建。我们知道在_endthread中有free一个struct _tiddata,但是在ExitThread并没有释放这个对象。那为什么在动态链接的时候,使用CreateThread和ExitThread也不会struct _tiddata因为有内存泄露。答案:在CRT DLL的入口函数DllMain中,在这个函数,会被每个DLL调用一次,所以在动态链接的版本中会被DllMain释放。但是在静态链接中就没有这么幸运了,所以会导致内存的泄露。
总结:在使用CRT多线程的时候,尽量用包装过的CRT函数。
- 程序员自我修养-CRT中的多线程
- [读书笔记]程序员的自我修养 chp13 一个简单的CRT 运行库实现
- 程序员的自我修养
- 程序员的自我修养
- 程序员的自我修养
- 程序员的自我修养
- 程序员的自我修养
- 程序员的自我修养
- 程序员的自我修养
- 程序员的自我修养
- 程序员的自我修养
- 程序员的自我修养
- 程序员的自我修养
- 程序员的自我修养
- 程序员的自我修养
- 程序员的自我修养
- 程序员自我修养
- 程序员的自我修养
- 在InitDialog中设置焦点不起作用 SETFOCUS
- 外包公司的员员看过来---面试篇
- AES加密
- 浅入深探究mysql索引结构原理、性能分析与优化
- 得到保存在assets目录下的txt文件的内容
- 程序员自我修养-CRT中的多线程
- 【摘抄】生活简单明了,享受人生守住30%便好。人生苦短,总会到站!
- 随想录(开源代码的学习方法)
- Android UI布局整理
- ASP无组件上传的原理
- php实现文件上传进度条 .
- Excel转换成Xml
- IBM Power7走向优化服务
- API hook原理和实例快速入门(inline hook),以dll线程注入方式使用(win7-64bit)