您的位置:首页 > 其它

用户方式中线程的同步

2010-07-29 07:08 204 查看
8.1 原子访问:互锁的函数家族
我们需要一种手段来保证值的递增能够以原子操作方式来进行,也就是不中断地进行。互锁的函数家族提供了我们需要的解决方案。互锁的函数尽管用处很大,而且很容易理解,却有些让人望而生畏,大多数软件开发人员用得很少。所有的函数都能以原子操作方式对一个值进行操作。让我们看一看下面这个I n t e r l o c k e dE x c h a n g e A d d函数:
LONG InterlockedExchangeAdd(
   PLONG plAddend,
   LONG Increment);


这是个最简单的函数了。只需调用这个函数,传递一个长变量地址,并指明将这个值递增多少即可。但是这个函数能够保证值的递增以原子操作方式来完成。因此可以将上面的代码重新编写为下面的形式:

// 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);
}


通过这个小小的修改,g _ x就能以原子操作方式来递增,因此可以确保g _ x中的值最后是2。这样是不是感到好一些?注意,所有线程都应该设法通过调用这些函数来修改共享的长变量,任何线程都不应该通过调用简单的C语句来修改共享的变量:

// 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);


不必清楚地了解互锁函数是如何工作的。重要的是要知道,无论编译器怎样生成代码,无论计算机中安装了多少个C P U,它们都能保证以原子操作方式来修改一个值。还必须保证传递给这些函数的变量地址正确地对齐,否则这些函数就会运行失败(第1 3章将介绍数据对齐问题)。

对于互锁函数,需要了解的另一个重要问题是,它们运行的速度极快。调用一个互锁函数通常会导致执行几个C P U周期(通常小于5 0),并且不会从用户方式转换为内核方式(通常这需要执行1 0 0 0个C P U周期)。

当然,可以使用I n t e r l o c k e d E x c h a n g e A d d减去一个值—只要为第二个参数传递一个负值。I n t e r l o c k e d E x c h a n g e A d d将返回在* p l A d d e n d中的原始值。

下面是另外两个互锁函数:

LONG InterlockedExchange(PLONG plTarget,
   LONG lValue);

PVOID InterlockedExchangePointer(PVOID* ppvTarget,
   PVOID pvValue);


I n t e r l o c k e d E x c h a n g e和I n t e r l o c k e d E x c h a n g e P o i n t e r能够以


原子操作方式用第二个参数中传递的值来取代第一个参数中传递的当前值。如果是3 2位应用程序,两个函数都能用另一个3 2位值


取代一个3 2位值。但是,如果是个6 4位应用程序,那么I n t e r l o c k e d E x c h a n g e能够取代一个3 2位值,


而I n t e r l o c k e d E x c h a n g e P o i n t e r则取代6 4位值。两个函数都返回原始值。当实现一个循环


锁时,I n t e r l o c k e d E x c h a n g e是非常有用的:


// Global variable indicating whether a shared resource is in use or not
BOOL g_fResourceInUse = FALSE;
...

void Func1() 
{
   //Wait to access the resource.
   while(InterlockedExchange(&g_fResourceInUse, TRUE) == TRUE)   //如果为TRUE,随机等待,循环查询
      Sleep(0);

   //Access the resource.

   ...

   //We no longer need to access the resource.
   InterlockedExchange(&g_fResourceInUse, FALSE);
}


w h i l e循环是循环运行的,它将g _ f R e s o u r c e I n U s e中的值改为T R U E,并查看它的前一个值,以了解它是否是T R U E。如果这个值原先是FA L S E,那么该资源并没有在使用,而是调用线程将它设置为在用状态并退出该循环。如果前一个值是T R U E,那么资源正在被另一个线程使用,w h i l e循环将继续循环运行。

如果另一个线程要执行类似的代码,它将在w h i l e循环中运行,直到g _ f R e s o u r c e I n U s e重新改为FA L S E。调用函数结尾处的I n t e r l o c k e d E x c h a n g e,可显示应该如何将g _ f R e s o u r c e I n U s e重新设置为FA L S E。

当使用这个方法时必须格外小心,因为循环锁会浪费C P U时间。C P U必须不断地比较两个值,直到一个值由于另一个线程而“奇妙地”改变为止。另外,该代码假定使用循环锁的所有线程都以相同的优先级等级运行。也可以把执行循环锁的线程的优先级提高功能禁用(通过调用S e t P r o c e s s P r i o r i t y B o o s t或s e t T h r e a d P r i o r i t y B o o s t函数来实现之)

