防重入或并发调用
通常我们要防止同一方法在执行过程被「重入」(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),就直接 return
if (busyFoo_.test_and_set(std::memory_order_acquire)) return;
// RAII 守卫:离开作用域时自动 clear
struct 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 都能帮你做到这一点。这样就能保证「正在运行」的那次调用结束前,任何重复触发都会被忽略。