Windows Interlocked函数族

当进行多线程编程的时候,一个随时都需要面对的问题就是对一些共享变量进行操作,例如增加/降低某个数值,更改某一个数据,对某一个数据进行各种逻辑操作,如XOR AND, OR等。在这时候,比较让人头疼的地方就是,C++的操作符不保证线程安全。例如:++X,不能保证X在自增的过程中不被打断。于是就有可能:

X+1,但是X的新值还没有被放到X中去,线程就因为时间片等问题被挂起,在这样的环境下,如果某一个线程读取了X的数值,那么该线程就得到了错误的数据。

如果某一个计算在完成之前不能被打断,那么该计算就被称为原子计算。windows提供了interlocked系列函数来保证计算的原子性,

例如InterlockedExchangeAdd函数,或者InterlockedExchange函数。下面这段代码给出了一个使用这些函数的例子。

// Interlocked Family.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <Windows.h>
#include <process.h>
#include <iostream>
using namespace std;
#define THREAD_NUM MAXIMUM_WAIT_OBJECTS
LONG lCounter = 0;
LONG lCounterLocked = 0;
LONG lCounterSpinLock = FALSE;
unsigned int WINAPI Thread(LPVOID pParam)
{
	while(InterlockedExchange(&lCounterSpinLock,TRUE) == TRUE) SwitchToThread(); //lock
	printf_s("Thread %d gains access\n",pParam);
	lCounter++;
	Sleep(500);
	InterlockedExchange(&lCounterSpinLock,FALSE); //unlock
	::InterlockedExchangeAdd(&lCounterLocked,1);
	return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
	HANDLE pThreads[THREAD_NUM];
	for(int i = 0; i<THREAD_NUM; i++)
		pThreads[i] = (HANDLE)_beginthreadex(NULL,NULL,Thread,(LPVOID)i,NULL,NULL);
	WaitForMultipleObjects(THREAD_NUM,pThreads,TRUE,INFINITE);
	for(int i = 0; i<THREAD_NUM; i++)
		CloseHandle(pThreads[i]);
	cout<<lCounter<<ends<<lCounterLocked<<endl;
	return 0;
}
在线程函数中我们可以看到,有一个非线程安全的变量lCounter在自增。那么为了保证这个变量的线程安全,我使用了 InterlockedExchange 函数来实现了一个自旋锁。线程在锁内增加变量的值,并休眠0.5秒。同时,还有另一个变量通过原子操作来自增。在执行过程中可以看到,每0.5秒会有一个线程获得lCounter的访问权,并自增该变量。

读过我之前写的那个线程优先级那点事的人也应该知道,SwitchToThread代表在获得Access之前,该线程自动放弃时间片改去调度其它线程,再次强调一遍这个函数和Sleep(0)之间的区别,Sleep(0)只会让OS调度同级或者高优先级的线程,而SwitchToThread的调度是全系统范围的。

最后,这段代码的结果显示了用自旋锁自增的变量和用Interlocked实现的变量最后的数值是一致的。


该代码另外的一些小点提示:

1.注意我使用了_beginthreadex函数,该函数在前面的博文里提到过,是CreateThread替代品,简单的说,它为CRT的多线程安全提供了保证,这一点CreateThread没有考虑。

2.注意,我在最后关闭了所有线程的句柄,如果你使用WinVista或者Win7,你可以观察到这个进程的句柄从125降低到了61(因为线程开了64个),记得关闭句柄是一个很好的习惯。

3.你可以从任务管理器看到自旋锁是如何FUCK CPU的,CPU的使用率一直都在100%,直到这64个线程"轮奸"完成。自旋锁只适合哪种你确定你短期之内就能访问到共享资源的情况,否则请使用内核对象(如信号量,临界区)。


最后一句话是:Jeffrey Richter andChristophe Nasarre 这两个基佬说,记得你不断访问的锁对象和你要保护的内存数据不在同一个Cache Line里,不然性能会降低。

至于什么是Cache Line,等我看到那再做总结。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值