此外,应该保证将循环锁变量和循环锁保护的数据维护在不同的高速缓存行中(本章后面部分介绍)。如果循环锁变量与数据共享相同的高速缓存行,那么使用该资源的C P U将与试图访问该资源的任何C P U争用高速缓存行。

循环锁在多处理器计算机上非常有用,因为当一个线程循环运行的时候,另一个线程可以在另一个C P U上运行。但是,即使在这种情况下,也必须小心。不应该让线程循环运行太长的时间,也不能浪费更多的C P U时间。

下面是最后两个互锁函数:

PVOID InterlockedCompareExchange(PLONG plDestination,
   LONG lExchange, LONG lComparand);

PVOID InterlockedCompareExchangePointer(PVOID* ppvDestination,
   PVOID pvExchange, PVOID pvComparand);


这两个函数负责执行一个原子测试和设置操作。如果是3 2位应用程序,那么两个函数都在3 2位值上运行,但是,如果是6 4位应用程序,I n t e r l o c k e d C o m p a r e E x c h a n g e函数在3 2位值上运行,而I n t e r l o c k e d C o m p a r e E x c h a n g e P o i n t e r函数则在6 4位值上运行。在伪代码中,它的运行情况如下面所示:

LONG InterlockedCompareExchange(PLONG plDestination,
   LONG lExchange, LONG lComparand)
{

   LONG lRet = *plDestination;  //Original value

   if(*plDestination == lComparand)
      *plDestination = lExchange;
   return(lRet);
}


该函数对当前值( p l D e s t i n a t i o n参数指向的值)与l C o m p a r a n d参数中传递的值进行比较。如果两个值相同,那么* p l D e s t i n a t i o n改为l E x c h a n g e参数的值。如果* p l D e s t i n a t i o n中的值与l E x c h a n g e的值不匹配, * p l D e s t i n a t i o n保持不变。该函数返回* p l D e s t i n a t i o n中的原始值。记住,所有这些操作都是作为一个原子执行单位来进行的。

虽然Wi n d o w s还提供了另外几个互锁函数,但是上面介绍的这些函数能够实现其他函数能做的一切功能,甚至更多。下面是两个其他的函数:

LONG InterlockedIncrement(PLONG plAddend);

LONG InterlockedDecrement(PLONG plAddend);


I n t e r l o c k e d E x c h a n g e A d d函数能够取代这些较老的函数。新函数能够递增或递减任何值,老的函数只能加1或减1。

8.3 高级线程同步

当线程想要访问共享资源,或者得到关于某个“特殊事件”的通知时,该线程必须调用一个操作系统函数,给它传递一些参数,以指明该线程正在等待什么。如果操作系统发现资源可供使用,或者该特殊事件已经发生,那么函数就返回,同时该线程保持可调度状态(该线程可以不必立即执行,它处于可调度状态,可以使用前一章介绍的原则将它分配给一个C P U)

如果资源不能使用,或者特殊事件还没有发生,那么系统便使该线程处于等待状态,使该线程无法调度。这可以防止线程浪费C P U时间。当线程处于等待状态时,系统作为一个代理,代表你的线程来执行操作。系统能够记住你的线程需要什么,当资源可供使用的时候,便自动使该线程退出等待状态,该线程的运行将与特殊事件实现同步。

从实际情况来看,大多数线程几乎总是处于等待状态。当系统发现所有线程有若干分钟均处于等待状态时,系统的强大的管理功能就会发挥作用。

8.4 关键代码段

关键代码段是指一个小代码段,在代码能够执行前,它必须独占对某些共享资源的访问权。这是让若干行代码能够“以原子操作方式”来使用资源的一种方法。所谓原子操作方式,是指该代码知道没有别的线程要访问该资源。当然,系统仍然能够抑制你的线程的运行,而抢先安排其他线程的运行。不过,在线程退出关键代码段之前,系统将不给想要访问相同资源的其他任何线程进行调度。

const int MAX_TIMES = 1000;
int   g_nIndex = 0;
DWORD g_dwTimes[MAX_TIMES];
CRITICAL_SECTION g_cs;

DWORD WINAPI FirstThread(PVOID pvParam) 
{
   while(g_nIndex < MAX_TIMES) 
   {
      EnterCriticalSection(&g_cs);
      g_dwTimes[g_nIndex] = GetTickCount();
      g_nIndex++;
      LeaveCriticalSection(&g_cs);
   }
   return(0);
}

