程序员自我修养-CRT中的多线程

线程的访问权限:

 

多线程运行库:

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函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值