上一篇文章中,我们已经了解了观察者模式的基本概念和同步通知的实现。在实际的 C++ 项目中,尤其是在高并发场景下,同步通知可能会阻塞主线程,导致响应缓慢。因此,异步通知和线程安全是观察者模式更高级的应用。本篇我们深入探讨 观察者模式 在异步场景下的实现,以及如何保证线程安全。
异步通知的实现
异步通知的核心思想是将通知过程放到独立的线程中执行,避免阻塞主线程。可以使用 C++11 的 std::thread 或者线程池来实现。
使用 std::thread 的简单示例:
#include <iostream>
#include <vector>
#include <thread>
#include <functional>
class Subject {
public:
void attach(std::function<void()> observer) {
observers_.push_back(observer);
}
void detach(std::function<void()> observer) {
// ... 实现移除 observer 的逻辑
}
void notify() {
for (auto& observer : observers_) {
std::thread t(observer); // 创建新线程执行 observer
t.detach(); // 让线程独立运行
}
}
private:
std::vector<std::function<void()>> observers_;
};
class ConcreteObserver {
public:
void update() {
std::cout << "Observer notified in thread: " << std::this_thread::get_id() << std::endl;
}
};
int main() {
Subject subject;
ConcreteObserver observer1;
ConcreteObserver observer2;
subject.attach([&]() { observer1.update(); });
subject.attach([&]() { observer2.update(); });
subject.notify();
// 主线程可能比子线程先结束,需要处理好同步问题
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 简单等待
return 0;
}
注意事项:
- 使用
std::thread需要注意线程的生命周期管理。t.detach()会让线程独立运行,但主线程可能在子线程完成之前就结束了。可以使用t.join()等待线程结束,但会阻塞主线程。通常,更好的方式是使用线程池,统一管理线程的生命周期。 - 上述代码仅仅是演示异步通知的基本实现,没有处理异常情况。实际项目中需要增加异常处理机制。
线程安全问题与解决方案
在多线程环境下,多个线程可能同时访问和修改观察者列表,导致数据竞争。需要使用锁机制来保护共享资源。
使用 std::mutex 保护观察者列表:
#include <iostream>
#include <vector>
#include <thread>
#include <mutex>
#include <functional>
class Subject {
public:
void attach(std::function<void()> observer) {
std::lock_guard<std::mutex> lock(mutex_);
observers_.push_back(observer);
}
void detach(std::function<void()> observer) {
std::lock_guard<std::mutex> lock(mutex_);
// ... 实现移除 observer 的逻辑,同样需要加锁
}
void notify() {
std::lock_guard<std::mutex> lock(mutex_); //保护observers_的读取
for (auto& observer : observers_) {
std::thread t(observer); // 创建新线程执行 observer
t.detach(); // 让线程独立运行
}
}
private:
std::vector<std::function<void()>> observers_;
std::mutex mutex_;
};
注意事项:
- 使用
std::lock_guard可以自动管理锁的生命周期,避免忘记解锁导致死锁。 - 所有访问和修改
observers_的地方都需要加锁,包括attach、detach和notify方法。 - 如果观察者的
update方法也需要访问共享资源,同样需要加锁。需要仔细设计锁的粒度,避免过度加锁导致性能下降。 - 在高并发情况下,可以考虑使用读写锁(
std::shared_mutex)来提高性能。读写锁允许多个线程同时读取共享资源,但只允许一个线程写入共享资源。
实战避坑经验总结
- 避免循环依赖: 观察者和主题之间可能存在循环依赖,导致内存泄漏或者无限循环。需要仔细设计类之间的关系,避免出现这种情况。
- 处理异常: 在异步通知中,如果观察者的
update方法抛出异常,可能会导致程序崩溃。需要在notify方法中捕获异常,并进行适当的处理,例如记录日志或者通知错误处理模块。 - 性能优化: 当观察者数量很多时,
notify方法的性能可能会成为瓶颈。可以考虑使用更高效的数据结构来存储观察者,例如哈希表。也可以使用线程池来限制并发线程的数量,避免系统资源耗尽。 - 序列化问题: 当观察者或者主题需要序列化时,需要注意处理观察者列表。通常,不应该序列化观察者列表,而是应该在反序列化之后重新注册观察者。
- Nginx 反向代理中监听端口变化: 在 Nginx 反向代理配置中,如果后端服务监听的端口发生变化,需要及时更新 Nginx 的配置。可以使用观察者模式,让 Nginx 配置管理模块监听后端服务的端口变化事件,并在端口变化时自动更新 Nginx 配置并重新加载。这可以结合宝塔面板的 API 接口实现自动化。
- 负载均衡中的服务器健康检查: 在负载均衡系统中,需要定期检查后端服务器的健康状态。可以使用观察者模式,让负载均衡器监听后端服务器的健康状态变化事件,并在服务器状态变化时自动调整流量分配策略。这涉及到对后端服务器并发连接数的监控,以及服务熔断机制的设计。
掌握了异步通知和线程安全,以及避坑经验,才能在实际项目中更好地应用 观察者模式。
冠军资讯
代码一只喵