您的位置:首页 > 其它

InterLockedIncrement and InterLockedDecrement

2014-04-24 20:21 465 查看
InterLockedIncrement and InterLockedDecrement

实现数的原子性加减。什么是原子性的加减呢?

举个例子:如果一个变量 Long value =0;

首先说一下正常情况下的加减操作:value+=1;

1:系统从Value的空间取出值,并动态生成一个空间来存储取出来的值;

2:将取出来的值和1作加法,并且将和放回Value的空间覆盖掉原值。加法结束。

如果此时有两个Thread ,分别记作threadA,threadB。

1:threadA将Value从存储空间取出,为0;

2:threadB将Value从存储空间取出,为0;

3:threadA将取出来的值和1作加法,并且将和放回Value的空间覆盖掉原值。加法结束,Value=1。

4:threadB将取出来的值和1作加法,并且将和放回Value的空间覆盖掉原值。加法结束,Value=1。

最后Value =1 ,而正确应该是2;这就是问题的所在,InterLockedIncrement 能够保证在一个线程访问变量时其它线程不能访问。同理InterLockedDecrement。

LONG InterlockedDecrement(

LPLONG lpAddend // variable address

);

属于互锁函数,用在同一进程内,需要对共享的一个变量,做减法的时候,

防止其他线程访问这个变量,是实现线程同步的一种办法(互锁函数)

首先要理解多线程同步,共享资源(同时访问全局变量的问题),否则就难以理解。

result = InterlockedDecrement(&SomeInt)

如果不考虑多线程其实就是 result = SomeInt - 1;

但是考虑到多线程问题就复杂了一些。就是说如果想要得到我预期的结果并不容易。

result = SomeInt - 1;

举例说:

SomeInt如果==1;

预期的结果result当然==0;

但是,如果SomeInt是一个全程共享的全局变量情况就不一样了。

C语言的"result = SomeInt - 1;"

在实际的执行过程中,有好几条指令,在指令执行过程中,其它线程可能改变SomeInt值,使真正的结果与你预期的不一致。

所以InterlockedDecrement(&SomeInt)的执行过程是这样的

{

__禁止其他线程访问 (&SomeInt) 这个地址

SomeInt --;

move EAX, someInt; // 设定返回值,C++函数的返回值 都放在EAX中,

__开放其他线程访问 (&SomeInt) 这个地址

}

但是实际上只需要几条指令加前缀就可以完成,以上说明是放大的。

你也许会说,这有必要吗? 一般来说,发生错误的概率不大,但是防范总是必要的

如果不考虑多线程

result = InterlockedDecrement(&SomeInt);

就是result = SomeInt - 1;

如果SomeInt==1,result一定==0;

但是,在多线程中如果SomeInt是线程间共享的全局变量,情况就不那么简单了。

result = SomeInt - 1;

在CPU中,要执行好几条指令。在指令中间有可能SomeInt被线程修改。那实际的结果就不是你预期的结果了。

InterlockedDecrement(&SomeInt)

放大的过程,如下:

{

__禁止其他线程访问 &SomeInt 地址;

SomeInt --;

/////其他线程不会在这里修改SomeInt值。 !!!!!!

mov EAX, SomeInt; //C++ 函数返回值 总放在EAX中。

__开放其他线程访问 &SomeInt 地址;

}

实际的CPU执行过程只有几条加前缀的指令(586指令)

你会说,有必要吗? 出错的概率不大,但是错误总是需要防范的。当然可以用其他多线程机制实现,但是都没有这样简洁,所以Interlocked...函数有必要提供。

补充知识类似的还有下面的几个

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

如果第三个参数与第一个参数指向的值相同,那么用第二个参数取代第一个参数指向的值,否则Destination就不改变,函数返回值为原始值。

(5) PVOID InterlockedCompareExchangePointer (

PVOID *Destination, PVOID Exchange, PVOID Comperand );

如果第三个参数与第一个参数指向的值相同,那么用第二个参数取代第一个参数指向的值。,否则Destination就不改变,函数返回值为原始值。

一般来说,在多用户线程环境中,我们使用临界区、事件对象甚至互斥量来进行同步,尤其是临界区,可以很方便地对某些变量甚至代码块进行锁定执行,防止多线程之间资源恶性抢夺。既然如此,为啥微软又单独提供了专用于一个数值锁定计算的API函数InterlockedIncrement和InterlockedDecrement呢?他们又有什么特殊作用呢?

恰好近段时间写了一个这方面的应用,帮我加深了对这类API函数的理解。

首先描述一下需求,在应用中,有这样一个类,它可能只被实例化一次,也可能会被实例化多次,但不管被实例化了几次,它必须在构造函数里执行一项初始化计算,假设初始化计算的函数为WSAStartup,同时还需要在析构函数里执行一下注销计算,假设注销计算的函数为WSACleanup,现在有一个要求,就是它们的初始化和注销计算只能被执行一次,就如同在一个项目中,只能运行一次WSAStartup和WSACleanup一样。当然,大家可能会想到直接在工程的开始和结尾处实现这样的功能,但是,如果把这个类的文件包括在其它测试工程里进行测试,同时不改变其它工程的代码,又该如何实现呢?

其实,我们可以充分利用InterlockedIncrement和InterlockedDecrement,就如同COM的CoInitialize()和CoUninitialize()一样,描述代码如下:

假设类名为A:

view plaincopy to clipboardprint?

class A

{

protected:

static long m_nRef;

public:

/ /类A的构造函数

A()

{

if(1 == InterlockedIncrement(&m_nRef))

{

//以下代码只执行一次

WSADATA wsaData;

WSAStartup(MAKEWORD(2,2), &wsaData);

}

};

//类A的虚析构函数

virtual ~A()

{

if(0 == InterlockedDecrement(&m_nRef))

{

//以下代码只执行一次

WSACleanup();

}

}

};

long A::m_nRef = 0;

class A

{

protected:

static long m_nRef;

public:

//类A的构造函数

A()

{

if(1 == InterlockedIncrement(&m_nRef))

{

//以下代码只执行一次

WSADATA wsaData;

WSAStartup(MAKEWORD(2,2), &wsaData);

}

};

//类A的虚析构函数

virtual ~A()

{

if(0 == InterlockedDecrement(&m_nRef))

{

//以下代码只执行一次

WSACleanup();

}

}

};

long A::m_nRef = 0;

这样,无论我们创建了类A的多少个实例,在类的构造函数和析构函数里,WSAStartup和WSACleanup均只被执行一次,有效地保证了单元代码的封装性。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: