Win32多线程 -- 线程同步之关键区域(临界区)与死锁

下面是链表结合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"(要不统统获得,要不统统没有)可以阻止死锁的发生。
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值