Win32多线程之临界区(Critical Sections)

来源:互联网 发布:nginx 配置http 307 编辑:程序博客网 时间:2024/06/17 13:00

Win32之中最容易使用的一个同步机制就是 critical sections。所谓critical sections意思是指一小块“用来处理一份呗共享的资源”的程序代码。这里所谓的资源,并不是指.res(资源文件)的Windows资源,而是广义上地指一块内存,一个数据结构,一个文件,或任何其他具有“使用之排他性”的东西。也就是说,“资源”每一次(同一时间内)只能够被一个线程处理。

  你可能必须在程序的许多地方处理这一块共享的资源,所有这些程序代码可以被同一个critical section保护起来。为了阻止问题发生,一次只能有一个线程获准 进入critical section中(相对地也就是说资源受到了保护)。实施的方式是在程序中加上“进入”或“离开”critical section 的操作,如果有一个线程已经“进入“某个critical section,另外一个线程就绝对不能够进入同一个critical section。

   在Win32程序中,你可以为每一个需要保护的资源声明一个CRITICAL_SECTION类型的变量。这个变量扮演红绿灯的角色,让同一时间内只有一个线程进入critical section。

  Critical section并不是核心对象。因此,没有所谓handle这样的东西。它和核心对象不同,它存在于进程的内存空间中。你不需要使用像”Create“这样的API函数获得一个critical sectionhandle,你应该做的事将一个类型为CRITICAL_SECTION的局部变量初始化,方法是调用InitializeCriticalSection():

   VOID InitializeCriticalSection(

         LPCRITICAL_SECTION lpCriticalSection 

);

参数:

lpCriticalSection: 一个指针,指向欲被初始化的CRITICAL_SECTION变量,这个变量应该在你的程序中定义。

返回值:void。

下面就是一个基本的调用程序,用来产生并摧毁一个critical section。请注意:gCriticalSection被声明在程序的最上方,作为任一线程都可以使用的全局变量。

CRITICAL_SECTION  gCriticalSection;

voidCreateDeleteCriticalSection()

{

     InitializeCriticalSection(&gCriticalSection);

         /* Do something here */

     DeleteCriticalSection(&gCriticalSection);

}

一旦critical section被初始化,每一个线程就可以进入其中------只要它通过了EnterCriticalSection()这一关。

void EnterCriticalSection(

    LPCRITICAL_SECTION lpCriticalSection ;

);

参数:

lpCriticalSection: 一个指针,指向一个你即将锁定的CRITICAL_SECTION变量。

返回值:void。

   当线程准备好要离开critical section时,它必须调用LeaveCriticalSection():

void LeaveCriticalSection(&cs)(

   LPCRITICAL_SECTION lpCriticalSection ;

);

参数:

lpCriticalSection: 一个指针,指向一个你即将解除锁定的CRITICAL_SECTION变量。

返回值:void。

 延续刚才的例子,下面是使用临界区的一个例子:

 void UpdateData()

{

       EnterCriticalSection(&gCriticalSection);

           /* Update the resource   */

       LeaveCriticalSection(&gCriticalSection);

}

  你可能会发现,有好几个函数都需要进入同一个critical section(以上而言指的就是gCriticalSection)中,它们都前后包夹着Enter/Leave函数,并使用相同的参数,你应该在每一个存取全局数据的地方使用Enter/Leave函数。有时候Enter/Leave甚至会在同一个函数中出现数次------如果这个函数需要很长的运行时间。下面是一个互斥访问链表的一个例子:

typedef struct _Node

{
             struct _Node *next;
             int data;
          } Node;


typedef struct _List
{
  Node *head;
  CRITICAL_SECTION  critical_sec;
} List;


List *CreateList()
{
  List *pList = (List *)malloc(sizeof(pList));
  pList->head = NULL;
  InitializeCriticalSection(&pList->Critical_sec);
  return pList;
}


void DeleteList(List *pList)
{
  DeleteCriticalSection(&pList->critical_sec);
  free(pList);
}


void AddHead(List *pList,Node *node)
{
   EnterCriticalSection(&pList->critical_sec);
   node->next = pList->head;
   pList->head = node;
   LeaveCriticalSection(&pList->critical_sec);
}


void Insert(List *pList, Node *afterNode, Node *newNode)
{
  EnterCriticalSection(&pList->critical_sec);
if( afterNode == NULL)

AddHead(pList, newNode);
}
else
{
newNode->next = afterNode->next;
afterNode->next = newNode;
}
  LeaveCriticalSection(&pList->critical_sec);
}


Node *Next(List *pList, Node *node)
{
   Node *next;
   EnterCriticalSection(&pList->critical_sec);
     next = node->next;
   LeaveCriticalSection(&pList->critical_sec);
   return next;
}

加上了额外的 的critical section操作之后,同一时间内最多只有一个人能够读或写链表内容。请注意,我把CRITICAL_SECTION 变量放在List结构之中,你也可以使用一个全局变量取代之,但我是希望一个链表实体都能够独立地读写,如果只使用一个全局性的critical section,就表示一次只能读写一个链表,这回产生效率上的严重问题。

 你或许纳闷,为什么Next()也需要环绕一个critical section,毕竟它只是处理单一一个值而已。请注意,return node->next 实际上被编译为数个机器指令,而不是一个”原子操作“(所谓的atomic operation)。如果我们在前后加上critical section的保护,就能够强迫该操作成为”不可分割的“。

 上述程序代码存在一个微妙点。在Next离开critical section之后,但尚未return之前,没有什么东西能够保护这个node免受另一个线程的删除操作,这个问题可以靠更高阶的"readers/writers锁定"来解决,我们将在后面解释怎么做。

这个简短的例子也说明了Win32 critical section的另一个性质,一旦线程进入critical section,它就能够一再地重复进入该critical section。这也就是为什么Insert()可以调用AddHead()而不需要先调用LeaveCriticalSection()的缘故。唯一的警告就是,每一个”进入“操作都必须有一个对应的”离开“操作,如果某个线程调用EnterCriticalSection()5次,它也必须调用LeaveCriticalSection()5次,该critical section才能够被释放。




原创粉丝点击