线程同步—线程锁

一、原子访问:Interlocked系列函数

可以保证一个值的操作是一个原子操作。实际的代码执行过程如下:

//C++代码:
g_x++;

//汇编:
MOV EAX, [g_x]
INC EAX
MOV [g_X], EAX

从上面的代码可以看出,即便是单条C++代码,编译为汇编后也会变成多条汇编指令。所以g_x++并非原子操作,也会出现多线程同步问题。
Interlocaked系列函数就是用来保障一次赋值操作是一个原子操作的。

LONG InterlockedExchangeAdd(
    PLONG volatile plAddend,
    LONG lIncrement
);
LONGLONG InterlockedExchangeAdd64(
    PLONGLONG volatile pllAddend,
    LONGLONG llIncrement
);
//使用方法:
long g_x = 0;
InterlockedExchangeAdd(&g_x, 1);

这样就可以保障g_x加上任意LONG数据都是一个原子操作。
这里需要注意的是,传给该函数的变量地址必须是经过对齐的,否则会失败。因为这个函数的原理是锁定一块内存区,这个内存区长度是sizeof(LONG)。如果地址是没有对齐的,那就会导致LONG数据是从两个内存块中读出来的,导致其中一块没有锁定,最终可能会出现同步失败的问题。

void* _aligned_malloc(size_t size, size_t alignment);

该函数可以申请一块对齐过的内存。其中的size表示长度,alignment表示对齐到的字节边界,传给alignment参数的值必须是2的整数幂次方。
InterlockedExchangeAdd也可以做减法,只需要传入一个负值即可。InterlockedExchangeAdd会返回*plAddend中原来的值。
下面还有一些相关的原子锁函数:

LONG InterlockedExchange(
    PLONG volatile plTarget,
    LONG lValue
);
LONGLONG InterlockedExchange64(
    PLONGLONG volatile plTarget,
    LONGLONG lValue
);
PVOID InterlockedExchangePointer(
    PVOID* volatile ppvTarget,
    PVOID pvValue
);

InterlockedExchange和InterlockedExchangePointer会把第一个参数所指向的内存地址的当前值,以原子方式替换为第二个参数指定的值。InterlockedExchangePointer函数在32位程序中替换的是32位值,64位程序中替换的是64位值。

BOOL g_fResourceInUse = FALSE;
InterlockedExchange(&g_fResourceInUse, TRUE);

Interlocked系列函数中还有一种特殊的单向链表栈。

InitializeSListHead          //创建一个空栈
InterlockedPushEntrySList    //在栈顶添加一个元素
InterlockedPopEntrySList     //移除位于栈顶的元素并将它返回
InterlockedFlushSList        //清空栈
QueryDepthSList              //返回栈中的元素的数量

上面的单向链表栈的操作全都是原子操作。

二、关键段

CRITICAL_SECTION g_cs;

VOID InitializeCriticalSection(PCRITICAL_SECTION pcs);
VOID DeleteCriticalSection(PCRITICAL_SECTION pcs);
VOID EnterCriticalSection(PCRITICAL_SECTION pcs);
BOOL TryEnterCriticalSection(PCRITICAL_SECTION pcs);
VOID LeaveCriticalSection(PCRITICAL_SECTION pcs);

LONG g_x = 0;
EnterCriticalSection(&g_cs);
g_x++;
LeaveCriticalSection(&g_cs);

TryEnterCriticalSection函数是不会阻塞的,他会通过返回值表明当前资源是否可以访问。如果当前资源正在被占用,则返回FALSE;如果返回的是TRUE,则需要调用LeaveCriticalSection。
由于EnterCriticalSection在等待时会将线程切换到等待状态。这意味着线程必须从用户模式切换到内核模式,这个开销很大。所以有一种新的接口方式来解决该问题:旋转锁+关键段。

//使用旋转锁初始化关键段
BOOL InitializeCriticalSectionAndSpinCount(
    PCRITICAL_SECTION pcs,
    DWORD dwSpinCount
);
//修改旋转锁循环次数
DWORD SetCriticalSectionSpinCount(
    PCRITICAL_SECTION pcs,
    DWORD dwSpinCount
);

旋转锁是一个类似死循环的锁,所以很占用CPU,但不需要进行内核切换。所以速度要比关键端快。InitializeCriticalSectionAndSpinCount函数第一个参数是一个关键段,第二个参数表示旋转锁的循环次数。范围是0-0x00FFFFFF。当循环结束时,资源如果还被占用着,就会自动从旋转锁切换到关键段。(用来保护进程堆的关键段所使用的旋转次数是4000,可以作为参考值)。

三、Slim读/写锁

SRWLock锁在读取资源时是不锁定的,只有在写资源时才会被锁定。

typedef struct _RTL_SRWLOCK{
    PVOID Ptr;
}RTL_SRWLOCK, *PRTL_SRWLOCK;

VOID InitializeSRWLock(PSRWLOCK SRWLock);
VOID ReleaseSRWLock(PSRWLOCK SRWLock);
//写锁
VOID AcquireSRWLockExclusive(PSRWLOCK SRWLock);
VOID ReleaseSRWLockExclusive(PSRWLOCK SRWLock);
//读锁
VOID AcquireSRWLockShared(PSRWLOCK SRWLock);
VOID ReleaseSRWLockShared(PSRWLOCK SRWLock);
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

孤月丶星辰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值