如何安全地将线程安全数据同步至另一线程安全数据结构?
问题解答
两个顾虑的合理性
- 第一个顾虑完全合理:分开调用
a.find()和b.update()时,两次操作之间无锁保护,其他线程可在间隙删除A中对应数据,导致B更新了A已不存在的条目,直接破坏两者同步关系。 - 第二个顾虑也合理:若存在其他线程执行先锁B的mutex,再尝试锁A的mutex的操作,会与当前代码的锁顺序形成循环等待,触发死锁。
可行的解决方案
1. 严格统一锁顺序(工程常用)
规定所有涉及同时操作A和B的线程,必须先锁A的mutex,再锁B的mutex,绝对不能反向操作。从根源上消除循环等待,避免死锁。
比如同步删除操作也要遵守该顺序:
void sync_remove(int i, A& a, B& b) { lock_guard<mutex> lk_a(a.mut); if (a.m.find(i) == a.m.end()) return; a.m.erase(i); lock_guard<mutex> lk_b(b.mut); b.m.erase(i); }
需确保所有相关代码都严格遵循这个锁顺序规则,无例外。
2. 将A和B合并为一个类(设计层面最优)
既然A、B数据强关联且必须同步,本质上属于同一个逻辑实体,没必要拆分为两个独立类。合并后用单个mutex保护所有操作,天然避免同步问题和死锁:
using Ta = some copyable UDT; using Tb = another copyable UDT; class SyncData { private: map<int, Ta> a_map; map<int, Tb> b_map; mutex mut; public: Ta find_a(int i) { lock_guard<mutex> lk(mut); if (auto it = a_map.find(i); it != a_map.end()) { return it->second; } return Ta{}; // 补充默认返回值,避免原代码的未定义行为 } void sync_update(int i, const Ta& a_data, const Tb& b_data) { lock_guard<mutex> lk(mut); a_map[i] = a_data; b_map[i] = b_data; } void sync_remove(int i) { lock_guard<mutex> lk(mut); a_map.erase(i); b_map.erase(i); } // 直接在锁内完成A到B的同步 void sync_a_to_b(int i) { lock_guard<mutex> lk(mut); auto it = a_map.find(i); if (it == a_map.end()) return; b_map[i] = process(it->second); } };
该方案从设计上解决了同步问题,代码更简洁,无死锁风险。
3. 全局互斥锁兜底(快速兼容旧代码)
如果不想修改现有类结构,可引入一个全局mutex,所有涉及A、B同步的操作都先锁这个全局锁,确保同一时间只有一个线程操作两者。缺点是并发度降低,但实现最简单:
mutex global_sync_mut; void sync_a_to_b(A& a, B& b, int i) { lock_guard<mutex> lk_global(global_sync_mut); lock_guard<mutex> lk_a(a.mut); auto it = a.m.find(i); if (it == a.m.end()) return; auto data = it->second; lock_guard<mutex> lk_b(b.mut); b.m[i] = process(data); }
额外提示
原代码中的find函数存在未定义行为:当找不到元素时无返回值,需补充默认返回值或抛出异常。
内容的提问来源于stack exchange,提问作者f1msch
相关产品推荐
相关产品推荐

