C++线程池学习 Day02

❓3个问题:

1️⃣构造函数做了什么?

2️⃣有哪些关键语法点?

3️⃣这么设计的优势?

构造函数

class ThreadPool {
public:
    ThreadPool(size_t);
    template<class F, class... Args>
    auto enqueue(F&& f, Args&&... args)
        -> std::future<typename std::result_of<F(Args...)>::type>;
    ~ThreadPool();
private:
    // need to keep track of threads so we can join them
    std::vector< std::thread > workers;
    // the task queue
    std::queue< std::function<void()> > tasks;
​
    // synchronization
    std::mutex queue_mutex;
    std::condition_variable condition;
    bool stop;
};
// the constructor just launches some amount of workers
inline ThreadPool::ThreadPool(size_t threads)
    : stop(false)
{
    for(size_t i = 0; i < threads; ++i)
        workers.emplace_back(
            [this]
            {
                for(;;)
                {
                    std::function<void()> task;
                    {
                        std::unique_lock<std::mutex> lock(this->queue_mutex);
                        this->condition.wait(lock,
                            [this]{ return this->stop || !this->tasks.empty(); });
                        if(this->stop && this->tasks.empty())
                            return;
                        task = std::move(this->tasks.front());
                        this->tasks.pop();
                    }
                    task();
                }
            });
}

宏观上不难理解

1.构造函数创建了threads个线程,把它们加入到workers中

:stop(false);//设置工厂不停止运行

2.给工人分配任务

for(size_t i=0;i<threads;++i)
    workers.emplace_back(/*每个工人的工作手册*/);

3.告诉工人开工和下班的信号

4.领取任务、执行任务

比喻理解:

构造函数创建并启动了一条工厂生产线

threads:雇佣的工人数量

workers:工人团队(线程集合)

tasks:待处理的任务队列

condition:开工/下班的信号,类似于工长的哨子(通知机制)

接着分析语法。

1️⃣inline

inline的功能之一,就是将整个函数体代码嵌入到调用点然后展开。

inline int add(int a,int b) {
    return a+b;
}
int main() {
    int x=5;
    int y=3;
    int ans=add(x,y);//调用点
    return 0;
}

编译器可能会将代码转换成这样:

inline int add(int a,int b) {
    return a+b;
}
int main() {
    int x=5;
    int y=3;
    int ans=x+y;//add的函数体被嵌入到这个位置(调用点)。直接替换为函数体代码。
    return 0;
}

不过,inline只是建议,编译器可以忽略,而且编译器未必真的做文本替换。

但是,线程池构造函数的实现里的inline不是为了代码展开优化。其目的是:防止多个cpp文件包含到同一头文件时产生多重定义错误(因为整个实现都在头文件中)。inline的作用就是告诉链接器:这个函数定义可以在多个编译单元中出现,链接器请处理好重复定义的问题。

什么叫编译单元?编译单元=一个.cpp文件+它包含的所有头文件。

更准确地说,一个源文件(.cpp文件)经过预处理(所有#include和宏)后得到的完整代码,就是编译单元。

假设一个目录下有三个文件:main.cpp\utils.cpp\ThreadPool.h.

编译单元1:main.cpp+它包含的所有头文件(展开iostream/ThreadPool.h...的内容)

编译单元2:utils.cpp+它包含的所有头文件(再次展开iostream/ThreadPool.h...的内容)

编译过程:1.每个.cpp文件单独编译成.o目标文件;2.所有.o文件合并成最终程序

链接器发现main.o和utils.o中都包含ThreadPool的定义(实现),报错。加了inline,链接器知道这是允许重复的定义,选择一个使用,不报错。

比喻理解:每个单元是一个独立的工厂车间。每个车间都需要完整的工作手册(头文件)。如果没有inline,每个车间都会因为自己拿到了完整的工作手册而声称自己是总指挥部。有了inline,大家知道这些只不过是相同的副本,可以协调工作。

这么一来,inline就可以支持头文件only了。

头文件only,即整个库的实现都在头文件中,没有对应的.cpp文件。

头文件only的优点:

1.使用简单:只需要#include"ThreadPool.h",无需编译链接.cpp文件(对开发者来说工作变轻了,不用处理复杂的编译链接)

2.跨平台:避免二进制兼容问题

3.模板友好:模板必须在头文件中实现

缺点:

1.编译时间长:每次包含都会重新编译整个实现(对编译器来说工作变重了,每次都要编译相同代码)

2.代码暴露:实现细节完全暴露给用户

为什么说模板必须在头文件中实现?

错误示范:

// ThreadPool.h (声明)
template<typename T>
class ThreadPool {
public:
    void enqueue(T task);  // 模板声明
};
​
// ThreadPool.cpp (定义)
template<typename T>
void ThreadPool<T>::enqueue(T task) {  // 模板定义
  
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值