c++实现简单线程池
主要结合操作系统的基本原理和c++11特性来写
首先了解一下lambda表达式,利用Lambda表达式,可以方便的定义和创建匿名函数
捕获外部变量有3种方法
1.值捕获
int main() { int a = 123; auto f = [a] { cout << a << endl; }; a = 321; f(); // 输出:123 }
2.引用捕获
int main() { int a = 123; auto f = [&a] { cout << a << endl; }; a = 321; f(); // 输出:321 }
3.隐式捕获
int main() { int a = 123; auto f = [=] { cout << a << endl; }; // 值捕获 f(); // 输出:123 } int main() { int a = 123; auto f = [&] { cout << a << endl; }; // 引用捕获 a = 321; f(); // 输出:321 }
类型尾置
让编译器在函数定义的时候知道返回类型
template <typename T> auto &getItem(T begin, T end) -> decltype(*begin) { return *begin; // 返回序列中一个元素的引用 }
下面就介绍一下c++11的特性
#include <iostream>
#include <thread>
void foo() {
std::cout << "hello world" << std::endl;
}
int main() {
std::thread t(foo);//创建一个线程实例
t.join();//加入一个线程
return 0;
}
/*
std::mutex mutex 创建一个互斥量
std::lock_guard<std::mutex> lock(mutex); 对互斥量上锁
std::unique_lock 也是上锁,但更灵活
std::packaged_task<int()> task([](){return 7;}); 用来封装任何可以调用的目标,从而用于实现异步的调用,异步即主线程A想获取某个计算结果而调用线程B
std::future<int> result = task.get_future(); 用来获取异步任务的结果
std::thread(std::move(task)).detach(); 一个线程中执行 task
std::this_thread::sleep_for 当前线程休眠一段时间,休眠期间不与其他线程竞争CPU,根据线程需求,等待若干时间
std::condition_variable 唤醒等待线程从而避免死锁
std::bind 将实参绑定到调用函数上
std::placeholders::_1 占用符
std::shared_ptr 一种智能指针,它能够记录多少个 shared_ptr 共同指向一个对象
std::make_shared 分配创建传入参数中的对象,并返回这个对象类型的std::shared_ptr
指针
std::move 将自己的参数转换为右值
std::forward 会把参数被绑定到一个右值的时候将其转化为右值
std::result_of 在编译的时候推导出一个函数调用表达式的返回值类型
看一个操作系统中生产者与消费者问题
假设存在一个缓冲区,生产者往里面存数据,消费者从里面取数据,如果缓冲区满了,生产者就不能再往里面添加数据。如果缓冲区没有数据,消费者不能从里面取
下面利用c++11来写一个简单的模型,要理解多线程并发,一个程序可能由多个线程来执行,因此程序上的顺序并不同于多线程中的执行顺序,代码中先写5个生产者,再写5个消费者,但是线程中的顺序并不是这样,可能先执行一个生产者,再执行一个消费者。
#include <condition_variable>
#include <mutex>
#include <thread>
#include <iostream>
#include <queue>
#include <chrono>
int main()
{
// 生产者数量
std::queue<int> produced_nums;
// 互斥锁
std::mutex m;
// 条件变量
std::condition_variable cond_var;
// 结束标志
bool done = false;
// 通知标志
bool notified = false;
// 生产者线程
std::thread producer([&]() {
for (int i = 0; i < 5; ++i) {
std::this_thread::sleep_for(std::chrono::seconds(1));//当前线程休眠
// 创建互斥锁
std::unique_lock<std::mutex> lock(m);
std::cout << "producing " << i << '\n';
produced_nums.push(i);
notified = true;
// 通知一个线程
cond_var.notify_one();
}
done = true;//生产结束
cond_var.notify_one();//通知休眠的线程执行,完成所有的进程
});
// 消费者线程
std::thread consumer([&]() {
std::unique_lock<std::mutex> lock(m);
while (!done) {
while (!notified) { // 循环避免虚假唤醒,执行完生产者的进程,notified为true,否则停掉消费者线程
cond_var.wait(lock);//停掉当前线程
}
while (!produced_nums.empty()) {
std::cout << "consuming " << produced_nums.front() << '\n';
produced_nums.pop();
}
notified = false;
}
});
producer.join();
consumer.join();
}
程序的结果是生产一个,消费一个,这样就引发思考了,为什么不可能是生产几个,后消费.发现在生产线程中,先让当前线程休眠,所以生产一个,消费一个。
线程池类代码
#ifndef ThreadPool_hpp #define ThreadPool_hpp #include <vector> // std::vector #include <queue> // std::queue #include <memory> // std::make_shared #include <stdexcept> // std::runtime_error #include <thread> // std::thread #include <mutex> // std::mutex, std::unique_lock #include <condition_variable> // std::condition_variable #include <future> // std::future, std::packaged_task #include <functional> // std::function, std::bind #include <utility> // std::move, std::forward class ThreadPool { public: inline ThreadPool(size_t threads) : stop(false) { //构造函数,且把stop变量赋值为false for(size_t i = 0;i<threads;++i)//创造线程实例 workers.emplace_back([this] {//使用lambda表达式返回this for(;;) { std::function<void()> task;//function函数对象类,可调用实体的一种类型安全的包裹 { std::unique_lock<std::mutex> lock(this->queue_mutex);//互斥量上锁 //std::cout<<"thread"<<std::this_thread::get_id()<<"begin work"<<std::endl; this->condition.wait(lock,[this]{ return this->stop || !this->tasks.empty(); });//如果线程池没有销毁且任务队列为空,返回false,该线程休眠 if(this->stop && this->tasks.empty())//线程池销毁且任务队列为空,返回 return; task = std::move(this->tasks.front()); this->tasks.pop(); } task();//执行任务 } std::cout<<"thread"<<std::this_thread::get_id()<<"begin work"<<std::endl; }); } inline ~ThreadPool() { { //std::unique_lock<std::mutex> lock(queue_mutex);//互斥量上锁,避免 stop = true; } condition.notify_all();//通知所有的休眠线程 for(std::thread &worker: workers) worker.join(); } template<class F, class... Args> auto enqueue(F&& f, Args&&... args)//可变参数的模板,Args 是一个模板参数包。而在后面的函数参数表中,args 则是函数参数包,用来表示零个或多个参数。 -> std::future<typename std::result_of<F(Args...)>::type> { using return_type = typename std::result_of<F(Args...)>::type;//获取函数返回类型 auto task = std::make_shared< std::packaged_task<return_type()>>( std::bind(std::forward<F>(f), std::forward<Args>(args)...) ); std::future<return_type> res = task->get_future();//获得 std::future 对象以供实施线程同步 { std::unique_lock<std::mutex> lock(queue_mutex); if(stop) throw std::runtime_error("enqueue on stopped ThreadPool"); tasks.emplace([task]{ (*task)(); });//把任务加入队列 } condition.notify_one();//唤醒一个休眠的线程 return res; } private: std::vector<std::thread> workers;//线程池 std::queue<std::function<void()>> tasks;//任务队列 std::mutex queue_mutex;//互斥量 std::condition_variable condition;//条件变量 bool stop;//结束标志 }; #endif /* ThreadPool_hpp */
测试代码
#include <iostream> // std::cout, std::endl #include <vector> // std::vector #include <string> // std::string #include <future> // std::future #include <thread> // std::this_thread::sleep_for #include <chrono> // std::chrono::seconds #include "ThreadPool.h" int main() { // 创建一个能够并发执行四个线程的线程池 //std::cout<<std::this_thread::get_id()<<std::endl; ThreadPool pool(4); // 创建并发执行线程的结果列表 std::vector< std::future<std::string> > results; // 启动八个需要执行的线程任务 for(int i = 0; i < 8; ++i) { // 将并发执行任务的返回值添加到结果列表中 results.emplace_back( // 将下面的打印任务添加到线程池中并发执行 pool.enqueue([i] { std::cout << "hello " << i << std::endl; // 上一行输出后, 该线程会等待1秒钟 std::this_thread::sleep_for(std::chrono::seconds(1)); // 然后再继续输出并返回执行情况 std::cout << "world " << i << std::endl; return std::string("---thread ") + std::to_string(i) + std::string(" finished.---"); }) ); } // 输出线程任务的结果 for(auto && result: results) std::cout << result.get() << '\n'; std::cout << std::endl; return 0; }