基于std::atomic实现互斥锁的相关技术问题咨询
线程安全锁实现疑问解答
class AtomicLock { std::atomic_flag flag; public: void lock() { while (flag.test_and_set(std::memory_order_acquire)) flag.wait(true, std::memory_order_acquire); } void unlock() { flag.clear(std::memory_order_release); flag.notify_one(); } }; class ThreadAtomicLock { static std::atomic_long id_generator; std::atomic<long> owner_thread = -1; static long get_id() { thread_local auto id = id_generator++; return id; } public: void lock() { long expected = -1; while (!owner_thread.compare_exchange_weak(expected, get_id(), std::memory_order_acquire)) { owner_thread.wait(expected, std::memory_order_acquire); expected = -1; } } void unlock() { long expected = get_id(); assert ( owner_thread.compare_exchange_strong(expected, -1, std::memory_order_release) && "unlock of unowned lock" ); owner_thread.notify_one(); } }; std::atomic_long ThreadAtomicLock::id_generator = 0; void testAtomicLock() { std::vector<std::thread> threads; std::atomic<bool> flag = false; // AtomicLock lock; ThreadAtomicLock lock; for (int i = 0; i < 100; i++) { threads.emplace_back([&] { while (!flag.load(std::memory_order_acquire)); lock.lock(); std::cout << "Hello"; std::cout << " from thread "; std::cout << std::this_thread::get_id() << " "; int n = 10; while (n--) std::cout << "="; std::cout << "\n"; lock.unlock(); }); } flag.store(true, std::memory_order_release); for (auto& t : threads) t.join(); }
上述AtomicLock类可正常运行,但存在任意线程均可解锁非自身持有的锁的问题。为此实现了ThreadAtomicLock类,确保仅持有锁的线程才能执行解锁操作,针对该实现有以下疑问及解答:
1. 该实现是否完整正确?能否放宽约束?
这个实现基本正确,满足互斥性、所有权校验、内存可见性等锁的核心要求,但有可优化或需注意的点:
- 正确性:通过
acquire/release内存序保证了临界区操作的跨线程可见性,所有权校验逻辑能阻止非持有线程解锁,互斥逻辑也能保证同一时间只有一个线程进入临界区。 - 可调整的约束:
lock()中每次wait后重置expected=-1的逻辑可以保留,若想优化可利用compare_exchange_weak失败后更新的expected值,但当前逻辑更清晰。- 内存序不能随意放宽:
lock()的acquire和unlock()的release是保证内存同步的关键,必须保留。 - 潜在缺失:未支持递归加锁,若同一线程多次调用
lock()会触发死锁,若需要该特性,可添加线程本地计数器记录加锁次数。
2. 解锁函数中的compare_exchange_strong能否替换为compare_exchange_weak?
不建议替换:
compare_exchange_weak存在伪失败可能(CPU层面的虚假失败,即使值匹配也可能返回false),而unlock()是必须保证原子性的操作——只有当前线程持有锁时,才能将owner_thread置为-1。- 伪失败会导致断言触发(即使线程合法持有锁),引发无意义的错误。
compare_exchange_strong能保证只要值匹配就一定会成功,适合这种必须一次性完成的场景。
3. 线程ID生成机制是否有更优实现?
当前方案已经高效,可考虑两种替代方向:
- 复用
std::thread::id:通过std::hash<std::thread::id>{}(std::this_thread::get_id())将系统线程ID转换为整数,无需额外原子生成操作。但要注意哈希值存在极低的冲突概率,对唯一性要求极高时原方案更稳妥。 - 使用平台原生线程ID:比如Linux的
gettid()、Windows的GetCurrentThreadId(),这类ID是系统级唯一值,无需自行生成,但会引入平台依赖,跨平台场景下不适用。
4. 锁定函数中的wait调用能否使用memory_order_relaxed?
不能:
wait的内存序参数决定了唤醒后对原子变量的加载语义。当线程被notify_one唤醒时,需要确保后续的compare_exchange_weak能看到其他线程释放锁时的owner_thread更新(从线程ID变为-1)。- 若使用
memory_order_relaxed,无法保证这种内存可见性,可能导致线程唤醒后仍读取旧值,进入无效循环甚至触发逻辑错误,必须保留memory_order_acquire来保证同步。
内容的提问来源于stack exchange,提问作者Osama Ahmad
相关产品推荐
相关产品推荐

