1、基于 C++11 多线程的单例任务队列实现
在这篇文章中,我们将使用 C++11 标准库中的多线程特性实现一个简单的任务队列。任务队列采用**懒汉单例模式**实现,以确保只有一个实例在程序中被创建。我们会使用 `mutex` 和 `lock_guard` 实现线程安全的任务存取操作,并探讨为什么要对共享资源进行加锁。
1. 项目需求
我们将实现一个多线程任务队列,该任务队列包含生产者和消费者线程。生产者线程负责将任务添加到队列中,而消费者线程从队列中取出任务并处理。使用单例模式是为了确保任务队列在整个程序中是唯一的对象,从而便于管理。
2. 代码实现
#include <iostream>
#include <queue>
#include <thread>
#include <mutex>
using namespace std;
class TaskQueue
{
public:
TaskQueue(const TaskQueue&) = delete;
TaskQueue& operator=(const TaskQueue&) = delete;
static TaskQueue* getInstance()//单列: 懒汉模式
{
static TaskQueue tq;
return &tq;
}
bool isEmpty() // 判断队列是否为空
{
lock_guard<mutex> lock(m_mutex);//自动加锁解锁
return m_queue.empty();
}
void pushTask(int val)//添加任务
{
lock_guard<mutex> lock(m_mutex);
m_queue.push(val);
}
bool popTask()//取出任务
{
lock_guard<mutex> lock(m_mutex);
if (m_queue.empty())
{
return false;
}
m_queue.pop();
return true;
}
int getTask()//获取任务
{
lock_guard<mutex> locker(m_mutex);
return m_queue.front();
}
private:
TaskQueue() {} // 将构造函数设为私有
queue<int> m_queue;//任务队列
mutex m_mutex;//互斥锁
};
int main()
{
TaskQueue* tq = TaskQueue::getInstance();//获取单列对象
// 生产者线程
thread t1([=]() {
for (int i = 0; i < 10; i++)
{
tq->pushTask(i);
cout << "push task: " << i << " "
<< this_thread::get_id() << endl;
this_thread::sleep_for(chrono::milliseconds(500));//延时500ms
}
});
// 消费者线程
thread t2([=]() {
this_thread::sleep_for(chrono::milliseconds(100));//延时100ms
while (!tq->isEmpty())
{
int num = tq->getTask();
cout << "pop task: " << num << " "
<< this_thread::get_id() << endl;
tq->popTask();
this_thread::sleep_for(chrono::milliseconds(1000));
}
});
t1.join();//等待线程结束
t2.join();//等待线程结束
return 0;
}
3. 代码解析
3.1 单例模式
`TaskQueue` 类采用**懒汉单例模式**,即只有在首次调用 `getInstance()` 时才会创建对象。通过将构造函数设为私有,并且禁止拷贝构造和赋值操作,我们确保了 `TaskQueue` 对象的唯一性。
3.2 互斥锁的使用
我们使用 `mutex` 和 `lock_guard` 来保证线程安全。具体来说:
- **`mutex`**:互斥锁,用于确保每次只有一个线程能够访问 `m_queue`。
- **`lock_guard`**:RAII风格的锁管理类,在对象构造时加锁,在析构时自动解锁,避免手动解锁时可能出现的错误。
3.3 加锁的目的
在多线程中,如果没有对共享资源加锁,会导致多个线程同时访问 `m_queue`。可能出现的并发问题包括:
- **数据竞争**:两个线程可能同时尝试修改任务队列,导致数据不一致。
- **资源争用**:没有加锁时,队列可能被多个线程同时访问,导致意想不到的行为。
4. 运行效果
在运行此代码后,生产者线程每隔 500 毫秒向任务队列中添加一个任务,消费者线程每隔 1000 毫秒取出一个任务并打印。加锁确保了每次只有一个线程能够访问任务队列,避免了竞争条件。
总结
本示例展示了如何在多线程环境下实现单例任务队列。并通过使用 `mutex` 和 `lock_guard`,我们能够有效地保护共享资源。