DWORD WINAPI SecondThread(PVOID pvParam) 
{
   while(g_nIndex < MAX_TIMES)
   {
      EnterCriticalSection(&g_cs);
      g_nIndex++;
      g_dwTimes[g_nIndex - 1] = GetTickCount();
      LeaveCriticalSection(&g_cs);
   }
   return(0);
}


这里指定了一个C R I T I C A L _ S E C T I O N数据结构g _ c s,然后在对E n t e r C r i t i c a l S e c t i o n和L e a v e C r i t i c a lS e c t i o n函数调用中封装了要接触共享资源(在这个例子中为g _ n I n d e x和g _ d w Ti m e s)的任何代码。注意,在对E n t e r C r i t i c a l S e c t i o n和L e a v e C r i t i c a l S e c t i o n的所有调用中,我传递了g _ c s的地址。

有一个关键问题必须记住。当拥有一项可供多个线程访问的资源时,应该创建一个C R I T I C A L _ S E C T I O N结构。

现在,无论在何处拥有需要访问资源的代码,都必须调用E n t e r C r i t i c a l S e c t i o n函数,为它传递用于标识该资源的C R I T I C A L _ S E C T I O N结构的地址。这就是说,当一个线程需要访问一个资源时,它首先必须检查厕所门上的“有人”标志。C R I T I C A L _ S E C T I O N结构用于标识线程想要进入哪个厕所,而E n t e r C r i t i c a l S e c t i o n函数则是线程用来检查“有人”标志的函数。

如果E n t e r C r i t i c a l S e c t i o n函数发现厕所中没有任何别的线程(门上的标志显示“无人”),那么调用线程就可以使用该资源。如果E n t e r C r i t i c a l S e c t i o n发现厕所中有另一个线程正在使用,那么调用函数必须在厕所门的外面等待,直到厕所中的另一个线程离开厕所。

当一个线程不再执行需要访问资源的代码时,它应该调用L e a v e C r i t i c a l S e c t i o n函数。这样,它就告诉系统,它准备离开包含该资源的厕所。如果忘记调用L e a v e C r i t i c a l S e c t i o n,系统将认为该线程仍然在厕所中,因此不允许其他正在等待的线程进入厕所。这就像离开了厕所但没有换上“无人”的标志。

注意最难记住的一件事情是,编写的需要使用共享资源的任何代码都必须封装在E n t e r C r i t i c a l S e c t i o n和L e a v e C r i t i c a l S e c t i o n函数中。如果忘记将代码封装在一个位置,共享资源就可能遭到破坏。例如,如果我删除了F r i s t T h r e a d线程对E n t e r C r i t i c a l S e c t i o n和L e a v e C r i t i c a l S e c t i o n的调用, g _ n I n d e x和g _ d w Ti m e s变量就会遭到破坏。即使S e c o n d T h r e a d线程仍然正确地调用E n t e r C r i t i c a l S e c t i o n和L e a v e C r i t i c a l S e c t i o n,也会出现这种情况。

当无法用互锁函数来解决同步问题时,你应该试用关键代码段。关键代码段的优点在于它们的使用非常容易,它们在内部使用互锁函数,这样它们就能够迅速运行。关键代码的主要缺点是无法用它们对多个进程中的各个线程进行同步。

8.4.1 关键代码段准确的描述

 

 

通常情况下,C R I T I C A L _ S E C T I O N结构可以作为全局变量来分配,这样,进程中的所有线程就能够很容易地按照变量名来引用该结构。但是, C R I T I C A L _ S E C T I O N结构也可以作为局部变量来分配,或者从堆栈动态地进行分配。它只有两个要求,第一个要求是,需要访问该资源的所有线程都必须知道负责保护资源的C R I T I C A L _ S E C T I O N结构的地址,你可以使用你喜欢的任何机制来获得这些线程的这个地址;第二个要求是, C R I T I C A L _ S E C T I O N结构中的成员应该在任何线程试图访问被保护的资源之前初始化。该结构通过调用下面的函数来进行初始化:

VOID InitializeCriticalSection(PCRITICAL_SECTION pcs);


该函数用于对(p c s指向的)C R I T I C A L _ S E C T I O N结构的各个成员进行初始化。由于该函数只是设置了某些成员变量。因此它的运行不会失败,并且它的原型采用了V O I D的返回值。该函数必须在任何线程调用E n t e r C r i t i c a l S e c t i o n函数之前被调用。Platform SDK的文档清楚地说明,如果一个线程试图进入一个未初始化的C RT I C A L _ S E C T I O N,那么结果将是很难预计的。

