您的位置:程序门 -> vc/mfc -> 进程/线程/dll



关于多线程写的一个问题,请指教


[收藏此页] [打印本页]选择字色:背景色:字体:[][][]


关于多线程写的一个问题,请指教
发表于:2007-04-14 11:45:57 楼主
有多个线程,他们都执行一样的操作,即对变量i   进行自增操作,那些线程并没有对变量i的值的一些判断,仅仅就是让i自增,请问这也需要做线程互斥吗?
发表于:2007-04-14 11:54:321楼 得分:0
需要,下面的来自《windows核心编程》,可能长了点:)

atomic   access:   the   interlocked   family   of   functions
a   big   part   of   thread   synchronization   has   to   do   with   atomic   access—a   thread 's   ability   to   access   a   resource   with   the   guarantee   that   no   other   thread   will   access   that   same   resource   at   the   same   time.   let 's   look   at   a   simple   example:

//   define   a   global   variable.
long   g_x   =   0;

dword   winapi   threadfunc1(pvoid   pvparam)   {
      g_x++;
      return(0);
}

dword   winapi   threadfunc2(pvoid   pvparam)   {
      g_x++;
      return(0);
}

 


i 've   declared   a   global   variable,   g_x,   and   initialized   it   to   0.   now   let 's   say   that   i   create   two   threads:   one   thread   EXECutes   threadfunc1,   and   the   other   thread   EXECutes   threadfunc2.   the   code   in   these   two   functions   is   identical:   they   both   add   1   to   the   global   variable   g_x.   so   when   both   threads   stop   running,   you   might   expect   to   see   the   value   2   in   g_x.   but   do   you?   the   answer   is…maybe.   the   way   the   code   is   written,   you   can 't   tell   what   g_x   will   ultimately   contain.   here 's   why.   let 's   say   that   the   compiler   generates   the   following   code   for   the   line   that   increments   g_x   by   1:  

mov   eax,   [g_x]               ;   move   the   value   in   g_x   into   a   register.
inc   eax                             ;   increment   the   value   in   the   register.
mov   [g_x],   eax               ;   store   the   new   value   back   in   g_x.

 


both   threads   are   unlikely   to   EXECute   this   code   at   exactly   the   same   time.   so   if   one   thread   EXECutes   this   code   followed   by   another   thread,   here   is   what   effectively   EXECutes:

mov   eax,   [g_x]               ;   thread   1:   move   0   into   a   register.
inc   eax                             ;   thread   1:   increment   the   register   to   1.
mov   [g_x],   eax               ;   thread   1:   store   1   back   in   g_x.

mov   eax,   [g_x]               ;   thread   2:   move   1   into   a   register.
inc   eax                             ;   thread   2:   increment   the   register   to   2.
mov   [g_x],   eax               ;   thread   2:   store   2   back   in   g_x.

 


after   both   threads   are   done   incrementing   g_x,   the   value   in   g_x   is   2.   this   is   great   and   is   exactly   what   we   expect:   take   zero   (0),   increment   it   by   1   twice,   and   the   answer   is   2.   beautiful.   but   wait—windows   is   a   preemptive,   multithreaded   environment.   so   a   thread   can   be   switched   away   from   at   any   time   and   another   thread   might   continue   EXECuting   at   any   time.   so   the   code   above   might   not   EXECute   exactly   as   i 've   written   it.   instead,   it   might   EXECute   as   follows:

mov   eax,   [g_x]               ;   thread   1:   move   0   into   a   register.
inc   eax                             ;   thread   1:   increment   the   register   to   1.

mov   eax,   [g_x]               ;   thread   2:   move   0   into   a   register.
inc   eax                             ;   thread   2:   increment   the   register   to   1.
mov   [g_x],   eax               ;   thread   2:   store   1   back   in   g_x.

mov   [g_x],   eax               ;   thread   1:   store   1   back   in   g_x.

 


