下面是链表结合CRITICAL_SECTION的例子(critical section不是内核对象):
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(pist));
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); // 一旦线程进入某个critical section, 他就可以一再进入该critical section
// 而不需要先LeaveCriticalSection
}
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 的保护,就能够强迫中间的操作成为"不可分割的"。请注意,我把 CRITICAL_SECTION 变量放在 List 结构之中。你也可以使用一个全局变量取代之,但我是希望每一个链表实体都能够独立地读写。如果只使用一个全局性 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才能够被释放。
Dangling Critical Sections
Critical section 的一个缺点就是,没有办法获知进入 critical section 中的那个线程是生是死。从另一个角度看,由于 critical section 不是核心对象,如果进入critical section的那个线程结束了或当掉了,而没有调用LeaveCriticalSection()的话,系统没有办法将该critical section清除.如果你需要那样的机能,你应该使用mutex. 在Windows NT之中,如果一个线程进入某个critical section而在未离开的情况下就结束,该critical section会被永远锁住。
死锁 Deadlock
为每一个链表准备一个critical section之后,我却开启了另一个问题。请看下面这个用来交换两个链表内容的函数:
void SwapLists(List *list, List *list2)
{
List *tmp_list;
EnterCriticalSection(list1->critical_sec);
EnterCriticalSection(list2->critical_sec);
tmp->list = list1->head;
list1->head = list2->head;
list2->head = temp->list;
LeaveCriticalSection(list1->critical_sec);
LeaveCriticalSection(list2->critical_sec);
}
假设下面两次调用发生在不同线程的同一个时间点:
线程A SwapLists(home_address_list, work_address_list);
线程B SwapLists(work_address_list, home_address_list);
而在线程A的SwapLists()的第一次EnterCriticalSection()之后, 发生了context switch ,然后线程B执行了它的SwapLists()操作, 两个线程于是会落入"我等你,你等我"的轮回, 而双方都掌握有对方所要的东西, 这种情况称为死锁(deadlock).
任何时候当一段代码需要两个(或更多)资源时,都有潜在性的死锁阴影。强迫将资源锁定,使它们成为"all-or-nothing"(要不统统获得,要不统统没有)可以阻止死锁的发生。