出处:
http://hi.baidu.com/microsoftxiao/blog/item/a6411546296bc90c6a63e561.html
由于Windows是抢占式操作系统,所以默认的运行是希望各种程序
抢占CPU资源,所以若对此机制不加限制,就会出现。
当一个进程或线程在修改某块内存的同时,另一进程或线程也在同时修改。
这样就会出现,我们期望修改成某值,而被其他进程/线程偷偷的篡改了。
造成了结果不符合我们的预见。
那么如何保证我们预见的正确性呢,或者说如何保证我们再修改某块内存的
同时,不让任何其他进程/线程修改呢?在Direct内,比如在创建VertexBuffer
时,有Locked和Unlocked函数来保证目标内存同一时刻有唯一的进程/线程访问。
就是要把该片内存先锁定(锁死), 然后再进行修改,然后再解锁。
Windows还为我们提供了互锁函数来保证对单个变量的递增或递减是同一时刻唯一的。
它们是InterlockedExchangeAdd等函数。
下面我们分别看几个例子,来看下究竟该如何正确的使用该函数。
代码1:
#include
#include
#include
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<
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
#include
#include
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<
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,
ThreadFunc, 0, 0, &tid);
tHandle2 = (HANDLE)_beginthreadex(NULL, 0,
ThreadFunc2, 0, 0, &tid2);
Sleep(1000);
cout<
system("pause");
return 0;
}
修改并执行后,我们发现Sleep(1000)发挥了作用,现在g_iData的值为我们期望的2了。
那么,我们应当作出如下思考,可能是由于线程体很段小,所以在主线程挂起这1秒终内
两个线程都一定可以被执行到。那么如果我们把其中一个线程体修改为一个超过1秒钟的
一系列的处理呢,那g_iData还会是2么?
#include
#include
#include
using namespace std;
long g_iData = 0; // 全局变量
// 线程体
unsigned int __stdcall ThreadFunc(void* param)
{
int i = 0;
char szBuf[MAX_PATH] = {0};
int tData = 0;
while(i<10000000)
{
sprintf(szBuf, "%d", i);
tData = i;
i++;
}
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);
Sleep(1000);
cout<
system("pause");
return 0;
}
我们对ThreadFunc 线程体进行了修改,增加了一些垃圾操作以延长ThreadFunc的执行时间,
这样不完成垃圾操作,就不会去执行ThreadFunc内的InterlockedExchangeAdd(&g_iData, 1);
操作。而同时线程ThreadFunc2 则会直接去执行InterlockedExchangeAdd(&g_iData, 1);
而主线程内才Sleep(1000), 等待了1秒钟,为了完成这些操作
int i = 0;
char szBuf[MAX_PATH] = {0};
int tData = 0;
while(i<10000000)
{
sprintf(szBuf, "%d", i);
tData = i;
i++;
}
主线程等待1秒显然是不够的,相同的这些代码在不同速度的处理器执行的速度可能不同。所以可能
会有不同的结果。
那么很显然在主线程内等待1秒是不行的,那么我们究竟该让主线程等待多长时间才能得到
我们最终期望的2的结果呢。或者说,线程体ThreadFunc究竟要多长时间才能被完全执行完呢?
为了完成这个功能,Windows为我们提供了WaitForSingleObject函数来等待线程或其他对象的
完成。WaitForSingleObject的原形为:
DWORD WaitForSingleObject(
HANDLE hHandle,
DWORD dwMilliseconds
);
通过第二参数dwMilliseconds我们来控制等待的时间。
由于不知道某个线程究竟能执行多长时间,我们可以将dwMilliseconds设置为INFINITE,
INFINITE为0xFFFFFFFF, 传递该值表示直到某个对象或线程被完成才继续执行主线程。
根据我们掌握的Windows消息机制,我们可以推断当线程执行完成后,会发送一些消息,
这些消息将通知WaitForSingleObject函数某个线程已经完成了。
好,下面我们将Sleep(1000)替换为WaitForSingleObject。
#include
#include
#include
using namespace std;
long g_iData = 0; // 全局变量
// 线程体
unsigned int __stdcall ThreadFunc(void* param)
{
int i = 0;
char szBuf[MAX_PATH] = {0};
int tData = 0;
while(i<10000000)
{
sprintf(szBuf, "%d", i);
tData = i;
i++;
}
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);
WaitForSingleObject(tHandle, INFINITE);
WaitForSingleObject(tHandle2, INFINITE);
cout<
system("pause");
return 0;
}
替换后,执行过程也许会花费一段时间,
int i = 0;
char szBuf[MAX_PATH] = {0};
int tData = 0;
while(i<10000000)
{
sprintf(szBuf, "%d", i);
tData = i;
i++;
}
这是由于以上代码在发挥作用,而最后的结果为我们期望的2。
说明WaitForSingleObject函数发挥了作用。好就到这里吧。
更多进程/线程信息,我们再慢慢发掘。
阅读(3405) | 评论(0) | 转发(0) |