# TaskQueue **Repository Path**: thesecret/task-queue ## Basic Information - **Project Name**: TaskQueue - **Description**: C++实现的任务队列 - **Primary Language**: C++ - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 1 - **Created**: 2022-03-19 - **Last Updated**: 2023-09-14 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 任务队列 ## 要求 描述:将函数和参数打包,在异步线程顺序执行 \ 知识点:完美转发,函数指针,多线程,队列(std::thread,std::mutex,std::condition_variable) ## 内容笔记 ### 任务队列 存储需要处理的任务,由工作的线程来处理这些任务。 ### C++左值和右值 - 判断某个表达式是左值还是右值,最常用的有以下 2 种方法: 1. 可位于赋值号(=)左侧的表达式就是左值;反之,只能位于赋值号右侧的表达式就是右值。(值得一提的是,C++ 中的左值也可以当做右值使用) 2. 有名称的、可以获取到存储地址的表达式即为左值;反之则是右值。 - C++ 右值引用(用 “&&” 表示) 1. 和声明左值引用一样,右值引用也必须立即进行初始化操作,且只能使用右值进行初始化 ```cpp int num = 10; //int && a = num; //右值引用不能初始化为左值 int&& a = 10; ``` 2. 和常量左值引用不同的是,右值引用还可以对右值进行修改 ```cpp int num = 10; int&& a = 10; a = 100; cout << a << endl; ``` ### unique_lock unique_lock是个类模板,工作中,一般使用lock_guard(推荐使用) unique_lock比lock_guard灵活很多,效率上差一点,内存占用多一点。 ### std::async、std::future std::async是个函数模板,用来启动一个异步任务,启动起来一个异步任务之后(什么叫“启动一个异步任务”,就是自动创建一个线程并开始执行对应的线程入口函数),他返回一个std::future对象,这个std::future对象里面就含有线程函数返回的结果,我们可以通过调用std::future对象的成员函数get()来获取结果;它返回一个std::future对象。 ### 条件变量std::condition_variable、wait()、notify_one() std:: condition_variable实际上是个类,是一个与条件相关的类,说白了就是等待一个条件的达成。这个类是需要和互斥量来配合工作的,用的时候我们要生成这个类的对象。 1. wait() 1. 如果第二个参数是true,那么wait()直接返回; 2. 如果第二个参数lambda表达式返回值是false,那么wait()将解锁互斥量,并堵塞到本行;那堵塞到什么时候为止呢?堵塞到其他某个线程调用notify_one()成员函数为止; 3. 如果wait()没有第二个参数,my_cond.wait(sbguard),那么就跟第二个参数lambda表达式返回false效果一样。 2. notify_one()+wait()的工作流程 1. 其他线程用notify_one()将本wait(原本是睡着/堵塞)的状态唤醒后,wait就开始恢复干活了,恢复后的wait干什么活?\ a)wait不断地尝试重新获取互斥量锁,如果获取不到,那么流程就卡在wait这里等着获取,如果获取到,那么wait就继续执行b \ b)上锁(实际上获取到了锁,就等于上了锁) \ 1)如果wait有第二个参数(lambda),就判断这个lamda表达式,如果lambda表达式为false那么wait又对互斥量解锁,然后又休眠,再等待被notify_one()唤醒 \ 2)如果lambda为true,则wait返回,流程走下来 \ 3)如果wait()没有第二个参数,则wait()返回,流程走下来 2. 为防止虚假唤醒:wait()中要有第二个参数(lambda)并且这个lambda中要正确处理公共数据是否存在 ### 完美转发 1. 定义 函数模板可以将自己的参数“完美”地转发给内部调用的其它函数。所谓完美,即不仅能准确地转发参数的值,还能保证被转发参数的左、右值属性不变。 2. C++11实现 ```cpp #include using namespace std; //重载被调用函数,查看完美转发的效果 void otherdef(int& t) { cout << "lvalue\n"; } void otherdef(const int& t) { cout << "rvalue\n"; } //实现完美转发的函数模板 template void function(T&& t) { otherdef(forward(t)); } int main() { function(5); // rvalue int x = 1; function(x); // lvalue return 0; } ``` 1. 怎么解决函数模板参数的左、右值接收问题? - C++11 标准中规定,通常情况下右值引用形式的参数只能接收右值,不能接收左值。 - 对于函数模板中使用右值引用语法定义的参数来说,它不再遵守这一规定,既可以接收右值,也可以接收左值(此时的右值引用又被称为“万能引用”)。 ```cpp template void function(T&& t) //既可以接受左值,又可以接受右值 { otherdef(t); // t继续传参,在otherdef()中又变成了左值 } ``` - 在实现完美转发时,只要函数模板的参数类型为 T&&,则 C++ 可以自行准确地判定出实际传入的实参是左值还是右值。 2. 如何将函数模板接收到的形参连同其左、右值属性,一起传递给被调用的函数? - C++11引入了一个模板函数 forword() 。 ```cpp //实现完美转发的函数模板 template void function(T&& t) { otherdef(forward(t)); //将函数模板接收到的形参连同其左、右值属性,一起传递给被调用的函数 } ``` ## 思路 ### 实现功能 1. 判断队列是否为空 2. 获取队列深度 3. 向任务队列添加任务 4. 从任务队列获取任务 ### 运行结果 ![Image text](https://gitee.com/thesecret/task-queue/raw/master/res1.png)