线程锁定可能已销毁的互斥锁问题排查与代码优化咨询
问题1:当前加锁逻辑在节点被删除时的风险
如果其他线程持有n的锁并执行删除操作,会触发两类严重问题:
- 阻塞后野指针访问:如果删除线程拿到
n的锁后直接释放了节点内存,你当前代码阻塞拿到锁后,访问的是已经释放的内存空间,属于未定义行为,会直接导致程序崩溃、数据污染等异常。 - 现有代码本身还有额外的逻辑漏洞:未初始化
prev变量、遍历逻辑无法正常推进、锁释放顺序错误存在死锁风险、匹配成功后未释放已持有锁导致锁泄漏。
问题2:风险规避方案
核心要保证两个前提:
- 节点访问生命周期可控:要么调用
find_match前上层已持有n的锁保证节点不会被释放,要么给节点增加引用计数字段,访问节点前先增加引用,访问结束减少引用,只有引用计数归0时才真正销毁节点。 - 统一加锁顺序:所有涉及多锁的场景遵守相同的加锁顺序,比如永远先锁传入节点
n,再锁遍历链表的节点,彻底避免死锁。
问题3:更合理的实现方式
采用手递手加锁逻辑遍历第二个链表,保证遍历过程中不会出现野指针,锁操作不会泄漏:
// 约定:调用方传入n前需保证n的生命周期有效,要么持有n的锁,要么n的引用计数已+1 node_t *find_match(list_t *l, node_t *n) { // 入参合法性校验 if (l == NULL || n == NULL) { return NULL; } // 统一先锁n,避免死锁(如果上层已经持有n的锁可删除该步,注意非递归锁不可重复加锁) pthread_mutex_lock(&n->lock); // 锁链表头,取遍历起始节点 pthread_mutex_lock(&l->lock); node_t *cur = l->head; if (cur == NULL) { pthread_mutex_unlock(&l->lock); pthread_mutex_unlock(&n->lock); return NULL; } // 手递手加锁:锁第一个节点后释放链表锁,降低链表全局锁粒度 pthread_mutex_lock(&cur->lock); pthread_mutex_unlock(&l->lock); node_t *matched_node = NULL; while (cur != NULL) { // 两个节点均已加锁,可安全执行数据匹配 if (/* 此处替换为你的节点数据匹配逻辑 */ 0) { matched_node = cur; // 匹配成功时持有cur的锁返回,由调用方负责释放cur和n的锁 // 如果不需要返回持锁节点,可拷贝数据后释放锁再返回拷贝值 break; } node_t *next = cur->next; if (next == NULL) { pthread_mutex_unlock(&cur->lock); break; } // 先锁下一个节点,再释放当前节点锁,保证遍历连续性 pthread_mutex_lock(&next->lock); pthread_mutex_unlock(&cur->lock); cur = next; } // 无匹配节点时释放n的锁 if (matched_node == NULL) { pthread_mutex_unlock(&n->lock); } return matched_node; }
如果业务不允许返回持锁节点,可在匹配成功时将节点数据拷贝到栈或堆内存中,释放所有锁后再返回拷贝的数据,避免上层遗漏解锁导致的问题。
内容的提问来源于stack exchange,提问作者ExhaustedCProgrammer
相关产品推荐
相关产品推荐

