第1章 用户方式中线程的同步
1.仅一条语句用不用考虑线程同步的问题?
当使用高级语言编程时,我们往往会认为一条语句是最小的原子访问,CPU不会在这条语句中间运行其他的线程。这是错误的,因为即使非常简单的一条高级语言的语句,经编译器编译后也可能变成多行代码由计算机来执行。因此必须考虑线程同步的问题。任何线程都不应该通过调用简单的C语句来修改共享的变量。
2. 互锁函数有那些?
(1) LONG InterlockedExchangeAdd ( LPLONG Addend, LONG Increment );
Addend为长整型变量的地址,Increment为想要在Addend指向的长整型变量上增加的数值(可以是负数)。这个函数的主要作用是保证这个加操作为一个原子访问。
(2) LONG InterlockedExchange( LPLONG Target, LONG Value );
用第二个参数的值取代第一个参数指向的值。函数返回值为原始值。
(3) PVOID InterlockedExchangePointer( PVOID *Target, PVOID Value );
用第二个参数的值取代第一个参数指向的值。函数返回值为原始值。
(4) LONG InterlockedCompareExchange(
LPLONG Destination, LONG Exchange, LONG Comperand );
如果第三个参数与第一个参数指向的值相同,那么用第二个参数取代第一个参数指向的值。函数返回值为原始值。
(5) PVOID InterlockedCompareExchangePointer (
PVOID *Destination, PVOID Exchange, PVOID Comperand );
如果第三个参数与第一个参数指向的值相同,那么用第二个参数取代第一个参数指向的值。函数返回值为原始值。
3.为什么单CPU的计算机不应该使用循环锁?
举例说明:
BOOL g_bResourceUse = FALSE;
……
void ThreadFunc1( )
{
BOOL bResourceUse = FALSE;
while( 1 )
{
bResourceUse = InterlockedExchange( &g_bResourceUse, TRUE );
if( bResourceUse == FALSE )
{
break;
}
Sleep( 0 );
}
……
……
……
InterlockedExchange( &g_bResourceUse, FALSE );
}
首先循环锁会浪费CPU时间。CPU必须不断地比较两个值,直到一个值由于另一个线程而“奇妙地”改变为止。而且使用该循环锁的线程都应该为同一优先级,并且应当使用SetProcessPriorityBoost函数或SetThreadPriorityBoost函数禁止线程优先级的动态提高功能,否则优先级较低的线程可能永远不能被调用。
4.如何使用volatile声明变量?
如果是对共享资源的地址进行使用如&g_Resource那么可以不使用volatile,因为将一个变量地址传递给一个函数时,该函数必须从内存读取该值。优化程序不会对它产生任何影响。如果直接使用变量,必须有一个volatile类型的限定词。它告诉编译器,变量可以被应用程序本身以外的某个东西进行修改,这些东西包括操作系统,硬件或同时执行的线程等。volatile限定词会告诉编译器,不要对该变量进行任何优化,并且总是重新加载来自该变量的内存单元的值。否则编译器会把变量的值存入CPU寄存器,每次对寄存器进行操作。线程就会进入一个无限循环,永远无法唤醒。
5.如何使用关键代码段实现线程的同步?
如果需要一小段代码以原子操作的方式执行,这时简单的互锁函数已不能满足需要,必须使用关键代码段来解决问题。不过使用关键代码段时,很容易陷入死锁状态,因为在等待进入关键代码段时无法设定超时值。关键代码段是通过对共享资源设置一个标志来实现的,就像厕所门上的“有人/没人”标志一样。这个标志就是一个CRITICAL_SECTION变量。该变量在任何一个线程使用它之前应当进行初始化。初始化可以有两种方法,使用InitializeCriticalSection函数和InitializeCriticalSectionAndSpinCount函数。然后在每个使用共享资源的线程函数的关键代码段前使用EnterCriticalSection函数或者使用TryEnterCriticalSection函数。在关键代码段使用之后调用LeaveCriticalSection函数。在所有的线程都不再使用该共享资源后应当调用DeleteCriticalSection函数来清除该标志。举例说明:
const int MAX_TIMES = 1000;
int g_intIndex = 0;
DWORD g_dwTimes[MAX_TIMES];
CRITICAL_SECTION g_cs;
void Init( )
{
……
InitializeCriticalSection( &g_cs );
……
}
DWORD WINAPI FirstThread( PVOID lpParam )
{
while ( g_intIndex < MAX_TIMES )
{
EnterCriticalSection( &g_cs );
g_dwTimes[g_intIndex] = GetTickCount( );
g_intIndex++;
LeaveCriticalSection( &g_cs );
}
return 0;
}
DWORD WINAPI SecondThread( PVOID lpParam )
{
while ( g_intIndex < MAX_TIMES )
{
EnterCriticalSection( &g_cs );
g_intIndex++;
g_dwTimes[g_intIndex - 1] = GetTickCount( );
LeaveCriticalSection( &g_cs );
}
return 0;
}
void Close( )
{
……
DeleteCriticalSection( &g_cs );
……
}
使用关键代码段应当注意一些技巧:
(1)每个共享资源使用一个CRITICAL_SECTION变量。
这样在当前线程占有一个资源时,另一个资源可以被其他线程占有。
EnterCriticalSection( &g_cs );
for ( intLoop = 0; intLoop < 100; intLoop++ )
{
g_intArray[intLoop] = 0;
g_uintArray[intLoop] = 0;
}
LeaveCriticalSection( &g_cs );
改为:
EnterCriticalSection( &g_csInt );
for ( intLoop = 0; intLoop < 100; intLoop++ )
{
g_intArray[intLoop] = 0;
}
LeaveCriticalSection( &g_csInt );
EnterCriticalSection( &g_csUint );
for ( intLoop = 0; intLoop < 100; intLoop++ )
{
g_uintArray[intLoop] = 0;
}
LeaveCriticalSection( &g_csUint );
(2)同时访问多个资源,必须始终按照完全相同的顺序请求对资源的访问。
这样才能避免死锁状态产生。离开的顺序没有关系。
Thread1:
EnterCriticalSection( &g_csInt );
EnterCriticalSection( &g_csUint );
for ( intLoop = 0; intLoop < 100; intLoop++ )
{
g_uintArray[intLoop] = g_intArray[intLoop];
}
LeaveCriticalSection( &g_csInt );
LeaveCriticalSection( &g_csUint );
Thread2:
EnterCriticalSection( &g_csUint );
EnterCriticalSection( &g_csInt );
for ( intLoop = 0; intLoop < 100; intLoop++ )
{
g_uintArray[intLoop] = g_intArray[intLoop];
}
LeaveCriticalSection( &g_csInt );
LeaveCriticalSection( &g_csUint );
改为:
Thread1:
EnterCriticalSection( &g_csInt );
EnterCriticalSection( &g_csUint );
for ( intLoop = 0; intLoop < 100; intLoop++ )
{
g_uintArray[intLoop] = g_intArray[intLoop];
}
LeaveCriticalSection( &g_csInt );
LeaveCriticalSection( &g_csUint );
Thread2:
EnterCriticalSection( &g_csInt );
EnterCriticalSection( &g_csUint );
for ( intLoop = 0; intLoop < 100; intLoop++ )
{
g_uintArray[intLoop] = g_intArray[intLoop];
}
LeaveCriticalSection( &g_csInt );
LeaveCriticalSection( &g_csUint );
(3)不要长时间运行关键代码段。
EnterCriticalSection( &g_cs );
SendMessage( hWnd, WM_SOMEMSG, &g_s, 0 );
LeaveCriticalSection( &g_cs );
改为:
EnterCriticalSection( &g_cs );
sTemp = g_s;
LeaveCriticalSection( &g_cs );
SendMessage( hWnd, WM_SOMEMSG, &sTemp, 0 );
6.InitializeCriticalSection/InitializeCriticalSectionAndSpinCount差别?
InitializeCriticalSection函数的返回值为空并且不会创建事件内核对象,比较节省系统资源,但是一旦发生两个或多个线程争用关键代码段的情况,如果内存不足,关键代码段可能被争用,同时系统可能无法创建必要的事件内核对象。这时EnterCriticalSection函数将会产生一个EXCEPTION_INVALID_HANDLE异常。这个错误非常少见。如果想对这种情况有所准备,可以有两种选择。可以使用结构化异常处理方法来跟踪错误。当错误发生时,既可以不访问关键代码段保护的资源,也可以等待某些内存变成可用状态,然后再次调用EnterCriticalSection函数。
另一种选择是使用InitializeCriticalSectionAndSpinCount,第二个参数dwSpinCount中,传递的是在使线程等待之前它试图获得资源时想要循环锁循环迭代的次数。这个值可以是0至0x00FFFFFF之间的任何数字。如果在单处理器计算机上运行时调用该函数,该参数被忽略,并且始终设置为0。使用InitializeCriticalSectionAndSpinCount函数创建关键代码段,确保设置了dwSpinCount参数的高信息位。当该函数发现高信息位已经设置时,它就创建该事件内核对象,并在初始化时将它与关键代码段关联起来。如果事件无法创建,该函数返回FALSE。可以更加妥善地处理代码中的这个事件。如果事件创建成功,EnterCriticalSection将始终都能运行,并且决不会产生异常情况(如果总是预先分配事件内核对象,就会浪费系统资源。只有当代码不能容许EnterCriticalSection运行失败,或者有把握会出现争用现象,或者预计进程将在内存非常短缺的环境中运行时,才能预先分配事件内核对象)。
7.TryEnterCriticalSection和EnterCriticalSection的差别是什么?
如果EnterCriticalSection将一个线程置于等待状态,那么该线程在很长时间内就不能再次被调度。实际上,在编写得不好的应用程序中,该线程永远不会再次被赋予CPU时间。TryEnterCriticalSection函数决不允许调用线程进入等待状态。它的返回值能够指明调用线程是否能够获得对资源的访问权。TryEnterCriticalSection发现该资源已经被另一个线程访问,它就返回FALSE。在其他所有情况下,它均返回TRUE。运用这个函数,线程能够迅速查看它是否可以访问某个共享资源,如果不能访问,那么它可以继续执行某些其他操作,而不必进行等待。如果TryEnterCriticalSection函数确实返回了TRUE,那么CRITICAL_SECTION的成员变量已经更新。Windows98没有可以使用的TryEnterCriticalSection函数的实现代码。
1.仅一条语句用不用考虑线程同步的问题?
当使用高级语言编程时,我们往往会认为一条语句是最小的原子访问,CPU不会在这条语句中间运行其他的线程。这是错误的,因为即使非常简单的一条高级语言的语句,经编译器编译后也可能变成多行代码由计算机来执行。因此必须考虑线程同步的问题。任何线程都不应该通过调用简单的C语句来修改共享的变量。
2. 互锁函数有那些?
(1) LONG InterlockedExchangeAdd ( LPLONG Addend, LONG Increment );
Addend为长整型变量的地址,Increment为想要在Addend指向的长整型变量上增加的数值(可以是负数)。这个函数的主要作用是保证这个加操作为一个原子访问。
(2) LONG InterlockedExchange( LPLONG Target, LONG Value );
用第二个参数的值取代第一个参数指向的值。函数返回值为原始值。
(3) PVOID InterlockedExchangePointer( PVOID *Target, PVOID Value );
用第二个参数的值取代第一个参数指向的值。函数返回值为原始值。
(4) LONG InterlockedCompareExchange(
LPLONG Destination, LONG Exchange, LONG Comperand );
如果第三个参数与第一个参数指向的值相同,那么用第二个参数取代第一个参数指向的值。函数返回值为原始值。
(5) PVOID InterlockedCompareExchangePointer (
PVOID *Destination, PVOID Exchange, PVOID Comperand );
如果第三个参数与第一个参数指向的值相同,那么用第二个参数取代第一个参数指向的值。函数返回值为原始值。
3.为什么单CPU的计算机不应该使用循环锁?
举例说明:
BOOL g_bResourceUse = FALSE;
……
void ThreadFunc1( )
{
BOOL bResourceUse = FALSE;
while( 1 )
{
bResourceUse = InterlockedExchange( &g_bResourceUse, TRUE );
if( bResourceUse == FALSE )
{
break;
}
Sleep( 0 );
}
……
……
……
InterlockedExchange( &g_bResourceUse, FALSE );
}
首先循环锁会浪费CPU时间。CPU必须不断地比较两个值,直到一个值由于另一个线程而“奇妙地”改变为止。而且使用该循环锁的线程都应该为同一优先级,并且应当使用SetProcessPriorityBoost函数或SetThreadPriorityBoost函数禁止线程优先级的动态提高功能,否则优先级较低的线程可能永远不能被调用。
4.如何使用volatile声明变量?
如果是对共享资源的地址进行使用如&g_Resource那么可以不使用volatile,因为将一个变量地址传递给一个函数时,该函数必须从内存读取该值。优化程序不会对它产生任何影响。如果直接使用变量,必须有一个volatile类型的限定词。它告诉编译器,变量可以被应用程序本身以外的某个东西进行修改,这些东西包括操作系统,硬件或同时执行的线程等。volatile限定词会告诉编译器,不要对该变量进行任何优化,并且总是重新加载来自该变量的内存单元的值。否则编译器会把变量的值存入CPU寄存器,每次对寄存器进行操作。线程就会进入一个无限循环,永远无法唤醒。
5.如何使用关键代码段实现线程的同步?
如果需要一小段代码以原子操作的方式执行,这时简单的互锁函数已不能满足需要,必须使用关键代码段来解决问题。不过使用关键代码段时,很容易陷入死锁状态,因为在等待进入关键代码段时无法设定超时值。关键代码段是通过对共享资源设置一个标志来实现的,就像厕所门上的“有人/没人”标志一样。这个标志就是一个CRITICAL_SECTION变量。该变量在任何一个线程使用它之前应当进行初始化。初始化可以有两种方法,使用InitializeCriticalSection函数和InitializeCriticalSectionAndSpinCount函数。然后在每个使用共享资源的线程函数的关键代码段前使用EnterCriticalSection函数或者使用TryEnterCriticalSection函数。在关键代码段使用之后调用LeaveCriticalSection函数。在所有的线程都不再使用该共享资源后应当调用DeleteCriticalSection函数来清除该标志。举例说明:
const int MAX_TIMES = 1000;
int g_intIndex = 0;
DWORD g_dwTimes[MAX_TIMES];
CRITICAL_SECTION g_cs;
void Init( )
{
……
InitializeCriticalSection( &g_cs );
……
}
DWORD WINAPI FirstThread( PVOID lpParam )
{
while ( g_intIndex < MAX_TIMES )
{
EnterCriticalSection( &g_cs );
g_dwTimes[g_intIndex] = GetTickCount( );
g_intIndex++;
LeaveCriticalSection( &g_cs );
}
return 0;
}
DWORD WINAPI SecondThread( PVOID lpParam )
{
while ( g_intIndex < MAX_TIMES )
{
EnterCriticalSection( &g_cs );
g_intIndex++;
g_dwTimes[g_intIndex - 1] = GetTickCount( );
LeaveCriticalSection( &g_cs );
}
return 0;
}
void Close( )
{
……
DeleteCriticalSection( &g_cs );
……
}
使用关键代码段应当注意一些技巧:
(1)每个共享资源使用一个CRITICAL_SECTION变量。
这样在当前线程占有一个资源时,另一个资源可以被其他线程占有。
EnterCriticalSection( &g_cs );
for ( intLoop = 0; intLoop < 100; intLoop++ )
{
g_intArray[intLoop] = 0;
g_uintArray[intLoop] = 0;
}
LeaveCriticalSection( &g_cs );
改为:
EnterCriticalSection( &g_csInt );
for ( intLoop = 0; intLoop < 100; intLoop++ )
{
g_intArray[intLoop] = 0;
}
LeaveCriticalSection( &g_csInt );
EnterCriticalSection( &g_csUint );
for ( intLoop = 0; intLoop < 100; intLoop++ )
{
g_uintArray[intLoop] = 0;
}
LeaveCriticalSection( &g_csUint );
(2)同时访问多个资源,必须始终按照完全相同的顺序请求对资源的访问。
这样才能避免死锁状态产生。离开的顺序没有关系。
Thread1:
EnterCriticalSection( &g_csInt );
EnterCriticalSection( &g_csUint );
for ( intLoop = 0; intLoop < 100; intLoop++ )
{
g_uintArray[intLoop] = g_intArray[intLoop];
}
LeaveCriticalSection( &g_csInt );
LeaveCriticalSection( &g_csUint );
Thread2:
EnterCriticalSection( &g_csUint );
EnterCriticalSection( &g_csInt );
for ( intLoop = 0; intLoop < 100; intLoop++ )
{
g_uintArray[intLoop] = g_intArray[intLoop];
}
LeaveCriticalSection( &g_csInt );
LeaveCriticalSection( &g_csUint );
改为:
Thread1:
EnterCriticalSection( &g_csInt );
EnterCriticalSection( &g_csUint );
for ( intLoop = 0; intLoop < 100; intLoop++ )
{
g_uintArray[intLoop] = g_intArray[intLoop];
}
LeaveCriticalSection( &g_csInt );
LeaveCriticalSection( &g_csUint );
Thread2:
EnterCriticalSection( &g_csInt );
EnterCriticalSection( &g_csUint );
for ( intLoop = 0; intLoop < 100; intLoop++ )
{
g_uintArray[intLoop] = g_intArray[intLoop];
}
LeaveCriticalSection( &g_csInt );
LeaveCriticalSection( &g_csUint );
(3)不要长时间运行关键代码段。
EnterCriticalSection( &g_cs );
SendMessage( hWnd, WM_SOMEMSG, &g_s, 0 );
LeaveCriticalSection( &g_cs );
改为:
EnterCriticalSection( &g_cs );
sTemp = g_s;
LeaveCriticalSection( &g_cs );
SendMessage( hWnd, WM_SOMEMSG, &sTemp, 0 );
6.InitializeCriticalSection/InitializeCriticalSectionAndSpinCount差别?
InitializeCriticalSection函数的返回值为空并且不会创建事件内核对象,比较节省系统资源,但是一旦发生两个或多个线程争用关键代码段的情况,如果内存不足,关键代码段可能被争用,同时系统可能无法创建必要的事件内核对象。这时EnterCriticalSection函数将会产生一个EXCEPTION_INVALID_HANDLE异常。这个错误非常少见。如果想对这种情况有所准备,可以有两种选择。可以使用结构化异常处理方法来跟踪错误。当错误发生时,既可以不访问关键代码段保护的资源,也可以等待某些内存变成可用状态,然后再次调用EnterCriticalSection函数。
另一种选择是使用InitializeCriticalSectionAndSpinCount,第二个参数dwSpinCount中,传递的是在使线程等待之前它试图获得资源时想要循环锁循环迭代的次数。这个值可以是0至0x00FFFFFF之间的任何数字。如果在单处理器计算机上运行时调用该函数,该参数被忽略,并且始终设置为0。使用InitializeCriticalSectionAndSpinCount函数创建关键代码段,确保设置了dwSpinCount参数的高信息位。当该函数发现高信息位已经设置时,它就创建该事件内核对象,并在初始化时将它与关键代码段关联起来。如果事件无法创建,该函数返回FALSE。可以更加妥善地处理代码中的这个事件。如果事件创建成功,EnterCriticalSection将始终都能运行,并且决不会产生异常情况(如果总是预先分配事件内核对象,就会浪费系统资源。只有当代码不能容许EnterCriticalSection运行失败,或者有把握会出现争用现象,或者预计进程将在内存非常短缺的环境中运行时,才能预先分配事件内核对象)。
7.TryEnterCriticalSection和EnterCriticalSection的差别是什么?
如果EnterCriticalSection将一个线程置于等待状态,那么该线程在很长时间内就不能再次被调度。实际上,在编写得不好的应用程序中,该线程永远不会再次被赋予CPU时间。TryEnterCriticalSection函数决不允许调用线程进入等待状态。它的返回值能够指明调用线程是否能够获得对资源的访问权。TryEnterCriticalSection发现该资源已经被另一个线程访问,它就返回FALSE。在其他所有情况下,它均返回TRUE。运用这个函数,线程能够迅速查看它是否可以访问某个共享资源,如果不能访问,那么它可以继续执行某些其他操作,而不必进行等待。如果TryEnterCriticalSection函数确实返回了TRUE,那么CRITICAL_SECTION的成员变量已经更新。Windows98没有可以使用的TryEnterCriticalSection函数的实现代码。
欢迎您使用http://Blogmove.cn提供的"博客搬家"和"博文三窟"服务.