C++实现简单线程池

一些C++11特性

了解一下lambda表达式,利用Lambda表达式,可以方便的定义和创建匿名函数

值捕获

int main()
{
    int a = 123;
    auto f = [a] { cout << a << endl; }; 
    a = 321;
    f(); // 输出:123
}

引用捕获

int main()
{
    int a = 123;
    auto f = [&a] { cout << a << endl; }; 
    a = 321;
    f(); // 输出:321
}

隐式捕获

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; // 返回序列中一个元素的引用
}

线程相关操作

#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 */

伪代码:

semaphore mutex=1; //临界区互斥信号量
semaphore empty=n;  //空闲缓冲区
semaphore full=0;  //缓冲区初始化为空
producer ()//生产者进程 
{
    while(1)
    {
        produce an item in nextp;  //生产数据
        P(empty);  //获取空缓冲区单元
        P(mutex);  //进入临界区.
        add nextp to buffer;  //将数据放入缓冲区
        V(mutex);  //离开临界区,释放互斥信号量
        V(full);  //满缓冲区数加1
    }
}

consumer ()//消费者进程
{
    while(1)
    {
        P(full);  //获取满缓冲区单元
        P(mutex);  // 进入临界区
        remove an item from buffer;  //从缓冲区中取出数据
        V (mutex);  //离开临界区,释放互斥信号量
        V (empty) ;  //空缓冲区数加1
        consume the item;  //消费数据
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345