c++线程池实现(三)同步队列实现

14 篇文章 0 订阅

前面设计同步队列的代码,下面详细说说实现。

Push
void Push(T&& task) {
    std::unique_lock<std::mutex> lock{mutex_};
    not_full_variable_.wait(lock, [this]{ return queue_.size() < max_size_; });
    queue_.push(std::forward<T>(task));
    not_empty_variable_.notify_one();
}

void Push(const T& task) {
    std::unique_lock<std::mutex> lock{mutex_};
    not_full_variable_.wait(lock, [this]{ return queue_.size() < max_size_; });
    queue_.push(std::move(task));
    not_empty_variable_.notify_one();
}

为了保证线程安全,首先创建了一个unique_lock获取mutex,接着通过队列非满条件变量来等待队列非满的时候。这里使用lambda表达式来返回队列是否已满,如果条件不满足,条件变量会释放mutex,并将线程置于waiting状态,等待其他线程发出通知唤醒自己;如果条件满足,则继续往下执行,往队列中添加任务,接着发出队列非空的通知,唤醒一个正在处于等待状态的线程取走任务。
右值引用
这里有两个Push函数,其实它们实现的功能是一样的,参数为T&&task是为了实现移动语句,它其实是一个不定的类型,可能是左值也可能是右值,如果参数是右值,就可以避免对临时对象的深拷贝,提高性能。
std::forward
一个右值引用参数作为函数的形参时,在这个函数内部如果再传递该参数给其函数,这个参数已经变成了一个左值,不会按照我们预期的类型进行传递。std::forward就是为了实现需要的完美转发,即保持参数的类型。不管参数T&&这种未定的引用还是明确的左值引用或者右值引用,它会按照参数本来的类型转发。
std::move
可以将一个左值强制转换为一个右值引用,这样就可以通过移动构造,避免深拷贝。

Pop
void Pop(T& task) {
    std::unique_lock<std::mutex> lock{mutex_};
    not_empty_variable_.wait(lock, [this]{ return !queue_.empty(); });
    task = queue_.front();
    queue_.pop();
    not_full_variable_.notify_one();
}

void Pop(std::queue<T>& tasks) {
    std::unique_lock<std::mutex> lock{mutex_};
    not_empty_variable_.wait(lock, [this]{ return !queue_.empty(); });
    tasks = std::move(queue_);
    not_full_variable_.notify_one();
}

这里有两个Pop函数,功能分别为只获取一个任务,和获取全部任务。如果只有获取一个任务接口,因为每次获取任务都需要获取锁,想要获取全部任务就需要调用多次,效率比较低。因此设计多设计了一个获取全部任务的接口,通过move的方式,将队列的所有任务都交换出来,避免了数据的复制。
它的流程和Push的过程类似,先获取mutex,然后通过队列非空条件变量来等待队列非空的时候,不满足时释放mutex继续等待,满足则会将任务从队列中取出,并唤醒一个在等待添加任务的线程去添加任务。

Empty Full Count Clear

Empty函数用于判断队列是否为空
Full函数用于判断队列是否已满
Count函数用于获取队列中的等待执行的任务数量
Clear函数用于清除队列中等待的任务

锁和条件变量

std::mutex
独占互斥锁,可以通过lock()方法阻塞线程,直到使用unlock()来解除对互斥量的占用。
std::lock_guard
只使用std::mutex需要先调用lock()方法锁,再调用unlock()来释放锁。因此,有时候就可能会出现忘记调用unlcok()的情况。而调用std::lock_guard可以简化锁的使用,它在构造的时候会自动锁定互斥锁,在作用域结束的时候自动地解锁。
std::condition_variable
条件变量需要和互斥量配合使用,它可以阻塞一个或者多个线程,直到收到其他线程发出的通知或者超时才会唤醒当前阻塞的线程。

std::unique_lock<std::mutex> lock{mutex_};
not_empty_variable_.wait(lock, [this]{ return !queue_.empty(); });

unique_lockstd::lock_guard是类似的,可以保证安全释放mutex,区别在于std::lock_guard需要等到作用域结束才释放mutex,而unique_lock可以自由的释放mutex。因此可以和条件变量配置使用。
not_empty_variable_就是一个std::condition_variable,它会先检查队列非空条件是否满足,如果满足则重新获取mutex,结束wait继续执行;如果不满足则会释放mutex,将线程置为waiting状态,等待唤醒。


现在我们已经实现了同步队列,下面我们将考虑线程池的设计。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值