现在的位置: 首页 > 综合 > 正文

互锁函数,原子操作方式,关键代码段

2013年10月16日 ⁄ 综合 ⁄ 共 2092字 ⁄ 字号 评论关闭

多个线程访问共享资源时会发生同步问题。根本原因是因为Windows是抢占式多线程环境。

线程在执行的时候随时可能被中断(运行完一个cpu时间或有更高优先级的线程要运行)

例如:

//Define a global variable.
long g_x = 0;

DWORD WINAPI ThreadFunc1(PVOID pvParam) 
{
   g_x++;
   return(0);
}

DWORD WINAPI ThreadFunc2(PVOID pvParam) 
{
   g_x++;
   return(0);
}

在这个代码中,声明了一个全局变量g _ x,并将它初始化为0。现在,假设创建两个线程,一个线程执行T h r e a d F u n c 1,另一个线程执行T h r e a d F u n c 2。这两个函数中的代码是相同的,它们都将1添加给全局变量g _ x。因此,当两个线程都停止运行时,你可能希望在g _ x中看到2这个值。但是你真的看到了吗?回答是,也许看到了。根据代码的编写方法,你无法说明g
_ x中最终包含了什么东西。下面我们来说明为什么会出现这种情况。假设编译器生成了下面这行代码,以便将g _ x递增1:

MOV EAX, [g_x]       ;Move the value in g_x into a register.
INC EAX              ;Increment the value in the register.
MOV [g_x], EAX       ;Store the new value back in g_x.

两个线程不可能在完全相同的时间内执行这个代码。因此,如果一个线程在另一个线程的后面执行这个代码,那么下面就是实际的执行情况:

 

MOV EAX, [g_x]      ;Thread 1: Move 0 into a register.
INC EAX             ;Thread 1: Increment the register to 1.
MOV [g_x], EAX      ;Thread 1: Store 1 back in g_x.

MOV EAX, [g_x]      ;Thread 2: Move 1 into a register.
INC EAX             ;Thread 2: Increment the register to 2.
MOV [g_x], EAX      ;Thread 2: Store 2 back in g_x.

当两个线程都将g _ x的值递增之后, g _ x中的值就变成了2。这很好,并且正是我们希望的:即取出零( 0),两次将它递增1,得出的值为2。太好了。不过不要急,Wi n d o w s是个抢占式多线程环境。一个线程可以随时中断运行,而另一个线程则可以随时继续执行。这样,上面的代码就无法完全按编写的那样来运行。它可能按下面的形式运行:

MOV EAX, [g_x]      ;Thread 1: Move 0 into a register.
INC EAX             ;Thread 1: Increment the register to 1.

MOV EAX, [g_x]      ;Thread 2: Move 0 into a register.
INC EAX             ;Thread 2: Increment the register to 1.
MOV [g_x], EAX      ;Thread 2: Store 1 back in g_x.

MOV [g_x], EAX      ;Thread 1: Store 1 back in g_x.

如果代码按这种形式来运行, g _ x中的最后值就不是2,而是你预期的1。这使人感到非常担心,因为你对调度程序的控制能力非常小。实际上,如果有1 0 0个线程在执行相同的线程函数,当它们全部退出之后, g _ x中的值可能仍然是1。显然,软件开发人员无法在这种环境中工作。我们希望在所有情况下两次递增0产生的结果都是2。另外,不要忘记,编译器生成代码的方法,哪个C P U在执行这个代码,以及主计算机中安装了多少个C
P U等因素,决定了产生的结果可能是不同的。这就是该环境的运行情况,我们对此无能为力。但是, Wi n d o w s确实提供了一些函数,如果正确地使用这些函数,就能确保产生应用程序的代码得到的结果。

 这些函数就被称为互锁函数。

互锁函数保证线程以原子操作的方式来访问资源。原子操作方式是指当一个线程正在访问共享的内存资源时,另一个线程也要访问共享资源,那么就只能等待访问资源的线程访问完毕之后才能访问该共享资源。

也就是说原子操作方式同样是可以被中断的,这点理解很重要。只是中断之前打开了cpu的一个特殊标志位来标识共享资源的地址,另一个线程访问共享资源时会检查这个标识,如果标志位打开就等待,如果关闭就直接访问该资源。(本人结合Windows核心编程的个人理解,仅供参考)

循环锁的概念很重要,是为了解决互锁函数只能修改单个值这个缺陷所引入的概念。采用一个全局的bool类型变量来标识一个复杂的资源是否正在被线程访问。当另一个线程也要访问该资源时,会先测试这个变量的值,如果为假就设为真,并访问该资源。如果为真就继续测试,直到另一个线程改变了它的值,然后设置为真并访问该资源,访问完毕再设为假。这样就导致了一个问题,那就是线程有可能在一个cpu时间内什么事也没有做,一直在测试这个变量的值。这样就导致浪费了其他线程的cpu时间。

据此引入了关键代码段的概念。由于互锁函数本身的缺陷(只能修改单个值)才引入关键代码段的概念。其实内部还是采用了互锁函数来实现。具体的策略不详。

 

抱歉!评论已关闭.