Understanding the consequences of WAIT_ABANDONED - The Old New Thing (microsoft.com)https://devblogs.microsoft.com/oldnewthing/20050912-14/?p=34253
Raymond Chen 2005年09月12日
理解 WAIT_ABANDONED 的后果
简要
文章讨论了在多线程同步中,如果一个线程在没有释放互斥体(mutex)的情况下退出,互斥体会被操作系统自动释放,这种情况被称为“WAIT_ABANDONED”,并且这种情况可能会导致严重的问题。
正文
互斥体与其他同步对象的一个重要区别在于,互斥体有所有者。如果拥有互斥体的线程在没有释放互斥体的情况下退出,互斥体会自动代表线程释放。
但当这种情况发生时,你就遇到大麻烦了。
许多人忽视的一件事是同步函数(如 WaitForSingleObject
)返回的 WAIT_ABANDONED
值。他们通常将其视为成功的等待,因为它确实意味着对象已被获取,但它还告诉你之前的所有者遗弃了互斥体,系统不得不代表所有者释放它。
当这种情况发生时,你将遇到什么样的大麻烦?
假设你创建了那个互斥体来保护多个线程在共享对象处于不稳定状态时不对其进行访问。代码进入互斥体,然后开始操作对象,暂时使其处于不稳定状态,但最终使其恢复稳定,并释放互斥体,以便下一个人可以访问该对象。
例如,你可能有代码管理共享内存中的锚定双向链表,代码如下:
void MyClass::ReverseList() {
WaitForSingleObject(hMutex, INFINITE);
int i = 0; // 锚点
do {
int next = m_items[i].m_next;
m_items[i].m_next = m_items[i].m_prev;
m_items[i].m_prev = next;
i = next;
} while (i != 0);
ReleaseMutex(hMutex);
}
没有什么特别激动人心,很基本的东西,对吧?
但如果程序在持有互斥体时崩溃呢?(如果你认为你的程序没有错误,考虑一下程序在运行时网络出现问题,导致页面异常。或者用户只是在这个函数运行时去了任务管理器并终止了你的程序。)
在这种情况下,操作系统会自动释放互斥体,使链表处于损坏状态。下一个请求互斥体的程序将收到 WAIT_ABANDONED
作为状态码。
如果你忽略了那个状态码,你最终会在一个损坏的链表上操作。根据链表的使用方式,它可能导致资源泄漏,或者系统意外地创建了某物的第二个副本,甚至可能导致崩溃。一个程序的不幸消亡导致其他程序开始异常行为。
然而,问题仍然存在,“那么,如果你得到 WAIT_ABANDONED
怎么办?”答案是,“好问题。”
你可能尝试修复损坏,如果你保留足够的辅助信息来恢复一致的状态。你甚至可以设计你的数据结构为事务性的,这样操纵数据结构的线程的死亡不会使它们处于损坏状态。
或者你可能决定,由于事情已经损坏,你应该丢弃一切并重新开始,失去了正在进行的工作状态,但至少允许新的工作无阻碍地进行。
或者你可能会简单地选择忽略错误并继续使用损坏的数据结构,希望无论出了什么问题都不会导致后续的连锁故障。这是大多数人的做法,尽管通常他们甚至没有意识到自己在这么做。而且,由此方法导致的崩溃真的很难调试。
练习:为什么我们在链表数据结构中使用索引而不是指针?
Raymond 目前正在外出;此消息是预录的。