if   the   code   EXECutes   this   way,   the   final   value   in   g_x   is   1—not   2   as   you   expect!   this   is   pretty   scary,   especially   since   you   have   so   little   control   over   the   scheduler.   in   fact,   if   you   have   100   threads   EXECuting   similar   thread   functions,   after   all   of   them   exit,   the   value   in   g_x   might   still   be   1!   obviously,   software   developers   can 't   work   in   an   environment   like   this.   we   expect   that   incrementing   0   twice   results   in   2   all   the   time.   also,   let 's   not   forget   that   the   results   might   be   different   depending   on   how   the   compiler   generates   codes,   what   cpu   is   EXECuting   the   code,   and   how   many   cpus   are   installed   in   the   host   computer.   this   is   how   the   environment   works,   and   there   is   nothing   we   can   do   about   that.   but   windows   does   offer   some   functions   that,   when   used   correctly,   guarantee   the   outcome   of   our   application 's   code.

to   solve   the   problem   above,   we   need   something   simple.   we   need   a   way   to   guarantee   that   the   incrementing   of   the   value   is   done   atomically—that   is,   without   interruption.   the   interlocked   family   of   functions   provides   the   solution   we   need.   the   interlocked   functions   are   awesome   and   underused   by   most   software   developers,   even   though   they   are   incredibly   helpful   and   easy   to   understand.   all   of   the   functions   manipulate   a   value   atomically.   take   a   look   at   interlockedexchangeadd:

long   interlockedexchangeadd(
      plong   pladdend,  
      long   lincrement);  

 


what   could   be   simpler?   you   call   this   function,   passing   the   address   of   a   long   variable   and   indicating   by   how   much   to   increment   this   value.   but   this   function   guarantees   that   the   adding   of   the   value   is   accomplished   atomically.   so   we   can   rewrite   the   code   presented   earlier   as   follows:

//   define   a   global   variable.
long   g_x   =   0;

dword   winapi   threadfunc1(pvoid   pvparam)   {
      interlockedexchangeadd(&g_x,   1);
      return(0);
}

dword   winapi   threadfunc2(pvoid   pvparam)   {
      interlockedexchangeadd(&g_x,   1);
      return(0);
}

 


by   making   this   small   change,   g_x   is   incremented   atomically   and   therefore   you   are   guaranteed   that   the   final   value   in   g_x   will   be   2.   don 't   you   feel   better   already?   note   that   all   the   threads   should   attempt   to   modify   the   shared   long   variable   by   calling   these   functions;   no   thread   should   ever   attempt   to   modify   the   shared   variable   by   using   simple   c   statements:

//   the   long   variable   shared   by   many   threads
long   g_x;


//   incorrect   way   to   increment   the   long
g_x++;


//   correct   way   to   increment   the   long
interlockedexchangeadd(&g_x,   1);

 


how   do   the   interlocked   functions   work?   the   answer   depends   on   the   cpu   platform   that   you 're   running   on.   for   the   x86   family   of   cpus,   interlocked   functions   assert   a   hardware   signal   on   the   bus   that   prevents   another   cpu   from   accessing   the   same   memory   address.   on   the   alpha   platform,   the   interlocked   functions   do   something   like   this:

发表于:2007-04-14 11:55:072楼 得分:0

turn   on   a   special   bit   flag   in   the   cpu   and   note   the   memory   address   being   accessed.


read   the   value   from   memory   into   a   register.


modify   the   register.


if   the   special   bit   flag   in   the   cpu   is   off,   go   to   step   2.   otherwise,   the   special   bit   flag   is   still   on   and   the   register 's   value   is   stored   back   into   memory.

you   might   wonder   how   the   special   cpu   bit   flag   is   ever   turned   off   by   the   time   step   4   is   EXECuted.   here 's   the   answer:   if   another   cpu   in   the   system   attempts   to   modify   the   same   memory   address,   it   can   turn   off   our   cpu 's   special   bit   flag,   causing   the   interlocked   function   to   loop   back   to   step   2.

you   need   not   understand   exactly   how   the   interlocked   functions   work.   what 's   important   to   know   is   that   they   guarantee   that   a   value   will   be   modified   atomically,   no   matter   how   the   compiler   generates   code   and   no   matter   how   many   cpus   are   installed   in   the   host   machine.   you   must   also   ensure   that   the   variable   addresses   that   you   pass   to   these   functions   are   properly   aligned   or   the   functions   might   fail.   (i 'll   discuss   data   alignment   in   chapter   13.)

