《Windows via C/C++》学习笔记 —— 用户模式的“线程同步”之“互锁函数族”

  线程需要相互通信,当多个线程访问共享资源的时候;当一个线程结束一个任务,然后要通知另一个线程的时候,我们需要进行线程同步的控制。

  要让共享资源不被破坏,或者让另一个线程能够在适当的时候被调度,这就是线程同步需要解决的问题。

 

  Windows Vista提供了多种线程同步的方法,这里主要介绍在用户模式下的线程同步。

1、互锁函数族:Interlocked*函数群(*表示省略后面的字符,即以Interlocked开头)。

2、关键代码段:Criticle Sections

3、读写锁:Slim Read-Write Locks

4、状态变量:Condition Variable

 

互琐函数族

  本质上是对一个变量进行原子性的修改,在修改的时候不受到其他线程的干扰。也就是说,“读数”、“修改”、“写回”这3个操作是一个整体,中间没有停顿,不可分割。

  考虑如下代码:

long g_x = 0;
DWORD WINAPI ThreadFunc1(PVOID pvParam) {
   g_x++;
   return(0);
}
DWORD WINAPI ThreadFunc2(PVOID pvParam) {
   g_x++;
   return(0);
}
  两个线程共享一个全局变量,当这两个线程都执行完成之后,g_x会变成2,但是事实一定是这样么?

  “g_x++”这条语句包含了三个过程:1、取得g_x的值,送到寄存器中;2、把寄存器中的值加1;3、将得到的新的值写回到原来g_x所对应的存储单元中。

  设想如下情形:线程1首先取得g_x的值,送到EAX寄存器中,然后对EAX中的数值加1,然后由于某种原因暂停执行。这个时候,CPU分配给了线程2,然后线程2取得g_x的值,也把它送到EAX寄存器中,这样EAX里面原来的值就被这个时候的g_x(数值为0)覆盖了,然后线程2将EAX中的数值加1,当线程2写回的时候,把1写回到了g_x中。然后线程1恢复执行,将数据从EAX中写回,它写回的也是1,而不是2!


  可见,这两个线程相互之间会干扰对方的行动,从而可能破坏共享的数据。上面讨论的这种情况,是典型的“丢失修改”。同时,还可能有“读脏数据”、“不可重复读”等同步问题。

 

  可以使用“互锁函数族”解决上述问题,你可以调用下面两个函数来代替g_x++:

LONG InterlockedExchangeAdd(

   PLONG volatile plAddend,
   LONG lIncrement);

LONGLONG InterlockedExchangeAdd64(
   PLONGLONG volatile pllAddend,
   LONGLONG llIncrement);

  这两个函数第一参数可以理解为“被加数”(其实传递的是一个数值的地址),第二个参数可以理解为“加数”。就相当于执行“*plAddend = *plAddend + lIncrement”。唯一的区别“取数”、“修改”、“写回”成了一个统一的整体,是一个不可分割原子操作。

  你可以把上面代码中的“g_x++”换成“InterlockedExchangeAdd(&g_x, 1)”。

  你也可以将第二个参数写为一个负数,就相当于执行减运算。

 

  还有一些互锁函数:

LONG InterlockedExchange(
   PLONG volatile plTarget,
   LONG lValue);

LONGLONG InterlockedExchange64(
   PLONGLONG volatile plTarget,
   LONGLONG lValue);

PVOID InterlockedExchangePointer(
   PVOID* volatile ppvTarget,
   PVOID pvValue);

  这三个函数将第一个参数(一个指针)指向的数据替换为第二个参数中的值。这三个函数都返回第一个参数指向数据的原始值。

  实现循环锁的时候,InterlockedExchange函数比较有用。

BOOL g_fResourceInUse = FALSE;  //全局变量,指明资源是否正在被访问

void Func1() {     //线程函数
   // 查看访问标志,如果当前值为TRUE表示正在被另一个线程使用,本线程等待
   while (InterlockedExchange (&g_fResourceInUse, TRUE) == TRUE)
      Sleep(0);
   ...     // 访问资源

   InterlockedExchange(&g_fResourceInUse, FALSE);// 将访问标志设为FALSE
}

  还有几个用户比较并修改值或指针的互锁函数:

PVOID InterlockedCompareExchange(
   PLONG plDestination,
   LONG lExchange,
   LONG lComparand);

 

LONGLONG InterlockedCompareExchange64(
   LONGLONG pllDestination,
   LONGLONG llExchange,
   LONGLONG llComparand);

PVOID InterlockedCompareExchangePointer(
   PVOID* ppvDestination,
   PVOID pvExchange,
   PVOID pvComparand);

 

  这三个函数负责执行一个原子的“比较和修改”操作。

  第一个参数:指向需要被修改的值的指针;第二个参数:修改的值;第三个参数:被比较的值。

  拿InterlockedCompareExchange为例。

  函数比较*plDestination(第一个参数指向的值)和lComparand(第三个)的值是否相等,如果相等,则将plDestination指向的值修改为lExchange,否则,就不进行修改。最后,函数返回plDestination代表的原来的值。

 

   还有一些互锁函数:

LONG InterlockedIncrement(PLONG plAddend);      //将参数指向的值加1
LONG InterlockedDecrement(PLONG plAddend);     //将参数指向的值减1

  在Windows XP及其以上版本系统中,除了可以原子地操纵整型和布尔型的值之外,还可以通过一系列函数来操纵一个堆栈,称为“Interlocked Singly Linked List”(互锁单链表)。每一个操作,比如压栈、出栈,都以原子的方式进行操作。

  下表里出了一些作用在“互锁单链表”上的函数:

函数

描述

InitializeSListHead

创建一个空的堆栈

InterlockedPushEntrySList

压栈

InterlockedPopEntrySList

出粘

InterlockedFlushSList

清空堆栈

QueryDepthSList

查询堆栈中元素个数

 

posted on 2008-08-06 13:36  小虎无忧  阅读(1871)  评论(2编辑  收藏  举报

导航