diff --git a/.vscode/settings.json b/.vscode/settings.json index c88bb975..87db21fb 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -13,6 +13,66 @@ "chrono": "cpp", "iostream": "cpp", "ostream": "cpp", - "iomanip": "cpp" + "iomanip": "cpp", + "algorithm": "cpp", + "atomic": "cpp", + "cctype": "cpp", + "cmath": "cpp", + "compare": "cpp", + "concepts": "cpp", + "condition_variable": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "exception": "cpp", + "resumable": "cpp", + "fstream": "cpp", + "functional": "cpp", + "future": "cpp", + "ios": "cpp", + "iosfwd": "cpp", + "istream": "cpp", + "iterator": "cpp", + "limits": "cpp", + "locale": "cpp", + "map": "cpp", + "memory": "cpp", + "mutex": "cpp", + "new": "cpp", + "numeric": "cpp", + "random": "cpp", + "ratio": "cpp", + "set": "cpp", + "sstream": "cpp", + "stack": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "string": "cpp", + "system_error": "cpp", + "thread": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "typeinfo": "cpp", + "unordered_map": "cpp", + "unordered_set": "cpp", + "utility": "cpp", + "xfacet": "cpp", + "xhash": "cpp", + "xiosbase": "cpp", + "xlocale": "cpp", + "xlocbuf": "cpp", + "xlocinfo": "cpp", + "xlocmes": "cpp", + "xlocmon": "cpp", + "xlocnum": "cpp", + "xloctime": "cpp", + "xmemory": "cpp", + "xstddef": "cpp", + "xtr1common": "cpp", + "xtree": "cpp" } } \ No newline at end of file diff --git a/C++/代码片段.md/1.cpp b/C++/代码片段.md/1.cpp new file mode 100644 index 00000000..e69de29b diff --git a/C++/标准库/1 通用工具.md b/C++/标准库/1 通用工具.md index af9bf6f6..12c34352 100644 --- a/C++/标准库/1 通用工具.md +++ b/C++/标准库/1 通用工具.md @@ -97,9 +97,10 @@ int main(){ ``` ### 术语 +* epoch表示世界的起始点 * duration时间段,某个时间单位上,tick数。 * timepoint时间点,duration和epoch(起始点)的组合。 -* clock时钟,每个时钟都有自己的epoch(起始点——) +* clock时钟,每个时钟都有自己的epoch、duration和timepoint(起始点——) ## 7.2 Duration时间段 @@ -138,7 +139,7 @@ hours aDay(24);//表示一天的duration ## 7.3 Clock和Timepoint ### 定义 -* Clock定义了一个epoch和一个tick周期。用来表示时间的起点和时间的计数方式。 +* Clock定义了一个epoch和一个tick周期。用来表示时间的起点和时间的计数方式。一个利用duration、epoch、timepoint计时的工具。 * Timepoint某个特定的时间点。 diff --git a/C++/标准库/2021-03-08-18-31-27.png b/C++/标准库/2021-03-08-18-31-27.png new file mode 100644 index 00000000..7457ff80 Binary files /dev/null and b/C++/标准库/2021-03-08-18-31-27.png differ diff --git a/C++/标准库/2021-03-08-19-36-00.png b/C++/标准库/2021-03-08-19-36-00.png new file mode 100644 index 00000000..b20acd0f Binary files /dev/null and b/C++/标准库/2021-03-08-19-36-00.png differ diff --git a/C++/标准库/9 线程.md b/C++/标准库/9 线程.md index e69de29b..f57f3b0f 100644 --- a/C++/标准库/9 线程.md +++ b/C++/标准库/9 线程.md @@ -0,0 +1,634 @@ +# 并发编程与多线程 + + +## 1. 基础知识 + +### C++多线程 + +* **线程**:线程是操作系统能够进行CPU调度的最小单位,它被包含在进程之中,一个进程可包含单个或者多个线程。可以用多个线程去完成一个任务,也可以用多个进程去完成一个任务,它们的本质都相当于多个人去合伙完成一件事。 +* **多线程并发**多线程是实现并发(双核的真正并行或者单核机器的任务切换都叫并发)的一种手段,多线程并发即多个线程同时执行,一般而言,多线程并发就是把一个任务拆分为多个子任务,然后交由不同线程处理不同子任务,使得这多个子任务同时执行。 +* **C++多线程并发**C++98标准中并没有线程库的存在,而在C++11中才提供了多线程的标准库,提供了管理线程、保护共享数据、线程间同步操作、原子操作等类,。(简单情况下)实现C++多线程并发程序的思路如下:将任务的不同功能交由多个函数分别实现,创建多个线程,每个线程执行一个函数,一个任务就这样同时分由不同线程执行了。 + +### 相关的头文件说明 + + +* thread头文件:存储thread线程与this_thread 命名空间的东西。基础实现 +* future头文件:存储future、promise、async相关的类。高级实现 +* mutex头文件:存储异步通信的相关的类 + + +## 2. 高级接口:Async与Future +### 头文件 +``` +#include +``` + +### future说明 +标准库提供了一些工具来获取异步任务(即在单独的线程中启动的函数)的返回值,并捕捉其所抛出的异常。这些值在共享状态中传递,其中异步任务可以写入其返回值或存储异常,而且可以由持有该引用该共享态的 std::future 或 std::shared_future 实例的线程检验、等待或是操作这个状态。 + + +定义于头文件 `` +* promise存储一个值以进行异步获取(类模板) +* packaged_task打包一个函数,存储其返回值以进行异步获取(类模板) +* future等待被异步设置的值(类模板) +* shared_future等待被异步设置的值(可能为其他 future 所引用)(类模板) +* async异步运行一个函数(有可能在新线程中执行),并返回保有其结果的 std::future(函数模板) +* launch指定 std::async 所用的运行策略(枚举) +* future_status指定在 std::future 和 std::shared_future上的定时等待的结果(枚举) + +Future 错误 +* future_error报告与 future 或 promise 有关的错误(类) +* future_category鉴别 future 错误类别(函数) +* future_errc鉴别 future 错误码(枚举) + +### 编程实例 +``` +#include +#include +#include +#include +#include +#include + +using namespace std; + +int do_something(char c){ + //初始化了一个随机数引擎和一个随机数分布 + default_random_engine dre(c); + uniform_int_distribution id(10,1000); + + for(int i =0;i<10;++i){ + //随机停止一段时间。 + this_thread::sleep_for(chrono::milliseconds(id(dre))); + cout.put(c).flush(); + } + return c; +} + +int func1(){ + return do_something('.'); +} + +int func2(){ + return do_something('+'); +} + +int main(){ + //启动异步线程,执行函数1。使用future作为占位符 + //async的返回值与func1自动匹配,是模板函数。 + //future object的类型也可以与async自动匹配,设置成auto result1() + //async接受任何可调用对象。包括函数、函数指针、lambda函数 + future result1(async(func1)); + + //主线程中执行函数2 + int result2 = func2(); + + int result=0; + try + { + result = result1.get()+result2; + + } + catch(const std::exception& e) + { + std::cerr << e.what() << '\n'; + } + + //计算结果,阻塞主线程 + + //输出结果 + cout< +``` + +### Thread与future的区别 + +* future在一定程度上提供了线程通信和线程同步的方法。例如get可以获得另一个线程的返回值。wait()可以等待线程,实现线程同步。但是thread没有提供任何线程通信的方法。需要自己实现线程通信。(在操作系统部分,应该理解线程通信的原理和所有的方法) +* 异常无法在线程之间传递。 +* 必须声明是同步线程join()还是一部线程detach()。future和async实现的线程是异步线程。可以使用get(),wait()进行同步。 +* 如果线程运行与后台,main函数没有通过join等待线程结束,后台线程会被强制终止。 + + +### thread说明 + +* 观察器 + +|函数|作用| +|---|---| +joinable | 检查线程是否可合并,即潜在地运行于平行环境中(公开成员函数) +get_id | 返回线程的 id(公开成员函数) +native_handle | 返回底层实现定义的线程句柄(公开成员函数) +hardware_concurrency | [静态]返回实现支持的并发线程数(公开静态成员函数) + +* 操作 + + +|函数|作用| +|---|---| +join | 等待线程完成其执行 +detach | 容许线程从线程句柄独立开来执行 + + +### thread编程实现 + +``` +#include +#include +#include +#include +#include + + +using namespace std; + +void doSomething(int num,char c){ + try + { + default_random_engine dre(42*c); + uniform_int_distribution id(10,1000); + for(int i=0;i +#include +#include +#include +#include +#include +#include + +using namespace std; +void doSomething(promise& p){ + try{ + cout<<"read char x for exception"<p; + thread t(doSomething,std::ref(p)); + t.detach(); + + future f(p.get_future()); + + cout<<"result:"< task(doSomething); + +//获得线程池的future +future f = task.get_future(); + +//使用线程池启动一个县城 +task(7,5); + +//使用future获得线程执行的结果。 +double res = f.get(); +``` +## 4 线程同步与数据访问 + +### 存在问题 + +多个线程共享资源出现访问冲突 + +* 读读互补冲突 +* 读写冲突 +* 写写冲突 + +### 解决问题的方法 + +保证操作的原子性和次序。atomicity不可分割。order按次序执行。 + +* future和promise能够保证原子性和次序。一定是在形成返回值和异常后,future才会读取数据,否则进行堵塞。 +* mutex和lock +* condition variable +* atomic data type底层接口 + +## 4.1 mutex和lock + +### mutex简单说明mutex + +|函数|作用| +|---|---| +lock|锁定互斥,若互斥不可用则阻塞 +try_lock|尝试锁定互斥,若互斥不可用则返回 +unlock|解锁互斥 + +``` +int val ; +mutex valMutex; +valMutex.lock(); + +//val的访问和修改 + +valMutex.unlock(); +``` +* 每次访问前上锁。访问后开锁。 +* 如果其他程序已经上锁,那么当前程序阻塞,直到其他程序释放锁。(发送开锁信号激活) +* 存在的问题:中途出现异常,无法执行开锁。资源会被永久上锁。 +* mutex尝试锁try_lock()用来判断资源是否上锁。如果成功就返回true,此时调用可以上锁 + +``` +mutex m; + +while(m.try_lock()==false){ + doSomethingOthers(); +} + +lock_guard lg(m,adopt_lock); +``` + +### mutex递归锁recursive_mutex +* recursive_mutex与mutex操作完全一致。 +* 死锁:两个程序分别锁上了对方需要的资源,并在相互等待。 +* 递归锁:一个线程两次上锁,导致第二次上锁的时候资源被自己占用。也是一种死锁。 +* recursive_mutex 能够防止递归锁出现。即防止同一个线程多次上锁同一个资源 + +### mutex时间锁timed_mutex/recursive_time_mutex + +|函数|作用| +|---|---| +lock|锁定互斥,若互斥不可用则阻塞 +try_lock|尝试锁定互斥,若互斥不可用则返回 +try_lock_for|尝试锁定互斥,若互斥在指定的时限时期中不可用则返回 +try_lock_until|尝试锁定互斥,若直至抵达指定时间点互斥不可用则返回 +(公开成员函数) +unlock|解锁互斥 + + +* 等待某个时间段。返回是否上锁。有如下成员函数 +``` +try_lock_for() +try_lock_until() +``` + +### mutex进阶版本lock_guard +``` +std::lock_guard sbguard1(my_mutex1, std::adopt_lock);// std::adopt_lock标记作用; +``` + +* 使用lock_guard管理锁。这样当出现异常后,lock_guard局部变量被销毁,执行析构函数的时候回自动释放资源锁。 +* lock_guard的第二个标质量adopt_lock标记的效果就是假设调用一方已经拥有了互斥量的所有权(已经lock成功了);通知lock_guard不需要再构造函数中lock这个互斥量了。 +``` +//mute & lock +#include +#include +#include +#include + +using namespace std; + +//互斥体的控制变量 +mutex printMutex; + +void print(const std::string&s){ + // 如果没有枷锁,多个线程共同调用会乱序输出 + lock_guard l(printMutex); + for(char c:s){ + cout.put(c); + } + cout< guard(_mu); + //do something 1 + guard.unlock(); //临时解锁 + + //do something 2 + + guard.lock(); //继续上锁 + // do something 3 + f << msg << id << endl; + cout << msg << id << endl; + // 结束时析构guard会临时解锁 + // 这句话可要可不要,不写,析构的时候也会自动执行 + // guard.ulock(); + } +``` + +## 4.2 condition variable + +### 简介 +* future的目的是处理线程的返回值和异常。因为它只能携带一次数据返回。 +* 这个明显是解决生产者和消费者问题。或者读、写问题。因为资源有数量限制。而之前的mutex只有互斥限制,也就是说,mutex与lock只能控制数量为1的消费者互斥访问问题。 +* condition variable控制数量大于1 的生产和消费问题 + +### condition_variable原理 +* condition_variable 类是同步原语,能用于阻塞一个线程,或同时阻塞多个线程,直至另一线程修改共享变量(条件)并通知 condition_variable 。 + +* 有意修改变量的线程必须 + 1. 获得 std::mutex (常通过 std::lock_guard ) + 2. 在保有锁时进行修改 + 3. 在 std::condition_variable 上执行 notify_one 或 notify_all (不需要为通知保有锁) + +* 即使共享变量是原子的,也必须在互斥下修改它,以正确地发布修改到等待的线程。 + +* 任何有意在 std::condition_variable 上等待的线程必须 + 1. 在与用于保护共享变量者相同的互斥上获得 std::unique_lock + 2. 执行下列之一: + 1. 检查条件,是否为已更新或提醒它的情况 + 2. 执行 wait 、 wait_for 或 wait_until ,等待操作**自动释放互斥**,并悬挂线程的执行。 + 3. condition_variable 被通知时,时限消失或虚假唤醒发生,**线程被唤醒,且自动重获得互斥**。之后线程应检查条件,若唤醒是虚假的,则继续等待。 + 4. 或者,使用 wait 、 wait_for 及 wait_until 的有谓词重载,它们包揽以上三个步骤 + +> condition_variable的消费者。有一下三种情况。 +> * 当资源没有被生产出来,没有加锁时,加锁,wait(),解锁,等待通知。 +> * 当资源被锁时,在unique_lock处等待解锁。 +> * 当资源生产出来,没有加锁时,直接执行。 + +> 对于第一种情况:condition_varaiblewait操作能够解锁等待信号量。当信号量来到时,加锁执行操作,然后解锁,退出。当信号量来到时,加锁,但是第二个参数的内容发现是虚假信号,能够继续解锁等待信号量。 + +![](2021-03-08-19-36-00.png) + +### condition_variable操作 + + +|函数|作用| +|---|---| +notify_one|通知一个等待的线程 +notify_all|通知所有等待的线程 +wait|阻塞当前线程,直到条件变量被唤醒 +wait_for|阻塞当前线程,直到条件变量被唤醒,或到指定时限时长后 +wait_until|阻塞当前线程,直到条件变量被唤醒,或直到抵达指定时间点 + +### 使用条件 + +* 一个“存放数据”的对象,或一个“表示条件满足”的flag。此处的readyFlag +* 一个mutex对象,此处的readyMutex +* 一个condition_variable对象词的readyCondVar + +### 编程实现——简单使用 + +``` +//condition variable生产者消费者问题 +#include +#include +#include +#include + +using namespace std; + + +bool readyFlag; +mutex readyMutex; +condition_variable readyCondVar; + +void thread1(){ + cout<<"thread1"< lg(readyMutex); + readyFlag = true; + } + readyCondVar.notify_one(); +} + +void thread2(){ + { + unique_lock ul(readyMutex); + readyCondVar.wait(ul,[]{return readyFlag;}); + } + + cout<<"done"< +#include +#include +#include +#include +#include + +using namespace std; + +queue que;//消费对象 +mutex queueMutex; +condition_variable queueCondVar; + +//生产者 +void provider(int val){ + for(int i=0;i<6;++i){ + lock_guard lg(queueMutex); + que.push(val+i); + //貌似这句话会被优化掉 + this_thread::sleep_for(chrono::microseconds(100000)); + } + queueCondVar.notify_one(); + + this_thread::sleep_for(chrono::microseconds(val)); +} + + +//消费者 +void consumer(int num){ + while(true){ + int val; + { + unique_lock ul(queueMutex); + queueCondVar.wait(ul,[]{return !que.empty();}); + val = que.front(); + que.pop(); + cout<<"consumer"< 等到以后再写吧。感觉没有必要。 + + +## 5 this_thread + +|函数|作用| +|----|----| +get_id | 获得thread id (function ) +yield | 放弃执行 (function ) +sleep_until | 休眠到某个时间节点chrono::timepoint (function ) +sleep_for | 休眠某个时间段chrono::duration (function ) diff --git a/C++/标准库/9.1.cpp b/C++/标准库/9.1.cpp new file mode 100644 index 00000000..1a67942a --- /dev/null +++ b/C++/标准库/9.1.cpp @@ -0,0 +1,60 @@ +//async & future +#include +#include +#include +#include +#include +#include + +using namespace std; + +int do_something(char c){ + //初始化了一个随机数引擎和一个随机数分布 + default_random_engine dre(c); + uniform_int_distribution id(10,1000); + + for(int i =0;i<10;++i){ + //随机停止一段时间。 + this_thread::sleep_for(chrono::milliseconds(id(dre))); + cout.put(c).flush(); + } + return c; +} + +int func1(){ + return do_something('.'); +} + +int func2(){ + return do_something('+'); +} + +int main(){ + //启动异步线程,执行函数1。使用future作为占位符 + //async的返回值与func1自动匹配,是模板函数。 + //future object的类型也可以与async自动匹配,设置成auto result1() + //async接受任何可调用对象。包括函数、函数指针、lambda函数 + future result1(async(func1)); + + //主线程中执行函数2 + int result2 = func2(); + + int result=0; + try + { + result = result1.get()+result2; + + } + catch(const std::exception& e) + { + std::cerr << e.what() << '\n'; + } + + //计算结果,阻塞主线程 + + //输出结果 + cout< +#include +#include +#include +#include + + +using namespace std; + +void doSomething(int num,char c){ + try + { + default_random_engine dre(42*c); + uniform_int_distribution id(10,1000); + for(int i=0;i +#include +#include +#include +#include +#include +#include + +using namespace std; +void doSomething(promise& p){ + try{ + cout<<"read char x for exception"<p; + thread t(doSomething,std::ref(p)); + t.detach(); + + future f(p.get_future()); + + cout<<"result:"< +#include +#include +#include + +using namespace std; + +//互斥体的控制变量 +mutex printMutex; + +void print(const std::string&s){ + // 如果没有枷锁,多个线程共同调用会乱序输出 + lock_guard l(printMutex); + for(char c:s){ + cout.put(c); + } + cout< +#include +#include +#include + +using namespace std; + + +bool readyFlag; +mutex readyMutex; +condition_variable readyCondVar; + +void thread1(){ + cout<<"thread1"< lg(readyMutex); + readyFlag = true; + } + readyCondVar.notify_one(); +} + +void thread2(){ + { + unique_lock ul(readyMutex); + readyCondVar.wait(ul,[]{return readyFlag;}); + } + + cout<<"done"< +#include +#include +#include +#include +#include + +using namespace std; + +queue que;//消费对象 +mutex queueMutex; +condition_variable queueCondVar; + +//生产者 +void provider(int val){ + for(int i=0;i<6;++i){ + lock_guard lg(queueMutex); + que.push(val+i); + //貌似这句话会被优化掉 + this_thread::sleep_for(chrono::microseconds(100000)); + } + queueCondVar.notify_one(); + + this_thread::sleep_for(chrono::microseconds(val)); +} + + +//消费者 +void consumer(int num){ + while(true){ + int val; + { + unique_lock ul(queueMutex); + queueCondVar.wait(ul,[]{return !que.empty();}); + val = que.front(); + que.pop(); + cout<<"consumer"<