another   important   thing   to   know   about   the   interlocked   functions   is   that   they   EXECute   extremely   quickly.   a   call   to   an   interlocked   function   usually   causes   just   a   few   cpu   cycles   (usually   less   than   50)   to   EXECute,   and   there   is   no   transition   from   user   mode   to   kernel   mode   (which   usually   requires   more   than   1000   cycles   to   EXECute).

of   course,   you   can   use   interlockedexchangeadd   to   subtract   a   value—you   simply   pass   a   negative   value   for   the   second   parameter.   interlockedexchangeadd   returns   the   original   value   that   was   in   *pladdend.

here   are   two   more   interlocked   functions:

long   interlockedexchange(
      plong   pltarget,  
      long   lvalue);

pvoid   interlockedexchangepointer(
      pvoid*   ppvtarget,  
      pvoid   pvvalue);

 


interlockedexchange   and   interlockedexchangepointer   atomically   replace   the   current   value   whose   address   is   passed   in   the   first   parameter   with   a   value   passed   in   the   second   parameter.   for   a   32-bit   application,   both   functions   replace   a   32bit   value   with   another   32-bit   value.   but   for   a   64-bit   application,   interlockedexchange   replaces   a   32-bit   value   while   interlockedexchangepointer   replaces   a   64-bit   value.   both   functions   return   the   original   value.   interlockedexchange   is   extremely   useful   when   you   implement   a   spinlock:

//   global   variable   indicating   whether   a   shared   resource   is   in   use   or   not
bool   g_fresourceinuse   =   false;


发表于:2007-04-14 11:55:183楼 得分:0
void   func1()   {
      //   wait   to   access   the   resource.
      while   (interlockedexchange   (&g_fresourceinuse,   true)   ==   true)
            sleep(0);

      //   access   the   resource.

       

      //   we   no   longer   need   to   access   the   resource.
      interlockedexchange(&g_fresourceinuse,   false);
}

 


the   while   loop   spins   repeatedly,   changing   the   value   in   g_fresourceinuse   to   true   and   checking   its   previous   value   to   see   if   it   was   true.   if   the   value   was   previously   false,   the   resource   was   not   in   use   but   the   calling   thread   just   set   it   to   in-use   and   exits   the   loop.   if   the   previous   value   was   true,   the   resource   was   in   use   by   another   thread   and   the   while   loop   continues   to   spin.

if   another   thread   were   to   EXECute   similar   code,   it   would   spin   in   its   while   loop   until   the   g_fresourceinuse   was   changed   back   to   false.   the   call   to   interlockedexchange   at   the   end   of   the   function   shows   how   g_fresourceinuse   should   be   set   back   to   false.

you   must   take   extreme   care   when   using   this   technique   because   a   spinlock   wastes   cpu   time.   the   cpu   must   constantly   compare   two   values   until   one   "magically "   changes   due   to   another   thread.   also,   this   code   assumes   that   all   threads   using   the   spinlock   run   at   the   same   priority   level.   you   might   also   want   to   disable   thread   priority   boosting   (call   setprocesspriorityboost   or   setthreadpriorityboost)   for   threads   that   EXECute   spinlocks.

in   addition,   you   should   ensure   that   the   lock   variable   and   the   data   that   the   lock   protects   are   maintained   in   different   cache   lines   (discussed   later   in   this   chapter).   if   the   lock   variable   and   data   share   the   same   cache   line,   a   cpu   using   the   resource   will   contend   with   any   cpus   attempting   access   of   the   resource.   this   hurts   performance.

you   should   avoid   using   spinlocks   on   single-cpu   machines.   if   a   thread   is   spinning,   it 's   wasting   precious   cpu   time,   which   prevents   the   other   thread   from   changing   the   value.   my   use   of   sleep   in   the   while   loop   above   improves   this   situation   somewhat.   if   you   use   sleep,   you   might   want   to   sleep   a   random   amount   of   time,   and   each   time   the   request   to   access   the   resource   is   denied,   you   might   want   to   increase   the   sleep   time   even   more.   this   prevents   threads   from   simply   wasting   cpu   time.   depending   on   your   situation,   it   might   be   better   to   remove   the   call   to   sleep   altogether.   or   you   might   want   to   replace   it   with   a   call   to   switchtothread   (not   available   on   windows   98).   i   hate   to   say   it,   but   trial   and   error   might   be   your   best   approach.

