防重入或并发调用(C++)
防重入或并发调用
- 1. std::mutex + std::unique_lock(try_to_lock)
- 2. QMutex + QMutexLocker(AdoptLock)
- 3. std::atomic_flag + 简易 RAII 守卫
- 4. 减少重复样板:宏或模版封装
- 宏
- 模板
- 小结
通常我们要防止同一方法在执行过程被「重入」(Re-entrance)或被并发调用,可以在方法入口做一个简单的“忙碌”检测,常见做法有两种:基于互斥锁(mutex)/尝试锁(try-lock),或基于原子标志(atomic flag)+ 小型 RAII 守卫。
1. std::mutex + std::unique_lock(try_to_lock)
#include <mutex>class MyClass {
public:void foo() {// 尝试获取锁,如果获取失败说明已有一次 foo() 在执行,直接返回std::unique_lock<std::mutex> lk(foo_mtx_, std::try_to_lock);if (!lk) {// 可选:记录日志或发信号告知“已在执行中”return;}// —— 这里是真正的业务逻辑 —— doHeavyWork();}void bar() {std::unique_lock<std::mutex> lk(bar_mtx_, std::try_to_lock);if (!lk) return;doOtherWork();}private:std::mutex foo_mtx_;std::mutex bar_mtx_;void doHeavyWork();void doOtherWork();
};
-
优点:跨线程安全;不用显式解锁,unique_lock 会在作用域尾自动释放。
-
缺点:每个方法要维护一个 std::mutex 成员,稍微繁琐。
2. QMutex + QMutexLocker(AdoptLock)
如果你想全部用 Qt 类型,也可以:
#include <QMutex>
#include <QMutexLocker>class MyClass {
public:void foo() {// tryLock() 返回 false 时表示已有执行if (!fooMtx_.tryLock()) return;// 采用 AdoptLock 模式,让 QMutexLocker 在析构时释放QMutexLocker locker(&fooMtx_, QMutexLocker::AdoptLock);doHeavyWork();}private:QMutex fooMtx_;void doHeavyWork();
};
tryLock() + QMutexLocker(…, AdoptLock) 等同于上面 std::try_to_lock 的模式。
3. std::atomic_flag + 简易 RAII 守卫
如果你仅仅需要同线程下防止重入,或者执行体非常短暂,也可以考虑更轻量的原子标志法:
#include <atomic>class MyClass {
public:void foo() {// test_and_set 返回前一个值:如果已经被 set 过(true),就直接 returnif (busyFoo_.test_and_set(std::memory_order_acquire)) return;// RAII 守卫:离开作用域时自动 clearstruct FlagGuard {std::atomic_flag &f;FlagGuard(std::atomic_flag &flag): f(flag) {}~FlagGuard() { f.clear(std::memory_order_release); }} guard(busyFoo_);doHeavyWork();}private:std::atomic_flag busyFoo_ = ATOMIC_FLAG_INIT;void doHeavyWork();
};
-
优点:无锁开销(其实底层也是原子操作),代码简洁。
-
缺点:只能防同线程或跨线程对同方法的并发调用,不适合复杂锁依赖。
4. 减少重复样板:宏或模版封装
如果方法很多,不想给每个都写一堆相似代码,可以把“尝试加锁 + RAII 解锁”封装成一个小宏或模版,例如:
宏
// nonreentrant.h
#pragma once
#include <atomic>
#define NON_REENTRANT(method) \static std::atomic_flag _busy_##method = ATOMIC_FLAG_INIT; \if (_busy_##method.test_and_set(std::memory_order_acquire)) \return; \auto _guard_##method = [&]() { \struct G { ~G() { _busy_##method.clear(std::memory_order_release); } } g; \}(); // MyClass.cpp
#include "nonreentrant.h"
void MyClass::foo() {NON_REENTRANT(foo);// … 实际逻辑 …
}
void MyClass::bar() {NON_REENTRANT(bar);// … 实际逻辑 …
}
这样每个方法顶部只要一行 NON_REENTRANT(方法名),就能自动完成标志检测和清理。
模板
// NonReentrantGuard.h
#pragma once
#include <atomic>/*** @brief 非重入守卫:同一 Tag 只允许一个实例“激活”其所在作用域** Tag 用来区分各个不同的方法/调用点。*/
template<typename Tag>
class NonReentrantGuard {
public:NonReentrantGuard(): engaged_(!flag_.test_and_set(std::memory_order_acquire)){}~NonReentrantGuard() {if (engaged_) {flag_.clear(std::memory_order_release);}}/// 转换为 bool:为 true 时表示“首次进入”,可以继续执行;为 false 则应直接返回explicit operator bool() const noexcept {return engaged_;}private:static std::atomic_flag flag_;bool engaged_;
};// 必须在某个翻译单元里定义静态成员
template<typename Tag>
std::atomic_flag NonReentrantGuard<Tag>::flag_ = ATOMIC_FLAG_INIT;
// MyClass.h
#include "NonReentrantGuard.h"class MyClass {
public:void foo();void bar();private:void doHeavyWork();void doOtherWork();
};
小结
-
跨线程/性能可控:std::mutex + std::try_to_lock 或者 Qt 的 QMutex::tryLock()
-
轻量级同线程:std::atomic_flag + 简易 RAII 守卫
-
大批方法:可用宏或模板封装上面逻辑,减少重复代码
选用时只要注意解锁一定要在所有退出路径(包括异常)都会执行 —— RAII 守卫或 unique_lock/QMutexLocker 都能帮你做到这一点。这样就能保证「正在运行」的那次调用结束前,任何重复触发都会被忽略。