当知道进程的线程不再试图访问共享资源时,应该通过调用下面的函数来清除该C R I T I C A L _ S E C T I O N结构:

VOID DeleteCriticalSection(PCRITICAL_SECTION pcs);


D e l e t e C r i t i c a l S e c t i o n函数用于对该结构中的成员变量进行删除。当然,如果有任何线程仍然使用关键代码段,那么不应该删除该代码段。同样, Platform SDK文档清楚地说明如果删除了关键代码段,其结果就无法知道。当编写要使用共享资源的代码时,必须在该代码的前面放置对下面的函数的调用:

VOID EnterCriticalSection(PCRITICAL_SECTION pcs);


E n t e r C r i t i c a l S e c t i o n函数负责查看该结构中的成员变量。这些变量用于指明当前是哪个变量正在访问该资源。E n t e r C r i t i c a l S e c t i o n负责进行下列测试:

• 如果没有线程访问该资源,E n t e r C r i t i c a l S e c t i o n便更新成员变量,以指明调用线程已被赋予访问权并立即返回,使该线程能够继续运行(访问该资源)。

• 如果成员变量指明,调用线程已经被赋予对资源的访问权,那么E n t e r C r i t i c a l S e c t i o n便更新这些变量,以指明调用线程多少次被赋予访问权并立即返回,使该线程能够继续运行。这种情况很少出现,并且只有当线程在一行中两次调用E n t e r C r i t i c a l S e c t i o n而不影响对L e a v e C r i t i c a l S e c t i o n的调用时,才会出现这种情况。

• 如果成员变量指明,一个线程(除了调用线程之外)已被赋予对资源的访问权,那么E n e r C r i t i c a l S e c t i o n将调用线程置于等待状态。这种情况是极好的,因为等待的线程不会浪费任何C P U 时间。系统能够记住该线程想要访问该资源并且自动更新C R I T I C A L _ S E C T I O N的成员变量,一旦目前访问该资源的线程调用L e a v e C r i t i c a l S e c t i o n函数,该线程就处于可调度状态。

从内部来讲, E n t e r C r i t i c a l S e c t i o n函数并不十分复杂。它只是执行一些简单的测试。为什么这个函数是如此有用呢?因为它能够以原子操作方式来执行所有的测试。如果在多处理器计算机上有两个线程在完全相同的时间同时调用E n t e r C r i t i c a l S e c t i o n函数,该函数仍然能够正确地起作用,一个线程被赋予对资源的访问权,而另一个线程则进入等待状态。

如果E n t e r C r i t i c a l S e c t i o n将一个线程置于等待状态,那么该线程在很长时间内就不能再次被调度。实际上,在编写得不好的应用程序中,该线程永远不会再次被赋予C P U时间。如果出现这种情况,该线程就称为渴求C P U时间的线程。

 

可以使用下面这个函数来代替E n t e r C r i t i c a l S e c t i o n:

BOOL TryEnterCriticalSection(PCRITICAL_SECTION pcs);


Tr y E n t e r C r i t i c a l S e c t i o n函数决不允许调用线程进入等待状态。相反,它的返回值能够指明调用线程是否能够获得对资源的访问权。因此,如果Tr y E n t e r C r i t i c a l S e c t i o n发现该资源已经被另一个线程访问,它就返回FA L S E。在其他所有情况下,它均返回T R U E。

运用这个函数,线程能够迅速查看它是否可以访问某个共享资源,如果不能访问,那么它可以继续执行某些其他操作,而不必进行等待。如果Tr y E n t e r C r i t i c a l S e c t i o n函数确实返回了T R U E,那么C R I T I C A L _ S E C T I O N的成员变量已经更新,以便反映出该线程正在访问该资源。因此,对返回T R U E的Tr y E n t e r C r i t i c a l S e c t i o n函数的每次调用都必须与对L e a v e C r i t i c a l S e c t i o n函数的调用相匹配。

 

在接触共享资源的代码结尾处,必须调用下面这个函数:

VOID LeaveCriticalSection(PCRITICAL_SECTION pcs);


L e a v e C r i t i c a l S e c t i o n要查看该结构中的成员变量。该函数每次计数时要递减1,以指明调用线程多少次被赋予对共享资源的访问权。如果该计数大于0,那么L e a v e C r i t i c a l S e c t i o n不做其他任何操作,只是返回而已。

