原文做者:aircrafthtml
原文连接:https://www.cnblogs.com/DOMLX/p/10945309.html前端
最近在找c++服务端开发的实习(大佬们有推荐吗QAQ。。),刚好写了一些c++11多线程有关的东西,就写一下笔记留着之后本身忘记回来看吧,也不是专门写给读者看的,我就想到哪就写到哪吧python
c++11呢,就是c++升级以后的一个版本,如今立刻就出c++20了,里面增长了不少对多线程支持的类,让多线程编程更加简单了,好了废话很少说,先来创建一个简单的多线程编程案例,看看c++11下多线程编程建立到底有多么的简单。ios
1.建立一个简单的多线程案例:c++
首先导入#include<thread>---用于建立线程
程序员
其次导入#include<chrono>--用于时间延时 获取时间之类的编程
定义一个线程对象t1,这就自动建立了一个线程,参数就是你要线程去执行的函数,t1是变量名字 随便取
windowsstd::thread t1(func);后端
下面这里返回一个毫秒级别的时间间隔参数值,间隔10毫秒 promise
std::chrono::milliseconds(10)
this_thread::sleep_for()就是让此线程休眠,能够传入休眠的时间
this_thread::sleep_for(std::chrono::milliseconds(10));让本线程休眠10毫秒
好了知道这些参数意思就好了,看一下代码:
#include<windows.h> #include <iostream> #include <chrono> #include <thread> using namespace std; int number = 1; int ThreadProc1() { while (number < 100) { cout << "thread 1 :" << number << endl; ++number; this_thread::sleep_for(std::chrono::milliseconds(10)); } return 0; } int ThreadProc2() { while (number < 100) { cout << "thread 2 :" << number << endl; ++number; this_thread::sleep_for(std::chrono::milliseconds(10)); } return 0; } int main() { thread t1(ThreadProc1); thread t2(ThreadProc2); t1.join(); t2.join(); system("pause"); return 0; }
join()就是阻塞线程,直到线程函数执行完毕,若是函数有返回值,在这里会直接忽略。阻塞的目的就是让Main主线程等待一下建立的线程,省得我函数还在跑,程序就直接结束了。
若是不想阻塞在这里就将join()换成使用线程的detach()方法,将线程与线程对象分离,线程就能够继续运行下去,而且不会形成影响。
从示例能够看到c++11下建立多线程多么方便了吧 ,比在Linux下用posix建立还简便,而这个也是能够在windows使用的(想一想windows下多线程的代码,看着都头疼好吧,乱七八糟一大堆)。
2.互斥量的使用
跟往常的多线程同样,多线程在运行过程当中都会对临界区进行访问,也就是一块儿访问共享资源。这样就会形成一个问题,当两个线程都要对一个变量int value值假如为11,加一时,线程一取出11 进行加一尚未存入value,这时候线程二又取得value的11进行加一,而后线程一存入12,线程二又存入12,这就导入两个线程访问冲突,也就是临界区问题。因此引进互斥量来解决。
导入#include <mutex>
代码案例:
一个线程对变量number进行加一100次,另一个减一100次,最后结果应该仍是原来的值0。
#include<windows.h>
#include <iostream> #include <chrono> #include <thread> #include <mutex> using namespace std; int number = 0; mutex g_lock; int ThreadProc1() { for (int i = 0; i < 100; i++) { g_lock.lock(); ++number; cout << "thread 1 :" << number << endl; g_lock.unlock(); this_thread::sleep_for(std::chrono::milliseconds(10)); } return 0; } int ThreadProc2() { for (int i = 0; i < 100; i++) { g_lock.lock(); --number; cout << "thread 2 :" << number << endl; g_lock.unlock(); this_thread::sleep_for(std::chrono::milliseconds(10)); } return 0; } int main() { thread t1(ThreadProc1); thread t2(ThreadProc2); t1.detach(); t2.detach(); system("pause"); return 0; }
上面的每次都要对mutex变量进行锁以及解锁,有时候忘记解锁就凉凉了。因此c++11还提供了一个lock_guard类,它利用了RAII机制能够保证安全释放mutex。
在std::lock_guard对象构造时,传入的mutex对象(即它所管理的mutex对象)会被当前线程锁住。在lock_guard对象被析构时,它所管理的mutex对象会自动解锁,不须要程序员手动调用lock和unlock对mutex进行上锁和解锁操做。lock_guard对象并不负责管理mutex对象的生命周期,lock_guard对象只是简化了mutex对象的上锁和解锁操做,方便线程对互斥量上锁,即在某个lock_guard对象的生命周期内,它所管理的锁对象会一直保持上锁状态;而lock_guard的生命周期结束以后,它所管理的锁对象会被解锁。程序员能够很是方便地使用lock_guard,而不用担忧异常安全问题。
代码:
#include<windows.h> #include <iostream> #include <chrono> #include <thread> #include <mutex> using namespace std; int number = 0; mutex g_lock; int ThreadProc1() { lock_guard<mutex> loker(mutex); for (int i = 0; i < 100; i++) { ++number; cout << "thread 1 :" << number << endl; } //this_thread::sleep_for(std::chrono::milliseconds(100)); return 0; } int ThreadProc2() { lock_guard<mutex> loker(mutex); for (int i = 0; i < 100; i++) { --number; cout << "thread 2 :" << number << endl; //this_thread::sleep_for(std::chrono::milliseconds(10)); } return 0; } int main() { thread t1(ThreadProc1); thread t2(ThreadProc2); t1.detach(); t2.detach(); system("pause"); return 0; }
除了lock_guard,以外c++11还提供了std::unique_lock
类 unique_lock 是通用互斥包装器,容许
延迟锁定、锁定的有时限尝试、递归锁定、全部权转移和与条件变量一同使用
。
unique_lock比lock_guard使用更加灵活,功能更增强大。
使用unique_lock须要付出更多的时间、性能成本。
#include <iostream> // std::cout #include <thread> // std::thread #include <mutex> // std::mutex, std::unique_lock #include <vector> std::mutex mtx; // mutex for critical section std::once_flag flag; //定义一个once_flag类型的变量做为call_once参数, //用std::call_once来保证多线程环境中只被调用一次 void print_block (int n, char c) { //unique_lock有多组构造函数, 这里std::defer_lock不设置锁状态 std::unique_lock<std::mutex> my_lock (mtx, std::defer_lock); //尝试加锁, 若是加锁成功则执行 //(适合定时执行一个job的场景, 一个线程执行就能够, 能够用更新时间戳辅助) if(my_lock.try_lock()){ for (int i=0; i<n; ++i) std::cout << c; std::cout << '\n'; } } void run_one(int &n){ std::call_once(flag, [&n]{n=n+1;}); //只执行一次, 适合延迟加载; 多线程static变量状况 } int main () { std::vector<std::thread> ver; int num = 0; for (auto i = 0; i < 10; ++i){ ver.emplace_back(print_block,50,'*'); ver.emplace_back(run_one, std::ref(num)); //emplace_back比push_back更好 是c++11增长的 } for (auto &t : ver){ t.join(); } std::cout << num << std::endl; return 0; }
这里还要补充一下跟互斥量很像的条件变量的知识。
条件变量std::condition_variable的使用
std::condition_variable 是为了解决死锁而生的。当互斥操做不够用而引入的。好比,线程可能须要等待某个条件为真才能继续执行,而一个忙等待循环中可能会致使全部其余线程都没法进入临界区使得条件为真时,就会发生死锁。因此,condition_variable实例被建立出现主要就是用于唤醒等待线程从而避免死锁。std::condition_variable的 notify_one()用于唤醒一个线程;notify_all() 则是通知全部线程。
C++11中的std::condition_variable就像Linux下使用pthread_cond_wait和pthread_cond_signal同样,可让线程休眠,直到别唤醒,如今在重新执行。线程等待在多线程编程中使用很是频繁,常常须要等待一些异步执行的条件的返回结果。
示例代码:
#include<iostream>
#include<thread> #include<condition_variable> #include<mutex> #include<chrono> std::mutex g_mu; std::condition_variable g_vc; bool g_ready = false; void dispaly_id(int id) { std::unique_lock<std::mutex> lck(g_mu); g_vc.wait(lck, []() {return g_ready; }); //线程阻塞,直到第二个参数返回值为真 std::cout << "id:" << id << std::endl; } void ready() { std::unique_lock<std::mutex> lck(g_mu); g_ready = true; g_vc.notify_all(); //唤醒全部的等待线程 } int main() { std::thread t[8]; for (int i = 0; i < 8; i++) { t[i] = std::thread(dispaly_id, i); } std::this_thread::sleep_for(std::chrono::seconds(3)); std::cout << "all thread lock......" << std::endl; ready(); for (auto & th : t) th.join(); system("pause"); return 0; }
3.原子变量的使用
在新标准C++11,引入了原子操做的概念,原子操做更接近内核,并经过这个新的头文件提供了多种原子操做数据类型,例如,atomic_bool,atomic_int等等,若是咱们在多个线程中对这些类型的共享资源进行操做,编译器将保证这些操做都是原子性的,也就是说,确保任意时刻只有一个线程对这个资源进行访问,编译器将保证,多个线程访问这个共享资源的正确性。从而避免了锁的使用,提升了效率。
上面咱们用互斥锁来实现加一百次,减小一百次。使用原子变量会更加简洁。
#include<windows.h> #include <iostream> #include <chrono> #include <thread> #include <mutex> #include <atomic> using namespace std; atomic<int> number(0);//定义原子变量 一次只容许一个线程对其进行访问 //int number = 0; //mutex g_lock; int ThreadProc1() { //lock_guard<mutex> loker(mutex); for (int i = 0; i < 100; i++) { ++number; cout << "thread 1 :" << number << endl; } //this_thread::sleep_for(std::chrono::milliseconds(100)); return 0; } int ThreadProc2() { //lock_guard<mutex> loker(mutex); for (int i = 0; i < 100; i++) { --number; cout << "thread 2 :" << number << endl; //this_thread::sleep_for(std::chrono::milliseconds(10)); } return 0; } int main() { thread t1(ThreadProc1); thread t2(ThreadProc2); t1.detach(); t2.detach(); system("pause"); return 0; }
能够看到使用了原子变量以后,代码简化了不少,以及之后对某些共享资源咱们均可以酌情的定义为原子变量类型,很方便有木有。。。。。
4.future与promise的使用
在c++11中增长的线程库很方便的让咱们去使用线程,可是由于作出了一些改变,咱们并不能像往常同样直接使用thread.join()获取线程函数的返回值了,而咱们有时候又确实要利用线程函数的返回值。
而thread库提供了future用来访问异步操做的结果,由于一个异步操做的结果每每不能当即获取,只能在将来的某个时候从某个地方获取,这个异步操做的结果是一个将来的期待值,因此被称为future
future和promise的做用是在不一样线程之间传递数据。
假设线程1须要线程2的数据,那么组合使用方式以下:
示例代码:
#include <iostream>
#include <chrono> #include <thread> #include <mutex> #include <atomic> #include <future> #include <vector> void disPlay(std::future<int>& value) { std::cout << "wait some times......" << std::endl; auto result = value.get(); //没有获取到值会阻塞等待获取 std::cout << "Value:" << result << std::endl; } int main() { std::promise<int> promise; std::future<int> value = promise.get_future(); //将promise与future绑定 std::thread t1(disPlay, std::ref(value)); //建立线程而且函数传参,ref()是传一个引用 std::this_thread::sleep_for(std::chrono::seconds(1)); //线程延时1秒 //给线程传值进去 promise.set_value(15); t1.join(); system("pause"); return 0; }
获取future的结果有三种方式上面是get()获取异步结果值返回,还有wait()等待异步操做完成,以及wait_for()超时等待返回结果。
5.future与package_task的使用
std::packaged_task包装一个可调用的对象,而且容许异步获取该可调用对象产生的结果。
std::packaged_task将其包装的可调用对象的执行结果传递给一个std::future对象,与std::promise某种程度上是很像的,promise保存一个共享状态的值,而package_task保存的是一个函数。
示例代码:
#include <iostream>
#include <chrono> #include <thread> #include <mutex> #include <atomic> #include <future> #include <vector> inline int func(int x) { return x + 6; } int main() { std::packaged_task<int(int)> tsk(func); std::future<int> fut = tsk.get_future(); //获取future绑定起来 std::thread(std::move(tsk), 2).detach();//直接将task转移做为线程函数使用 auto value = fut.get(); std::cout << "result:" << value << std::endl; system("pause"); return 0; }
6.线程异步操做函数async的用法
,std::async比std::packaged_task,std::promise中,std::thread更高一层,它能够直接用来建立异步的task,异步的结果也保存在future中。完成后,外面再经过future.get/wait来获取这个将来的结果,强烈推荐使用async,咱们不须要关注异步任务的结果,只要等待任务完成获取值就好了。
如今来看看std::async的原型async(std::launch::async | std::launch::deferred, f, args...),第一个参数是线程的建立策略,有两种策略,默认的策略是当即建立线程:
第二个参数是线程函数,第三个参数是线程函数的参数。
代码示例:
#include <iostream>
#include <chrono> #include <thread> #include <mutex> #include <atomic> #include <future> #include <vector> int main() { std::future<int> fut = std::async(std::launch::async, []() { return 9; }); std::cout << "result:" << fut.get() << std::endl; system("pause"); return 0; }
[]()这是c++11里面lambda表达式用法
函数原型:
等待结果变得可用。阻塞直至通过指定的 timeout_duration
,或结果变为可用,二者的先到来者。返回值鉴别结果的状态。
此函数可能因为调度或资源争议延迟而阻塞长于 timeout_duration
。
推荐标准库用稳定时钟度量时长。若实现用系统时钟代替,则等待时间可能也对时钟调整敏感。
若调用此函数前 valid()== false 则行为未定义。
timeout_duration | - | 要阻塞的最大时长 |
常量 | 解释 |
future_status::deferred | 要计算结果的函数仍未启动 |
future_status::ready | 结果就绪 |
future_status::timeout | 已通过时限 |
时钟、时间点或时长在执行中可能抛的任何异常(标准库提供的时钟、时间点和时长决不抛出)。
代码示例:
#include <iostream>
#include <future> #include <thread> #include <chrono> int main() { std::future<int> future = std::async(std::launch::async, [](){ std::this_thread::sleep_for(std::chrono::seconds(3)); return 8; }); std::cout << "waiting...\n"; std::future_status status; do { status = future.wait_for(std::chrono::seconds(1)); if (status == std::future_status::deferred) { std::cout << "deferred\n"; } else if (status == std::future_status::timeout) { std::cout << "timeout\n"; } else if (status == std::future_status::ready) { std::cout << "ready!\n"; } } while (status != std::future_status::ready); std::cout << "result is " << future.get() << '\n'; }
可能结果:
waiting...
timeout
timeout
ready! result is 8
后面还会出不少一系列的入门教程,能够关注我噢。(我博客难道写的不清楚吗,大家还不关注我???小声bb)。。。。hhhhhhhh
也能够补一下基础多线程编程教程以下:
如有兴趣交流分享技术,可关注本人公众号,里面会不按期的分享各类编程教程,和共享源码,诸如研究分享关于c/c++,python,前端,后端,opencv,halcon,opengl,机器学习深度学习之类有关于基础编程,图像处理和机器视觉开发的知识