- 原子访问:Interlocked 系列函数
- 高速缓存行
- 高级线程同步
- 关键段
- Slim读写锁
- 条件变量
8.1 原子访问 Interlocked 系列函数
LONG InterlockedExangeAdd(
PLONG volatile plAddend,
LONG lIncrement
);
LONGLONG InterlockedExchangeAdd64(
PLONGLONG volatile pllAddend,
LONGLONG llIncrement
);
void * _aligned_malloc(size_t size,size_t alignment)
内存对齐函数
几个赋值的函数
LONG InterlockedExange(
PLONG volatile plTarget,
LONG lValue
);//*plTarget=lValue; 返回原来的值即 *plTarget
LONG InterlockedExchange64(
PLONGLONG volatile plTarget,
LONGLONG lValue
);
PVOID InterlockedExchangePointer(
PVOID * volatile ppvTarget,
PVOID pvValue
);//*ppvTarget=pvValue; 返回原来的值即 *plTarget
几个用来比较然后赋值的函数
LONG InterlockedCompareExchange(
PLONG plDestination,
LONG lExchange,
LONG lComparand
);
LONG InterlockedCompareExchangePointer(
PVOID* ppvDestination,
PVOID pvExchange,
PVOID pvComparand
);
// InterlockedCompareExchange 的伪代码如下
LONG InterlockedCompareExchange(PLONG plDestination,
LONG lExchange ,Long lComparand){
LONG lRet=*plDestination;
if(*plDestination == lComparand )
*plDestination=lExchange;
return(lRet);
}
// end
//for x64
LONGLONG InterlockedCompareExchange64(
LONGLONG pllDestination,
LONGLONG llExchange,
LONGLONG llComparand);
其他两个函数
LONG InterlockedIncrement(PLONG plAddend);
LONG InterlockedDecrement(PLONG plAddend);
//others for and , or ,xor
LONGLONG InterlockedAnd64(
LONGLONG* Destination,
LONGLONG Value){
LONGLONG Old;
do{
Old=*Destination;
}while(InterlockedCompareExchange64(Destination,Old & Value,Old)!=Old);
return Old;
}
从Windows XP 开始,还提供了一系列能对单向链表提供的函数。
8.2 高速缓存行
一个糟糕的设计
struct CUSTINFO{
DWORD dwCustomerID; // mostly read only
int nBalanceDue; // read write
wchar_t szName[100]; // mostly read only
FILETIME ftLastOrderDate; // read write
}
这个例子没有考虑到cpu的高速缓存行
//一个简单的改进版
#define CHCHE_ALIGN 64
struct __declspec(align(CACHE_ALIGN)) CUSTINFO{
DWORD dwCustomerID ;//most read-only
wchar_t szName[100]; //most read-only
__declspec(align(CACHE_ALIGN))
int nBalanceDue; // r w
FILETIME ftLastOrderDate; // r w
};
8.3 高级线程同步
如果线程想要访问一个共享资源或者想要得到一些特殊事件的通知时,线程必须调用操作系统的一个函数,并将线程正在等待的东西作为参数传入。
如果OS检测到资源已经可供使用,或者特殊事件已经发生,那么这个函数会立刻返回。这样线程将仍然保持可调度状态。
* 当线程在等待的时候,系统会充当它的代理,系统会记住它想要的资源,并在合适的时候唤醒他
我们要避免两个线程使用共享变量的方式来实现同步。
8.4 关键段
const int COUNT=10;
int g_nSum=0;
CRITICAL_SECTION g_cs;
DWORD WINAPI FirstThread(PVOID pvParam){
EnterCriticalSection(&g_cs);/////////// enter
g_sum=0;
for(int n=1;n<=COUNT;n++){
g_nSum+=n;
}
LeaveCriticalSection(&g_cs);/////////////// leave
return(g_nSum);
}
DWORD WINAPI SecondThread(PVOID pvParam){
EnterCriticalSection(&g_cs);
g_sum=0;
for(int n=1;n<=COUNT;n++){
g_nSum+=n;
}
LeaveCriticalSection(&g_cs);
return(g_nSum);
}
8.4.1 关键段:细节
VOID InitIalizeCriticalSection(PCRITICAl_SECTION pcs);
VOID EnterCriticalSection(PCRITICAL_SECTION pcs);
VOID DeleteCriticalSection(PCRITICAL_SECTION pcs);
VOID TryEnterCriticalSection(PCRITICAL_SECTION pcs);
//如果返回true,那么CRITICAL_SECTION的成员变量已经更新过了,表示该线程正在访问资源
//如果false,线程可以去做别的事情,而不会等待。
8.4.2 关键段和旋转锁
BOOL InitializeCriticalSectionAndSpinCount(
PCRITICAL_SECTION pcs,
DWORD dwSpinCount//0~0x00FFFFFFF
);//init
DWORD SetCriticalSectionSpinCount(
PCRITICAL_SECTION pcs;
DWORD dwSpinCount;
);//set
//dwSpinCount 推荐 4000
8.4.3 关键段和错误处理
InitIalizeCriticalSection 函数可能会由于内存不足返回失败,ms最初设计的时候没有考虑到这个问题,所以该函数返回类型为VOID , 返回失败的时候回抛出 STATUS_NO_MEMORY异常
可以使用InitializeCriticalSectionAndSpinCount函数来解决这个问题,内存分配失败的时候返回FALSE
第一次使用EnterCriticalSection创建事件内核对象。
DeleteCriticalSection可以删除事件内核对象。
XP之前,内存不足可能会发生关键段争夺现象,EnterCriticalSection函数会抛出EXCEPTION_INVALID_HANDLE异常。
处理方式:
1. 结构化异常捕获
2. InitializeCriticalSectionAndSpinCount来创建关键段,将dwSpinCount参数的最高位设为1,当函数看到最高位为1的时候,会在初始化的时候创建一个与关键段相关联的时间内核对象。如果无法创建会返回FALSE
XP开始引入了新的有键事件类型的内核对象。
8.5 Slim 读写锁
SRWLock和关键段相同,对一个资源进行保护,不让其他线程访问它。
但SRWLock允许我们区分那些想要读取资源的值的线程和想要更新资源的值的线程、
让所有读取者线程在同一时刻访问资源是可行的,但当写入这线程想要对资源进行更新的时候需要进行同步,这种情况下写入这线程应该独占资源的访问权。
VOID InitializeSRWLock(PSRWLOCK SRWLock);
//for writer
VOID AcquireSRWLockExclusive(PSRWLOCK SRWLock);
VOID ReleaseSRWLockExclusive(PSRWLOCK SRWLock);
//for reader
VOID AcquireSRWLockShared(PSRWLOCK SRWLock);
VOID ReleaseSRWLockShared(PSWRLOCK SRWLock);
8.6 条件变量
有时我们想让线程以原子方式把锁释放并将自己阻塞,直到某个条件成立为止。
windows提供两个函数
BOOL SleepCondtionVariableCS(
PCONDITION_VARIABLE pConditionVariable,
PCONDITION_VARIABLE pCriticalSection,
DWORD dwMilliseconds
);
BOOL SleepCondtionVariableSRW(
PCONDITION_VARIABLE pConditionVariable,
PSWLOCK pSRwLock,
DWORD dwMilliseconds,
ULONG Flags
);
// pConditionVariable 指向我们的条件变量,
// pCriticalSection pSRwLock 指向我们的锁变量
VOID WakeConditionVariable(
PCONDITION_VARIABLE ConditionVariable
);//唤醒一个处于SleepConditionVariable*函数中等待同一个条件变量的线程获得锁。
VOID WakeAllConditionVariable(
PCONDITION_VARIABLE ConditionVariable
);//唤醒一个或多个处于SleepConditionVariable*函数中等待同一个条件变量的线程获得锁。