如果该计数变为0,它就要查看在调用E n t e r C r i t i c a l S e c t i o n中是否有别的线程正在等待。如果至少有一个线程正在等待,它就更新成员变量,并使等待线程中的一个线程(“公正地”选定)再次处于可调度状态。如果没有线程正在等待, L e a v e C r i t i c a l S e c t i o n函数就更新成员变量,以指明没有线程正在访问该资源。

与E n t e r C r i t i c a l S e c t i o n函数一样,L e a v e C r i t i c a l S e c t i o n函数也能以原子操作方式执行所有这些测试和更新。不过,L e a v e C r i t i c a l S e c t i o n从来不使线程进入等待状态,它总是立即返回。

 

8.4.2 关键代码段与循环锁   //不适用于WinCE

 

当线程试图进入另一个线程拥有的关键代码段时,调用线程就立即被置于等待状态。这意味着该线程必须从用户方式转入内核方式(大约1 0 0 0个C P U周期)。这种转换是要付出很大代价的。在多处理器计算机上,当前拥有资源的线程可以在不同的处理器上运行,并且能够很快放弃对资源的控制。实际上拥有资源的线程可以在另一个线程完成转入内核方式之前释放资源。如果出现这种情况,就会浪费许多C P U时间。

为了提高关键代码段的运行性能, M i c r o s o f t将循环锁纳入了这些代码段。因此,当E n t e r C r i t i c a l S e c t i o n函数被调用时,它就使用循环锁进行循环,以便设法多次取得该资源。只有当为了取得该资源的每次试图都失败时,该线程才转入内核方式,以便进入等待状态。

 

若要将循环锁用于关键代码段,应该调用下面的函数,以便对关键代码段进行初始化:

BOOL InitializeCriticalSectionAndSpinCount(
   PCRITICAL_SECTION pcs, DWORD dwSpinCount);


与I n i t i a l i z e C r i t i c a l S e c t i o n中的情况一样,I n i t i a l i z e C r i t i c a l S e c t i o n A n d S p i n C o u n t的第一个参数是关键代码段结构的地址。但是在第二个参数d w S p i n C o u n t中,传递的是在使线程等待之前它试图获得资源时想要循环锁循环迭代的次数。这个值可以是0至0 x 0 0 F F F F F F之间的任何数字。如果在单处理器计算机上运行时调用该函数, d w S p i n C o u n t参数将被忽略,它的计数始终被置为0。这是对的,因为在单处理器计算机上设置循环次数是毫无用处的,如果另一个线程正在循环运行,那么拥有资源的线程就不能放弃它。

通过调用下面的函数,就能改变关键代码段的循环次数:

DWORD SetCriticalSectionSpinCount(
   PCRITICAL_SECTION pcs, DWORD dwSpinCount);


同样,如果主计算机只有一个处理器,那么d w S p i n C o u n t的值将被忽略。我认为,始终都应该将循环锁用于关键代码段,因为这样做有百利而无一害。难就难在确定为d w S p i n C o u n t参数传递什么值。为了实现最佳的性能,只需要调整这些数字,直到对性能结果满意为止。作为一个指导原则,保护对进程的堆栈进行访问的关键代码段使用的循环次数是4 0 0 0次。

8.4.4 非常有用的提示和技巧

当使用关键代码段时,有些很好的方法可以使用,而有些方法则应该避免。下面是在使用关键代码段时对你有所帮助的一些提示和技巧。这些技巧也适用于内核对象的同步(下一章介绍)。

1. 每个共享资源使用一个C R I T I C A L _ S E C T I O N变量

如果应用程序中拥有若干个互不相干的数据结构,应该为每个数据结构创建一个C R I T I C A L _ S E C T I O N变量。这比只有单个C R I T I C A L _ S E C T I O N结构来保护对所有共享资源的访问要好。

int g_nNum[100];            //A shared resource
CRITICAL_SECTION g_csNums;  //Guards g_nNums

TCHAR g_cChars[100];        //Another shared resource
CRITICAL_SECTION g_csChars; //Guards g_cChars

DWORD WINAPI ThreadFunc(PVOID pvParam) 
{
   EnterCriticalSection(&g_csNums);

   for(int x = 0; x < 100; x++)
      g_nNums[x] = 0;

   LeaveCriticalSection(&g_csNums);

   EnterCriticalSection(&g_csChars);

   for(x = 0; x < 100; x++)
      g_cChars[x] = TEXT('X');

   LeaveCriticalSection(&g_ csChars);
   return(0);
}


运用这个实现代码,一旦T h r e a d F u n c完成对g _ n N u m s数组的初始化,另一个线程就可以开始使用g _ n N u m s数组。也可以考虑让一个线程对g _ n N u m s数组进行初始化,而另一个线程函数对g _ n C h a r s数组进行初始化。

2. 同时访问多个资源

有时需要同时访问两个资源。如果这是T h r e a d F u n c的要求,可以用下面的代码来实现:

DWORD WINAPI ThreadFunc(PVOID pvParam)
{
   EnterCriticalSection(&g_csNums);
   EnterCriticalSection(&g_csChars);

   //This loop requires simultaneous access to both resources.
   for(int x = 0; x < 100; x++)
      g_nNums[x] = g_cChars[x];

   LeaveCriticalSection(&g_csChars);
   LeaveCriticalSection(&g_csNums);
   return(0);
}


假定下面这个函数的进程中的另一个线程也要求访问这两个数组:

DWORD WINAPI OtherThreadFunc(PVOID pvParam) 
{
   EnterCriticalSection(&g_csChars);
   EnterCriticalSection(&g_csNums);

   for(int x = 0; x < 100; x++)
      g_nNums[x] = g_cChars[x];

   LeaveCriticalSection(&g_csNums);
   LeaveCriticalSection(&g_csChars);
   return(0);
}


在上面这个函数中我只是切换了对E n t e r C r i t i c a l S e c t i o n和L e a v e C r i t i c a l S e c t i o n函数的调用顺序。但是,由于这两个函数是按上面这种方式编写的,因此可能产生一个死锁状态。假定T h r e a d F u n c开始执行,并且获得了g _ c s N u m s关键代码段的所有权,那么执行O t h e r T h r e a d F u n c函数的线程就被赋予一定的C P U时间,并可获得g _ c s C h a r s关键代码段的所有权。这时就出现了一个死锁状态。当T h r e a d F u n c或O t h e r T h r e a d F u n c中的任何一个函数试图继续执行时,这两个函数都无法取得对它需要的另一个关键代码段的所有权。

为了解决这个问题,必须始终按照完全相同的顺序请求对资源的访问。注意,当调用L e a v e C r i t i c a l S e c t i o n函数时,按照什么顺序访问资源是没有关系的,因为该函数决不会使线程进入等待状态。

 

3. 不要长时间运行关键代码段

当一个关键代码段长时间运行时,其他线程就会进入等待状态,这会降低应用程序的运行性能。下面这个方法可以用来最大限度地减少关键代码段运行所花费的时间。这个代码能够防止其他线程在W M _ S O M E M S G消息发送到一个窗口之前改变g _ s的值:

SOMESTRUCT g_s;
CRITICAL_SECTION g_cs;

DWORD WINAPI SomeThread(PVOID pvParam) 
{
   EnterCriticalSection(&g_cs);

   //Send a message to a window.
   SendMessage(hwndSomeWnd, WM_SOMEMSG, &g_s, 0);

   LeaveCriticalSection(&g_cs);
   return(0);
}


无法确定窗口过程处理W M _ S O M E M S G消息时需要花费多长时间,它可能是几个毫秒,也可能需要几年时间。在这个时间内,其他线程都不能访问g _ s结构。这个代码最好编写成下面的形式:

SOMESTRUCT g_s;
CRITICAL_SECTION g_cs;

DWORD WINAPI SomeThread(PVOID pvParam) 
{
   EnterCriticalSection(&g_cs);
   SOMESTRUCT sTemp = g_s;
   LeaveCriticalSection(&g_cs);

   //Send a message to a window.
   SendMessage(hwndSomeWnd, WM_SOMEMSG, &sTemp, 0);
   return(0);
}


这个代码将该值保存在临时变量s Te m p中。也许你能够猜到C P U需要多长时间来执行这行代码—只需要几个C P U周期。当该临时变量保存后,L e a v e C r i t i c a l S e c t i o n函数就立即被调用,因为这个全局结构不再需要保护。上面的第二个实现代码比第一个要好得多,因为其他线程只是在几个C P U周期内被停止使用g _ s结构,而不是无限制地停止使用该结构。当然,这个方法的前提是该结构的“瞬态图”应当做到非常好才行,以方便于窗口过程读取。此外,窗口过程不需要改变该结构中的成员。

 

 

 

 

 

 

 

 

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: