C++11中Mutex与Condition Variable无法拷贝的解决方案咨询
你碰到的问题很典型——std::mutex和std::condition_variable在C++标准库中是被设计为不可拷贝、不可移动的,它们的拷贝构造函数和赋值运算符都被显式delete了。这是因为这些同步原语是用来协调线程间操作的,拷贝它们没有实际意义:拷贝后的mutex是全新的独立对象,无法和原有的mutex同步线程;condition_variable则和特定的mutex绑定工作,拷贝会破坏这种关联逻辑。
下面给你几种可行的解决方案,按常用程度排序:
方案1:使用引用成员(最直接)
把类中的mutex和condition_variable声明为引用类型,构造函数通过初始化列表来初始化引用(注意引用成员必须在初始化列表中初始化,不能在构造函数体里赋值)。这样生产者类会直接使用main函数中创建的原对象,避免拷贝。
修改后的代码:
#include <mutex> #include <condition_variable> class producer { public: // 构造函数接收引用参数,并用初始化列表初始化成员引用 producer(std::mutex& m, std::condition_variable& cv) : mut(m), cvar(cv) {} // 后续可以在成员函数中正常使用mut和cvar,比如: void produce() { std::lock_guard<std::mutex> lock(mut); // 生产操作... cvar.notify_one(); } private: std::mutex& mut; std::condition_variable& cvar; }; int main() { std::mutex m; std::condition_variable cv; producer prod(m, cv); // 直接传递引用,无拷贝 return 0; }
⚠️ 注意:必须保证main函数中的m和cv的生命周期长于producer对象,否则会出现悬空引用问题(比如如果producer对象被放到堆上,而main退出后销毁了m和cv,producer再使用就会崩溃)。
方案2:使用智能指针(更灵活)
如果担心生命周期问题,或者需要在多个线程/对象间共享这些同步原语,可以用std::shared_ptr来管理它们的生命周期。这样只要有对象持有智能指针,底层的mutex和condition_variable就不会被销毁。
代码示例:
#include <mutex> #include <condition_variable> #include <memory> class producer { public: producer(std::shared_ptr<std::mutex> m_ptr, std::shared_ptr<std::condition_variable> cv_ptr) : mut_ptr(m_ptr), cvar_ptr(cv_ptr) {} void produce() { if (!mut_ptr || !cvar_ptr) { // 处理空指针情况 return; } std::lock_guard<std::mutex> lock(*mut_ptr); // 生产操作... cvar_ptr->notify_one(); } private: std::shared_ptr<std::mutex> mut_ptr; std::shared_ptr<std::condition_variable> cvar_ptr; }; int main() { // 用make_shared创建智能指针 auto m = std::make_shared<std::mutex>(); auto cv = std::make_shared<std::condition_variable>(); producer prod(m, cv); return 0; }
方案3:封装到共享结构体(更整洁)
如果你的生产者和消费者还需要共享其他数据(比如任务队列、停止标志),可以把mutex、condition_variable和这些共享数据一起封装到一个结构体中,然后传递结构体的引用或指针。这样代码结构更清晰,也避免了传递多个独立同步原语的麻烦。
代码示例:
#include <mutex> #include <condition_variable> #include <queue> // 封装所有共享数据和同步原语 struct SharedResources { std::mutex mut; std::condition_variable cv; std::queue<int> task_queue; // 比如生产者生产的任务队列 bool stop = false; // 停止标志 }; class producer { public: producer(SharedResources& resources) : res_(resources) {} void produce(int item) { std::lock_guard<std::mutex> lock(res_.mut); res_.task_queue.push(item); res_.cv.notify_one(); // 通知消费者有新任务 } private: SharedResources& res_; }; class consumer { public: consumer(SharedResources& resources) : res_(resources) {} void consume() { std::unique_lock<std::mutex> lock(res_.mut); // 等待任务或停止信号 res_.cv.wait(lock, [this](){ return !res_.task_queue.empty() || res_.stop; }); if (!res_.task_queue.empty()) { int item = res_.task_queue.front(); res_.task_queue.pop(); // 处理任务... } } }; int main() { SharedResources res; producer prod(res); consumer cons(res); // 启动线程执行生产消费逻辑... return 0; }
总结一下,核心思路就是避免拷贝同步原语,转而传递引用或指针,这样多个线程/对象可以共享同一个mutex和condition_variable来实现同步。
内容的提问来源于stack exchange,提问作者donguy76