spinlocks   assume   that   the   protected   resource   is   always   accessed   for   short   periods   of   time.   this   makes   it   more   efficient   to   spin   and   then   transition   to   kernel   mode   and   wait.   many   developers   spin   some   number   of   times   (say   4000),   and   if   access   to   the   resource   is   still   denied,   the   thread   transitions   to   kernel   mode,   where   it   waits   (consuming   no   cpu   time)   until   the   resource   becomes   available.   this   is   how   critical   sections   are   implemented.

spinlocks   are   useful   on   multiprocessor   machines   because   one   thread   can   spin   while   the   other   thread   runs   on   another   cpu.   however,   even   in   this   scenario,   you   must   be   careful.   you   do   not   want   a   thread   to   spin   for   a   long   time,   or   you 'll   waste   more   cpu   time.   we 'll   discuss   spinlocks   further   later   in   this   chapter.   also,   the   section   titled   "implementing   a   critical   section:   the   optex "   in   chapter   10   shows   how   to   use   spinlocks.

here   are   the   last   two   interlocked   functions:

pvoid   interlockedcompareexchange(
      plong   pldestination,  
      long   lexchange,  
      long   lcomparand);

pvoid   interlockedcompareexchangepointer(
      pvoid*   ppvdestination,
      pvoid   pvexchange,  
      pvoid   pvcomparand);  

 


these   two   functions   perform   an   atomic   test   and   set   operation:   for   a   32-bit   application,   both   functions   operate   on   32-bit   values,   but   in   a   64-bit   application,   interlockedcompareexchange   operates   on   32-bit   values   while   interlockedcompareexchangepointer   operates   on   64-bit   values.   in   pseudocode,   here   is   what   happens:

long   interlockedcompareexchange(plong   pldestination,
      long   lexchange,   long   lcomparand)   {

      long   lret   =   *pldestination;       //   original   value

      if   (*pldestination   ==   lcomparand)
            *pldestination   =   lexchange;
      return(lret);
}

 


the   function   compares   the   current   value   (pointed   to   by   the   pldestination   parameter)   with   the   value   passed   in   the   lcomparand   parameter.   if   the   values   are   the   same,   *pldestination   is   changed   to   the   value   of   the   lexchange   parameter.   if   what   is   in   *pldestination   doesn 't   match   the   value   of   lcomparand,   *pldestination   is   not   changed.   the   function   returns   the   original   value   in   *pldestination.   remember   that   all   of   these   operations   are   performed   as   one   atomic   unit   of   EXECution.

there   is   no   interlocked   function   that   simply   reads   a   value   (without   changing   it)   because   no   such   function   is   necessary.   if   a   thread   simply   attempts   to   read   the   contents   of   a   value   that   is   always   modified   with   an   interlocked   function,   the   value   read   is   always   a   good   value.   you   don 't   know   if   you 'll   read   the   original   value   or   the   updated   value,   but   you   know   that   it   will   be   one   of   them.   for   most   applications,   this   is   sufficient.   in   addition,   the   interlocked   functions   might   be   used   by   threads   in   multiple   processes   when   you 're   synchronizing   access   to   a   value   that   is   in   a   shared   memory   section   such   as   a   memory-mapped   file.   (chapter   9   includes   a   few   sample   applications   that   show   how   to   properly   use   the   interlocked   functions.)

windows   offers   a   few   other   interlocked   functions,   but   the   functions   i 've   described   do   everything   that   the   other   functions   do   and   more.   here   are   two   other   functions:

long   interlockedincrement(plong   pladdend);

long   interlockeddecrement(plong   pladdend);

 


interlockedexchangeadd   replaces   both   of   these   older   functions.   the   new   function   can   add   or   subtract   any   value;   the   old   functions   are   limited   to   adding   or   subtracting   1.
发表于:2007-04-15 19:16:354楼 得分:0
用原子操作即可,如上面说的interlockedincrement()
发表于:2007-04-15 21:21:295楼 得分:0
如果变量i是线程内的局部变量,不需要,否则就需要,总的来说,如果一个线程读写的是一个本线程内的局部变量,不需要互斥,而对于本线程外的变量,则一定要互斥,除非所有的线程都是对这个变量读
发表于:2007-04-16 09:52:366楼 得分:0
用interlockedincrement,不需要互斥,但是要对变量做保护


快速检索

最新资讯
热门点击