互锁函数InterlockedExchangeAdd的用法
(2012-05-21 09:35:39)
标签:
杂谈 |
分类: CPP与ASP |
出处:http://hi.baidu.com/microsoftxiao/blog/item/a6411546296bc90c6a63e561.html
由于Windows是抢占式操作系统,所以默认的运行是希望各种程序
抢占CPU资源,所以若对此机制不加限制,就会出现。
当一个进程或线程在修改某块内存的同时,另一进程或线程也在同时修改。
这样就会出现,我们期望修改成某值,而被其他进程/线程偷偷的篡改了。
造成了结果不符合我们的预见。
那么如何保证我们预见的正确性呢,或者说如何保证我们再修改某块内存的
同时,不让任何其他进程/线程修改呢?在Direct内,比如在创建VertexBuffer
时,有Locked和Unlocked函数来保证目标内存同一时刻有唯一的进程/线程访问。
就是要把该片内存先锁定(锁死), 然后再进行修改,然后再解锁。
Windows还为我们提供了互锁函数来保证对单个变量的递增或递减是同一时刻唯一的。
它们是InterlockedExchangeAdd等函数。
下面我们分别看几个例子,来看下究竟该如何正确的使用该函数。
代码1:
#include <windows.h>
#include <process.h>
#include <iostream>
using namespace std;
int g_iData = 0;
// 全局变量
// 线程体
unsigned int __stdcall ThreadFunc(void* param)
{
g_iData++;
return
0;
}
unsigned int __stdcall ThreadFunc2(void* param)
{
g_iData++;
return
0;
}
int main()
{
unsigned int
tid = 0;
unsigned int
tid2 = 0;
HANDLE
tHandle = NULL;
HANDLE
tHandle2 = NULL;
tHandle =
(HANDLE)_beginthreadex(NULL, 0,
ThreadFunc,
0, 0, &tid);
tHandle2 =
(HANDLE)_beginthreadex(NULL, 0,
ThreadFunc2, 0, 0, &tid2);
cout<<g_iData<<endl;
system("pause");
return
0;
}
说明:
这段代码首先利用_beginthreadex函数创建了两个线程,然后分别在线程体ThreadFunc和
ThreadFunc2内对全局变量g_iData进行递增操作。
然后我们通过cout来输出g_iData的值,最后通过system("pause")来挂起一会。
按照我们的理解,我们可以预见g_iData被两线程修改后值必为2。
但实际运行则有可能是0, 1 或2。 这是我们不希望看到的结果。
那么为什么会得到这样的结果呢,一方面正如前面所说windows是抢占式多线程环境,
所以线程可能随时被挂起。所以有这种可能当ThreadFunc被执行后,ThreadFunc2的创建还
没有准备好,或者由于运行条件不够ThreadFunc2正在被挂起,而不能执行其内的g_iData++;操作。
总之这样都是不可预测的。
所以windows提供了互锁函数来帮助我们实现在同一时间段内对某片内存的唯一操作。
那么在这之前,我们先要了解什么是原子访问。
所谓原子访问,是指线程在访问资源时能够确保所有其他线程都不在同一时间内访问相同的资源,
也就是说对资源的访问操作就像是原子一样不可再分。
那么,我们先来看互锁函数中的一个函数
InterlockedExchangeAdd(), 这个函数的原形为:
LONG
InterlockedExchangeAdd(
IN OUT PLONG
Addend,
IN LONG
Value
);
Parameters
Addend
Pointer to an integer variable.
Value
Specifies the value to be added to Addend.
Return Value
InterlockedExchangeAdd returns the original value of the Addend variable when the call occurred.
该函数可实现对一个long型变量的相加操作。
互锁函数的实现机制是通过硬件级别的操作来完成的,所以我们不需要担心它的速度问题。
但是,是不是当我们把g_iData++;替换为InterlockedExchangeAdd(&g_iData, 1) 就万事大吉了呢。
下面我们来看代码2:
#include <windows.h>
#include <process.h>
#include <iostream>
using namespace std;
long g_iData = 0;
// 全局变量
// 线程体
unsigned int __stdcall ThreadFunc(void* param)
{
InterlockedExchangeAdd(&g_iData, 1);
return
0;
}
unsigned int __stdcall ThreadFunc2(void* param)
{
InterlockedExchangeAdd(&g_iData, 1);
return
0;
}
int main()
{
unsigned int
tid = 0;
unsigned int
tid2 = 0;
HANDLE
tHandle = NULL;
HANDLE
tHandle2 = NULL;
tHandle =
(HANDLE)_beginthreadex(NULL, 0,
ThreadFunc,
0, 0, &tid);
tHandle2 =
(HANDLE)_beginthreadex(NULL, 0,
ThreadFunc2, 0, 0, &tid2);
cout<<g_iData<<endl;
system("pause");
return
0;
}
此段代码只是对代码1的小幅修改,将g_iData替换为InterlockedExchangeAdd(&g_iData, 1);
但执行结果却大大不同,经过执行我们发现主线程输出g_iData的值不在在0, 1, 2之间变化,
而一直为0。这是为什么呢?
具体实际的原因我们不得而知,但是根据g_iData的值为0,我们可以推断
InterlockedExchangeAdd(&g_iData, 1)没有被执行,甚至连线程体也没有被执行。
那么为了解决这个问题,我们通过在主线程内等待线程一定被执行是否可行呢?比如
通过插入Sleep(1000)来挂起主线程1秒钟,我们来看一看。
int main()
{
unsigned int
tid = 0;
unsigned int
tid2 = 0;
HANDLE
tHandle = NULL;
HANDLE
tHandle2 = NULL;
tHandle =
(HANDLE)_beginthreadex(NULL, 0,
由于Windows是抢占式操作系统,所以默认的运行是希望各种程序
抢占CPU资源,所以若对此机制不加限制,就会出现。
当一个进程或线程在修改某块内存的同时,另一进程或线程也在同时修改。
这样就会出现,我们期望修改成某值,而被其他进程/线程偷偷的篡改了。
造成了结果不符合我们的预见。
那么如何保证我们预见的正确性呢,或者说如何保证我们再修改某块内存的
同时,不让任何其他进程/线程修改呢?在Direct内,比如在创建VertexBuffer
时,有Locked和Unlocked函数来保证目标内存同一时刻有唯一的进程/线程访问。
就是要把该片内存先锁定(锁死), 然后再进行修改,然后再解锁。
Windows还为我们提供了互锁函数来保证对单个变量的递增或递减是同一时刻唯一的。
它们是InterlockedExchangeAdd等函数。
下面我们分别看几个例子,来看下究竟该如何正确的使用该函数。
代码1:
#include <windows.h>
#include <process.h>
#include <iostream>
using namespace std;
int g_iData = 0;
// 线程体
unsigned int __stdcall ThreadFunc(void* param)
{
}
unsigned int __stdcall ThreadFunc2(void* param)
{
}
int main()
{
}
说明:
这段代码首先利用_beginthreadex函数创建了两个线程,然后分别在线程体ThreadFunc和
ThreadFunc2内对全局变量g_iData进行递增操作。
然后我们通过cout来输出g_iData的值,最后通过system("pause")来挂起一会。
按照我们的理解,我们可以预见g_iData被两线程修改后值必为2。
但实际运行则有可能是0, 1 或2。 这是我们不希望看到的结果。
那么为什么会得到这样的结果呢,一方面正如前面所说windows是抢占式多线程环境,
所以线程可能随时被挂起。所以有这种可能当ThreadFunc被执行后,ThreadFunc2的创建还
没有准备好,或者由于运行条件不够ThreadFunc2正在被挂起,而不能执行其内的g_iData++;操作。
总之这样都是不可预测的。
所以windows提供了互锁函数来帮助我们实现在同一时间段内对某片内存的唯一操作。
那么在这之前,我们先要了解什么是原子访问。
所谓原子访问,是指线程在访问资源时能够确保所有其他线程都不在同一时间内访问相同的资源,
也就是说对资源的访问操作就像是原子一样不可再分。
那么,我们先来看互锁函数中的一个函数
InterlockedExchangeAdd(), 这个函数的原形为:
LONG
InterlockedExchangeAdd(
Parameters
Addend
Pointer to an integer variable.
Value
Specifies the value to be added to Addend.
Return Value
InterlockedExchangeAdd returns the original value of the Addend variable when the call occurred.
该函数可实现对一个long型变量的相加操作。
互锁函数的实现机制是通过硬件级别的操作来完成的,所以我们不需要担心它的速度问题。
但是,是不是当我们把g_iData++;替换为InterlockedExchangeAdd(&g_iData, 1) 就万事大吉了呢。
下面我们来看代码2:
#include <windows.h>
#include <process.h>
#include <iostream>
using namespace std;
long g_iData = 0;
// 线程体
unsigned int __stdcall ThreadFunc(void* param)
{
}
unsigned int __stdcall ThreadFunc2(void* param)
{
}
int main()
{
}
此段代码只是对代码1的小幅修改,将g_iData替换为InterlockedExchangeAdd(&g_iData, 1);
但执行结果却大大不同,经过执行我们发现主线程输出g_iData的值不在在0, 1, 2之间变化,
而一直为0。这是为什么呢?
具体实际的原因我们不得而知,但是根据g_iData的值为0,我们可以推断
InterlockedExchangeAdd(&g_iData, 1)没有被执行,甚至连线程体也没有被执行。
那么为了解决这个问题,我们通过在主线程内等待线程一定被执行是否可行呢?比如
通过插入Sleep(1000)来挂起主线程1秒钟,我们来看一看。
int main()
{