多线程编程中非常有用的提示和技巧

来源:互联网 发布:mac 读取exe 编辑:程序博客网 时间:2024/04/30 14:08

本文摘抄自《windows核心编程》

非常有用的提示和技巧

当使用关键代码段时,有些很好的方法可以使用,而有些方法则应该避免。下面是在使用

关键代码段时对你有所帮助的一些提示和技巧。这些技巧也适用于内核对象的同步(下一章介

绍)。

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_nNums[100]; // A shared resource

TCHAR g_cChars[100];  // Another shared resource

CRITICAL_SECTION g_cs; //Guard both resources

 

DWORD WINAPI ThreadFunc(PVOID pvParam)

{

EnterCriticalSection(&g_cs);

 

for (int x=0; x<100; x++){

g_nNums[x] = 0;

g_cChars[x] = TEXT('X');

}

 

LeaveCriticalSection(&g_cs);

return 0;

}

 

这个代码使用单个关键代码段,以便在g n N u m s数组和g c C h a r s数组初始化时对它们同时

实施保护。但是,这两个数组之间毫无关系。当这个循环运行时,没有一个线程能够访问任何

一个数组。如果T h r e a d F u n c函数按下面的形式来实现,那么两个数组将分别被初始化:

 

DWORD WINAPI ThreadFunc(PVOID pvParam)

{

EnterCriticalSection(&g_cs);

 

for (int x=0; x<100; x++)

g_nNums[x] = 0;

 

for (x=0; x<100; x++)

g_cChars[x] = TEXT('X');

 

LeaveCriticalSection(&g_cs);

return 0;

}

 

从理论上讲,当g n N u m s数组初始化后,另一个只需要访问g n N u m s数组而不需要访问

g c C h a r s数组的线程就可以开始执行,同时T h r e a d F u n c可以继续对g c C h a r s数组进行初始化。

但是实际上这是不可能的,因为有一个关键代码段保护着这两个数据结构。为了解决这个问题,

可以创建下面两个关键代码段:

 

int g_nNums[100]; // A shared resource

CRITICAL_SECTION g_csNums; //Guard g_csNums

 

TCHAR g_cChars[100];  // Another shared resource

CRITICAL_SECTION g_csChars; //Guard g_csChars

 

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 (int x=0; x<100; x++)

g_nNums[x] = 0;

 

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

 

for (int x=0; x<100; x++)

g_nNums[x] = g_cChars[x];

 

LeaveCriticalSection(&g_csChars);

LeaveCriticalSection(&g_csNums);

return 0;

}

 

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

 

DWORD WINAPI ThreadFunc(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);

 

//

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

//

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结构,而不是无限制地停止使用该结构。当然,这个方法

的前提是该结构的“瞬态图”应当做到非常好才行,以方便于窗口过程读取。此外,窗口过程

不需要改变该结构中的成员。