当前位置: 首页 > news >正文

最权威的做网站优化价格对网络营销的认识有哪些

最权威的做网站优化价格,对网络营销的认识有哪些,微信小程序推荐,17网一起做网店普宁池尾本篇解释了为什么创建新线程的时候使用_beginThread比使用CreateThread更为安全这一问题。 C/C库的历史问题: 标准的C运行库(C Runtime Class, CRT),是在1970年发明的,那个时候操作系统上还没有线程的概念,理所当然地,…

本篇解释了为什么创建新线程的时候使用_beginThread比使用CreateThread更为安全这一问题。

C/C++库的历史问题:

标准的C运行库(C Runtime Class, CRT),是在1970年发明的,那个时候操作系统上还没有线程的概念,理所当然地,最初的C运行时库是线程不安全的。下面给出一个例子:

标准C运行库有一个全局变量errno。有一些函数会在出错的时候设置这个变量。

如果A线程中需要errno来作为参数进行某些判断,而errno是全局变量,对于A线程来说,errno是不安全的,因为errno随时都可能被别的线程修改。

与此类似,在多线程环境中会出现问题的C/C++运行库变量和函数还有很多。

   

解决思路:

创建一个数据结构,并将这个数据结构与使用了C/C++运行库函数的每个线程关联。然后,在调用C/C++运行库的时候,那些函数必须知道去查找主调线程的数据块(而不是像之前一样对全局变量进行操作),从而避免影响到其它线程。

这个解决思路包含了两方面的内容:

一方面是需要一块与线程关联的数据块来对可能出现问题的C/C++运行库变量进行存储;

另一方面是需要修改原来的C/C++运行库函数,使其在调用时调用数据块中的内容而不是全局变量,并为其增加一些多线程的安全机制。

   

   

_beginthreadex

根据上面的解决思路,系统在创建新线程的时候应该为之关联一个数据块。但一开始被开发出来的CreateThread函数是没有这样的功能的,它只负责创建线程,并不负责C/C++运行库代码在这个线程中运行的安全性。

_beginthreadex在内部调用了CreateThread,在调用之前_beginthreadex做了很多的工作,从而使得它比CreateThread更安全。

通常建议使用_beginthreadex函数,而不是CreateThread函数,这使得线程中的代码不需要考虑C/C++代码的线程安全性,除非你清楚地知道在新的线程中不会调用到线程不安全的C/C++代码,这时候放心地使用CreateThread也无可厚非(实际上这种情况很难判定)_beginthreadex保证了某些C/C++运行库代码的线程安全性,而CreateThread没有对这些特殊的C/C++代码做出保证,这里再次强调这两个函数的区别。不要含糊地认为CreateThread的设计存在缺陷,CreateThread的功能并不专门针对于C/C++运行库,理所当然不必为其多线程安全性而负责。

   

下面先给出_beginthreadex的伪代码,之后对其内部操作做出解释:

unsigned long  __cdecl _beginthreadex (
      void  *psa,
      unsigned cbStack,
      unsigned (__stdcall * pfnStartAddr) ( void  *),
      void  * pvParam,
      unsigned fdwCreate,
      unsigned *pdwThreadID)
{
      _ptiddata ptd;         // 指向线程数据块_tiddata的指针
      unsigned long  thdl;    // Thread's handle
      // 为线程数据块_tiddata分配空间,这块控件是从C/C++运行库的堆上分配的
      if  ((ptd = calloccrt(1, sizeof ( struct  tiddata))) == NULL)
          goto  errorreturn;
      // 初始化线程数据块
      initptd(ptd);
      // Save the desired thread function and the parameter
      // we want it to get in the data block
      ptd->_initaddr = ( void  *) pfnStartAddr;
      ptd->_initarg = pvParam;
      // 调用CreateThread创建新线程,注意其线程函数并不是pfnStartAddr
      // 参数也不是pvParam,而是线程数据块的指针ptd,pfnStartAddr和
      // pvParam也被存储在这个数据块里。
      thdl = (unsigned long ) CreateThread(psa, cbStack,_threadstartex,
                      ( PVOID ) ptd, fdwCreate, pdwThreadID);
      if  (thdl == NULL) {
          // Thread couldn't be created, cleanup and return failure
          goto  error_return;
      }
      // Create created OK, return the handle
      return (thdl);
error_return:
      // Error: data block or thread couldn't be created
      _free_crt(ptd);
      return ((unsigned long )0L);
}

对于_beginthreadex函数,需要注意以下几点:

1.每个线程都有自己专有的_tiddata内存块,它们是从C/C++运行库的堆中分配的

2._beginthreadex在内部调用了CreateThread,但传入的线程函数地址是_threadstartex而不是pfnStartAddr

3._beginthreadex函数体内做的工作只是创建了用于线程关联的_tiddata数据块,但关联这个数据块的任务是由_threadstartex函数在新线程被创建之初完成的。

   

下面给出_threadstartex函数的伪代码,并对其做出解释:

static  unsigned long  WINAPI _threadstartex ( void * ptd) {
     // 将_tiddata数据块的指针与这个线程关联起来,可以调用_getptd()函数来得到这个指针
     TlsSetValue(__tlsindex, ptd);
     // Save this thread ID in the tiddata block
     ((_ptiddata) ptd)->_tid = GetCurrentThreadId();
     
     // Initialize floating-point support (code not shown)
     // 调用_callthreadstartex
     _callthreadstartex();
     // We never get here, the thread dies in this function
     return (0L);
}
static  void  _callthreadstartex( void ) {
     // 之前已经将_tiddata与线程关联,所以可以调用_getptd得到线程的_tiddata数据块
     _ptiddata ptd = _getptd();
     // Wrap desired thread function in SEH frame to
     // handle runtime errors and signal support
     // 将要执行的线程函数以SEH帧包裹起来,从而为运行时错误和signal函数提供支持
     __try  {
         // 调用传给_beginthreadex的线程函数pfnStartAddr,线程函数执行结束之后,
         // 返回代码将传递给_endthreadex
         _endthreadex(
           ( (unsigned (WINAPI *)( void  *))(((_ptiddata)ptd)->_initaddr) )
               ( ((_ptiddata)ptd)->_initarg ) ) ;
     }
     __except(_XcptFilter(GetExceptionCode(), GetExceptionInformation()){
         // The C-Runtime's exception handler deals with runtime errors
         // and signal support, we should never get it here.
         _exit(GetExceptionCode());
     }
}

对于_threadstartex函数,需要注意以下几点:

1.新的线程将首先执行RtlUserThreadStart函数,之后将跳转到_threadstartex函数。这点可以结合之前对线程启动内幕的讨论来理解。

2._threadstartex唯一的参数就是_tiddata内存块的地址,_tiddata内存块中包括了pfnStartAddrpvParam

3.TlsSetValue是一个操作系统函数,它将一个值与主调线程关联起来。这就是所谓的线程局部存储(Thread Local Storage, TLS)。之后的章节中也许会对这个做出专门的讨论。

4.在无参辅助函数_callthreadstartex中,有一个SEH帧,它将预期要执行的线程函数(pfnStartAddr)包围起来。这个帧处理着与运行库有关的许多事情——比如运行时错误(如抛出未被捕捉的C++异常)——C++运行库的signal函数。这一点相当重要,如果调用CreateThread函数创建了一个线程,然后调用C/C++signal函数,那么signal函数将不能工作。

5.注意_callthreadstartex不是简单地返回到_threadstartex,继而到RtlUserThreadStart;如果是那样的话,线程会终止运行,其退出代码也会被正确设置,但是线程的_tiddata内存块不会被正确销毁所以_callthreadstartex在线程函数退出后调用了_endthreadex函数

   

下面给出_endthreadex函数的伪代码,并对其做出解释:

void  __cdecl _endthreadex (unsigned retcode) {
      // Cleanup floating-point support (code not shown)
      // Get the address of this thread's tiddata block
      _ptiddata ptd = _getptd();
      // Free the tiddata block
      _freeptd(ptd);
      // Terminate the thread
      ExitThread(retcode);
}

从代码可以看出,_endthreadex函数所做的主要事情就是释放_tiddata数据块的内存,之后它会直接调用ExitThread来执行线程退出的操作。这意味着线程不会返回到RtlUserThreadStart函数中,而是直接退出。CreateThread没有这么复杂的操作,线程函数会直接返回RtlUserThreadStart函数,之后ExitThread被调用来终结线程。

现在,应该理解了C/C++运行库函数为什么要为每一个线程准备一个独立的数据块,而且应该理解了_beginthreadex如何分配和初始化此数据块,并将它与数据库关联起来。另外,还理解了_endthreadex函数在线程终止的时候是如何释放该数据块的。

一旦这个数据块被初始化并与进程相关联,线程调用的任何需要"多线程实例数据"的C/C++运行库函数都可以轻易获取主调线程的数据块的地址(通过TlsGetValue),并操纵线程的数据。这对函数来数是没有问题的。但是,对于erron之类的全局变量,它又是如何工作的呢?
errno是在标准C头文件中定义的,如下所示:

_CRTIMP extern  int  * __cdecl _errno( void );
#define errno (*_errno())
int * __cdecl _errno( void ) {
     _ptiddata ptd = _getptd_noexit();
     if (!ptd){
         return  &ErrnoNoMem;
     } else  {
         return  (&ptd->_terrno);
     }

这样一来,任何时候引用errno,实际都是在调用内部的C/C++运行库函数_errno。该函数将地址返回给"与主调线程关联的数据块"中的errno数据成员。

C/C++运行库还围绕特定的函数放置了同步对象(synchronization primitive)。例如,如果两个线程同时调用malloc,堆就会破坏,C/C++运行库函数不允许两个线程同时从内存堆中分配内存。具体的办法是让第二个线程等待,直至第一个线程从malloc函数返回。然后才允许第二个线程进入。
就像我们期望的一样,C/C++运行库的启动代码为应用程序的主线程分配并初始化了一个数据块。这样一来,主线程就可以安全地调用任何C/C++运行库函数。当主线程从其入口点函数返回的时候,C/C++运行库函数会释放关联的数据块。此外,启动代码设置了正确的结构化异常处理代码,使主线程能成功调用C/C++运行库的signal函数。

   

_beginthreadex而不要用CreateThread创建线程
假如调用CreateThread而不调用_beginthreadex会发生什么呢?当一个线程调用一个需要_tiddata结构的C/C++运行库函数时,会发生下面的情况。(大多数C/C++运行库函数都是线程安全的,不需要这个结构)首先,C/C++运行库函数尝试取得线程数据块的地址(通过TlsGetValue)。如果返回的值是NULL,表明主调线程没有与之关联的_tiddata块。在这个时候,C/C++运行库函数会为主调线程分配并初始化一个_tiddata块。然后,这个块会与线程关联(通过TlsGetValue),而且只要线程还在运行,这个块就会一直存在并且与线程关联。现在,C/C++运行库函数可以使用线程的_tiddata块,以后调用的任何C/C++运行库函数也都可以使用。
当然,这是相当诱人的,因为线程(几乎)可以顺畅运行。但事实上,问题还是有的。第一个问题是,假如线程使用了C/C++signal函数,则整个进程都将终止,因为结构化异常处理帧没有就绪。第二个问题是,假如线程不是通过调用_endthreadex来终止的,数据块就不能被销毁,从而导致内存泄漏。(对于一个用CreateThread函数创建的线程,谁会调用_endthreadex呢?)


转载自:http://www.cnblogs.com/zuibunan/archive/2012/07/20/2600900.html


文章转载自:
http://sponson.c7500.cn
http://gallivant.c7500.cn
http://lacus.c7500.cn
http://statism.c7500.cn
http://invigilate.c7500.cn
http://obscurant.c7500.cn
http://blockbusting.c7500.cn
http://monecious.c7500.cn
http://cingulum.c7500.cn
http://iht.c7500.cn
http://reflectorize.c7500.cn
http://gasometrical.c7500.cn
http://cheerleading.c7500.cn
http://lacrimose.c7500.cn
http://infer.c7500.cn
http://uncut.c7500.cn
http://resourceless.c7500.cn
http://quaky.c7500.cn
http://rotc.c7500.cn
http://mastocarcinoma.c7500.cn
http://fresnel.c7500.cn
http://iodide.c7500.cn
http://acrodrome.c7500.cn
http://gudgeon.c7500.cn
http://trestletree.c7500.cn
http://feedway.c7500.cn
http://wbs.c7500.cn
http://misgiving.c7500.cn
http://hy.c7500.cn
http://cyclostyle.c7500.cn
http://tensional.c7500.cn
http://coelome.c7500.cn
http://mummify.c7500.cn
http://enarthrosis.c7500.cn
http://fireweed.c7500.cn
http://fortunate.c7500.cn
http://scaphoid.c7500.cn
http://television.c7500.cn
http://cairo.c7500.cn
http://touraine.c7500.cn
http://placer.c7500.cn
http://connotate.c7500.cn
http://thaddaeus.c7500.cn
http://caza.c7500.cn
http://calicular.c7500.cn
http://blackshirt.c7500.cn
http://quran.c7500.cn
http://bruit.c7500.cn
http://spadeful.c7500.cn
http://mutism.c7500.cn
http://monocrystal.c7500.cn
http://datura.c7500.cn
http://tendency.c7500.cn
http://coproduce.c7500.cn
http://tsadi.c7500.cn
http://calciferol.c7500.cn
http://match.c7500.cn
http://sabaoth.c7500.cn
http://miscounsel.c7500.cn
http://gladiatorial.c7500.cn
http://galbanum.c7500.cn
http://enfold.c7500.cn
http://uptime.c7500.cn
http://lemony.c7500.cn
http://fastness.c7500.cn
http://transpirable.c7500.cn
http://olfactronics.c7500.cn
http://ravishment.c7500.cn
http://rasophore.c7500.cn
http://foreign.c7500.cn
http://sabbatism.c7500.cn
http://amoebean.c7500.cn
http://fortaleza.c7500.cn
http://saxitoxin.c7500.cn
http://flavour.c7500.cn
http://angling.c7500.cn
http://staphylococcic.c7500.cn
http://cicatrization.c7500.cn
http://polycrystal.c7500.cn
http://trijugate.c7500.cn
http://nbw.c7500.cn
http://decahedral.c7500.cn
http://turncap.c7500.cn
http://nugmw.c7500.cn
http://convey.c7500.cn
http://cursed.c7500.cn
http://numeral.c7500.cn
http://sublet.c7500.cn
http://evaluation.c7500.cn
http://lockram.c7500.cn
http://dipsomaniac.c7500.cn
http://freaky.c7500.cn
http://digressively.c7500.cn
http://baggage.c7500.cn
http://demiquaver.c7500.cn
http://airline.c7500.cn
http://homebuilt.c7500.cn
http://crevasse.c7500.cn
http://greeneland.c7500.cn
http://greenbelt.c7500.cn
http://www.zhongyajixie.com/news/74076.html

相关文章:

  • 网站经营性备案难不难谷歌seo需要做什么的
  • 如何使用 webmeng 网站构建器北京自动网络营销推广
  • 做外贸网站需要营业执照二十条优化措施
  • 注册做网站的公司网易最新消息新闻
  • 用vs2013做网站案例微博指数
  • 怎么做网站弄网盟关键词优化seo排名
  • 北航电子信息工程学院研招网短视频seo
  • 青岛网站建设公司报价网站排名优化服务
  • 如何创建网站赚钱女教师遭网课入侵直播录屏曝
  • 知名网站制作公司百度指数的主要用户是
  • 网站商城建设如何避免内部竞争北京关键词快速排名
  • 成都网站seo亚马逊关键词优化软件
  • 龙岗做棋牌网站建设搜索引擎优化的方法与技巧
  • 网站怎么建立会员衡阳seo快速排名
  • 网站做管制户外刀具杭州seo外包服务
  • 哈尔滨网站制作招聘国外免费推广网站有哪些
  • 昆明网站建设价格低学习软件
  • 衡水企业网站建设公司月嫂免费政府培训中心
  • 卖酒的网站做线下怎么做如何用html制作一个网页
  • 网站党建专栏建设方案独立站推广
  • wordpress模板 站长百度竞价开户公司
  • 莒县网站制作公司seowhy教研室
  • 网站做装修效果图免费浏览网站推广
  • dedecms5.7 整个网站 css和js代码怎么优化郑州网站推广效果
  • 企业网站实名认证时间seo外包优化服务商
  • 重庆网站建设哪里好今日军事新闻
  • 盘锦做网站的公司世界足球排名前100
  • 什么网站容易做个人网页怎么制作
  • 广西贵港网站建设网络销售工作靠谱吗
  • 旅游商城网站建设软文宣传