You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让两个线程安全访问C++类中的共享vector资源

解决Worker类共享vector的线程安全问题

嘿,我来帮你梳理下这个C++线程安全的问题。你现在的场景是两个线程同时遍历databases_list_这个vector,还会调用其中database对象的接口,要保障安全可以从这几个层面一步步来:

1. 先搞定is_work_的线程可见性问题

你现在用普通bool作为线程循环的终止条件,但普通bool不是原子类型,跨线程读写会有可见性隐患——比如主线程把is_work_设为false,线程#1可能迟迟看不到这个修改,导致线程无法正常退出。所以第一步要把它改成原子类型:

#include <atomic>

class Worker {
private:
    // ... 其他成员不变
    std::atomic<bool> is_work_{ false }; // 替换原来的bool类型
};

这样不管哪个线程修改is_work_,其他线程都能立刻看到最新值。

2. 保障databases_list_的遍历安全

首先得明确一个前提:如果databases_list_的结构不会被修改(也就是不会调用push_back、pop_back、erase这类改变vector大小或元素位置的操作),那多个线程同时只读遍历这个vector是安全的——因为vector的迭代器在结构稳定时不会失效,纯读操作也不会产生数据竞争。

但如果你的业务里存在其他线程(比如主线程)会修改这个vector的结构(比如添加/删除database对象),那必须给vector加锁保护:

方案:用互斥锁包裹所有vector访问

给Worker类加一个std::mutex成员,然后在所有访问databases_list_的地方(包括遍历、修改)都用锁包裹:

#include <mutex>

class Worker {
private:
    // ... 其他成员不变
    std::vector<std::unique_ptr<database>> databases_list_;
    std::mutex db_list_mutex_; // 专门保护databases_list_的互斥锁
};

然后修改两个线程函数:

void Worker::check_oo() {
    while (is_work_) {
        // std::lock_guard会自动在作用域开始加锁,结束时解锁,避免忘记解锁
        std::lock_guard<std::mutex> lock(db_list_mutex_);
        for (auto& db : databases_list_) {
            auto db_cfg = db->get_cfg();
            data_handler_.set_database(db_cfg.db_server);
            auto db_respond = db->send_command(receive_cmd, command_type::get_data);
        }
        std::this_thread::sleep_for(std::chrono::seconds(processing_period_1));
    }
}

void Worker::check_db() {
    while (is_work_) {
        std::lock_guard<std::mutex> lock(db_list_mutex_);
        for (auto& db : databases_list_) {
            auto db_cfg = db->get_cfg();
            data_handler_.set_database(db_cfg.db_server);
            auto db_respond = db->send_command(receive_cmd, command_type::get_stat);
        }
        std::this_thread::sleep_for(std::chrono::seconds(processing_period_2));
    }
}

要是有其他修改databases_list_的代码(比如添加新database的方法),也要用同样的std::lock_guard加锁,确保同一时间只有一个线程在操作这个vector。

3. 别漏了database对象本身的线程安全

上面的锁只保护了databases_list_这个容器的访问,但两个线程可能同时调用同一个database对象的get_cfg()或send_command()方法。如果database类的这些接口不是线程安全的(比如内部有共享的非原子变量,或者有写操作),那即使容器访问安全了,还是会出现数据竞争。

所以你需要检查database类的实现:

  • 如果get_cfg()是纯只读操作,返回的是值或者内部没有修改逻辑,那没问题;
  • 如果send_command()会修改database对象的内部状态,那得给database类内部加锁(比如每个database实例自己带一个std::mutex),或者用其他同步机制保障线程安全。

4. 优化:减少锁的持有时间提升并发效率

如果遍历vector的时间很长,或者send_command()是耗时操作,上面的方案会导致两个线程互相等待,降低并发效率。这时候可以考虑先复制一份容器的快照,让锁只在复制阶段持有,后续遍历快照不需要锁:

void Worker::check_oo() {
    while (is_work_) {
        std::vector<database*> db_ptrs;
        {
            std::lock_guard<std::mutex> lock(db_list_mutex_);
            db_ptrs.reserve(databases_list_.size());
            // 先把所有database的指针收集起来
            for (auto& db : databases_list_) {
                db_ptrs.push_back(db.get());
            }
        } // 这里锁已经释放了,后面的耗时操作不需要持有锁
        for (auto db_ptr : db_ptrs) {
            auto db_cfg = db_ptr->get_cfg();
            data_handler_.set_database(db_cfg.db_server);
            auto db_respond = db_ptr->send_command(receive_cmd, command_type::get_data);
        }
        std::this_thread::sleep_for(std::chrono::seconds(processing_period_1));
    }
}

这个方案的好处是锁的持有时间极短,两个线程的并发效率更高,但要注意:如果这时候有线程删除了某个database对象,db_ptr就会变成悬空指针。所以这种方式只适合databases_list_的元素不会被删除的场景,或者你可以把std::unique_ptr<database>改成std::shared_ptr<database>——这样即使容器里的元素被移除,只要还有线程持有shared_ptr,database对象就不会被销毁,避免悬空指针问题。

最后总结下关键步骤

  1. 把is_work_替换成std::atomic<bool>,解决线程可见性问题;
  2. 给databases_list_添加互斥锁,保护所有容器访问操作;
  3. 确保database类的接口是线程安全的;
  4. 根据业务场景优化锁的持有时间,提升并发效率。

内容的提问来源于stack exchange,提问作者pipetka

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 19:57:29