c++11标准线程库用法总结
来源:互联网 发布:淘宝规则关于假货 编辑:程序博客网 时间:2024/05/18 02:58
- 一线程库thread
- 通过全局函数创建线程
- 通过伪函数创建线程
- 通过lambda表达式创建线程
- 通过成员函数创建线程
- 二原子操作库atomic
- 三互斥 mutex
- 互斥体类
- 1 非定时互斥体类
- 2定时互斥锁类
- 锁类
- 1 简单锁 stdlock_guard
- 2 复杂锁 stdunique_lock
- 多个互斥体对象上锁
- call_once 和once_flag
- 实例
- 互斥体类
- 四条件变量
- condition_variable
- condition_variable_any
- 五信号量类
- 六线程异步调用和返回值
- stdasync
- stdfuture
- stdshared_future
- stdpackaged_task
- 七this_thread
- 八总结
- 参考资料
一、线程库(thread)
C++11中线程较轻量级,线程的创建和执行的消耗较小,但是同时C++中线程没有明确的状态信息,如果不怕麻烦可以直接使用linux下的posix线程库,这对底层开发者还是很有用的。如果有时间我会整理一份posix 线程的相关使用手册。当然还有很多第三方优秀的c++线程库这里就不讨论了。
**c++标准线程库是c++11/14/17才支持的,很多编译器的版本还并没有完全支持他们的标准,所以在正式开始教程之前呢,先给大家提供一个c++ 最新标准的在线编译环境(http://cpp.sh/)用来测试程序。
c++11线程特点:
1. thread创建后,C++始终尝试在一个新线程中执行人物,如果失败返回std::system_error
2. 没有线程运行结果的接口,无法获得执行结果。(后面会讨论其他方法)
3. 如果有异常在thread中发生,并且没有被捕获,std::terminate将会被调用
4. 只能调用join去结束线程,或者detach去转成后台线程执行。否则当thread被析构时,程序会被abort。
5. 如果线程detach并且main函数执行结束,所以的detach线程会被终止.
6. 线程只可以通过std::move转移,不可以复制。
1. 通过全局函数创建线程
线程类的构造函数是变参构造函数,第一个参数是线程函数,后面的参数为线程
函数的参数(参数通过值传递方式,若要引用传递须加std::ref())。
include <thread>#include <iostream> void hello(int i){ std::cout << "Hello from thread :" << i<<std::endl; } int main(){ std::thread t1(hello,1); t1.join(); return 0;}
2. 通过伪函数创建线程
//class Counter 实现 operator()1) thread t1{Counter(1, 20)}; //c++统一推荐方法2) Counter c(1, 20); thread t2(c);3) thread t3(Counter(1,20)); #include <iostream>#include <thread>#include <chrono>using namespace std;class thread_c{public: void operator () () { cout << "hello from class member function" << endl; }private:};int main(){thread_c c;thread t(c);//thread t1{thread_c()}; this_thread::sleep_for(chrono::seconds(1)); return 0;}
比较第一种和第三种构造方式,如果函数对象的构造函数不需要任何参数,thread t3(Counter());是不行的,因为编译器会认为你在声明一个函数,函数名为t3,此时只能用第一种构造方式。
3. 通过lambda表达式创建线程
#include <thread>#include <iostream>#include <vector> int main(){ std::vector<std::thread> threads; for(int i = 0; i < 5; ++i){ threads.push_back(std::thread([](){ std::cout << "Hello from thread " << std::this_thread::get_id() << std::endl; })); } for(auto& thread : threads){ thread.join(); } return 0;}
4. 通过成员函数创建线程
// class Counter c();
thread t{&Counter::process, &c};//也可以
一般常见的是一个类自己创建一个后台处理线程:thread t{&Counter::process, this};
--------------------------------------范例1------------------------------------#include <iostream>#include <thread>#include <chrono>using namespace std;class thread_c{public: void thread_1(int a) { cout << "hello from class member function :" <<a<< endl; }};int main(int argc, char **argv){ thread_c a; thread t1(&thread_c::thread_1, a,1); t1.join(); this_thread::sleep_for(chrono::seconds(2)); return 0;}--------------------------------------范例2------------------------------------#include <iostream>#include <thread>#include <chrono>#include <vector>using namespace std;class thread_c{public: thread_c() { t1 = thread(&thread_c::thread_1,this); };void thread_1(){ cout << "hello from class member function" << endl;}private: thread t1;};int main(int argc, char **argv){ thread_c a; //this_thread::sleep_for(chrono::seconds(1)); cout<<"test"<<endl; return 0;}
线程本地存储 thread_local
thread_local int n;
n作为线程参数传递给线程,那么每个线程有一个n的副本,在线程整个生命周期中存在,且只初始化一次,如同static局部变量.
二、原子操作库(atomic)
1.实例
例如:
atomic counter(0); //等效于 atomic_int counter(0);
…………
++counter; //此时多个线程执行++counter是一个原子操作,是线程安全的。
例:
void func( std::atomic<int>& counter){ for( int i=0; i<1000; ++i ) ++counter;}int main(){ std::atomic<int> counter(0); std::vector<std::thread> threads; for( int i=0; i<10; ++i ) threads.push_back( std::thread{ func, std::ref(counter)} ); for( auto& t : threads ) t.join(); std::count<<"Result="<<counter<<std::endl; return 0;}
三、互斥 (mutex)
编写多线程必须分外留意操作顺序,如果无法避免线程共享数据,则必须提供同
步机制,保证一次只有一个线程能更改数据。使用互斥解决竞争条件,可能导致死锁。
互斥体类
1) 非定时互斥体类
std::mutex std::recursive_mutex
方法:
lock() : 尝试获取锁,并且阻塞直到获取锁。
try_lock() : 尝试获取锁,并立即返回,成功获取返回true,否则false。
unlock() : 释放锁。
mutex与recursive_mutex的区别在于,前者已经获得所后不得再尝试获取,这会死锁,后者能递归获取,注意释放次数应与获取次数相等。
2)定时互斥锁类
std::timed_mutex std::recursive_timed_mutex
方法:
lock() , try_lock() , unlock()
try_lock_for(rel_time) : 指定相对时间内获得返回true, 超时返回false。
try_lock_until(abs_time) : 指定系统绝对时间内获得返回true, 超时返回false。
timed_mutex与recursive_timed_mutex区别同上。
#include <iostream> #include <chrono> #include <thread> #include <mutex> using namespace std;timed_mutex mtx;void fireworks () { while (!mtx.try_lock_for(std::chrono::milliseconds(200))) { cout << "-"; } this_thread::sleep_for(std::chrono::milliseconds(1000)); cout << "*\n"; mtx.unlock();}int main (){ thread threads[10]; // spawn 10 threads: for (int i=0; i<10; ++i) threads[i] = thread(fireworks); for (auto& th : threads) th.join(); return 0;}输出:------------------------------------*----------------------------------------*-----------------------------------*------------------------------*-------------------------*--------------------*---------------*----------*-----**
2. 锁类
锁类是一个包装器,析构函数会自动释放关联的互斥体。
1) 简单锁 std::lock_guard
C++中互斥锁lock之后必须调用unlock,但是考虑可能存在的复杂情况unlock
不一定会被正确调用。使用C++的RAII(Res Acquisition is init 资源获取即初
始化),利用destructor做unlock处理。其构造函数会要求获得互斥体,并阻塞直到获得锁。
如:
std::mutex mutex;...{std::lock_guard<std::mutex> lg(mutex);}//unlock when block end
尝试获取锁
std::mutex m;
while(!m.try_lock()){//可能失败即使锁可用
doSomethings();
}
std::lock_guard lg(m,std::adopt_lock);
//不会lock,但在lg对象析构时会解锁
2) 复杂锁 std::unique_lock
explict unique_lock( mutex_type& m); //阻塞直到获得锁。unique_lock(mutex_type& m, defer_lock_t) noexcept; //保存一个互斥体引用,不会立即尝试获得锁。锁可以在以后获得。之后可以用std::lock() 进行加锁 unique_lock(mutex_type& m, try_to_lock_t); //尝试获得引用的互斥锁,未能获得也不阻塞。 unique_lock(mutex_type& m, adopt_lock_t); //该锁假定线程已获得引用的互斥锁,并负责管理这个锁。 template<class Clock, class Duration> unique_lock(mutex& m, const chrono::time_point<Clock, Duration>& abs_time); //尝试获取该锁,直到超过给定的绝对时间。 template<class Rep, class Period> unique_lock(mutex& m, const chrono::duration<Rep, Period>& rel_time); //尝试获取该锁,直到超过给定的相对时间。unique_lock类还支持lock(), try_lock(), try_lock_for(), try_lock_until()等方法。通过owns_lock()查看是否获得了这个锁;也可以用if对unique_lock对象直接判断是否获得锁,因为它定义了bool()运算符。
3. 多个互斥体对象上锁
std::mutex m1;std::mutex m2;...{ std::lock(m1,m2);//lock m1 and m2 //init with adopt_lock(will not do lock) std::lock_guard<mutex> lockM1(m1,std::adopt_lock);//只管理,不上锁 std::lock_guard<mutex> lockM2(m2,std::adopt_lock);}再例如:try多个锁(不能保证死锁处理)int idx = std::try_locK(m1,m2);if(idx<0){//成功返回 -1 lock_guard<mutex> lg1(m1,adopt_lock); lock_guard<mutex> lg1(m1,adopt_lock);//不会lock,但保证锁可以被释放}else{ //lock failed}
参数顺序每次应保持一致, 否则易死锁。
4. call_once 和once_flag
保证call_once调度的函数只被执行一次。某些场景下,我们需要代码只被执行一次,比如单例类的初始化,考虑到多线程安全,需要进行加锁控制。C++11中提供的call_once可以很好的满足这种需求,使用又非常简单。
#include<mutex>template <class Fn, class... Args>void call_once (once_flag& flag, Fn&& fn, Args&&...args);
第一个参数是std::once_flag的对象(once_flag是不允许修改的,其拷贝构造函数和operator=函数都声明为delete),第二个参数可调用实体,即要求只执行一次的代码,后面可变参数是其参数列表。
call_once保证函数fn只被执行一次,如果有多个线程同时执行函数fn调用,则只有一个活动线程(active call)会执行函数,其他的线程在这个线程执行返回之前会处于”passive execution”(被动执行状态)——不会直接返回,直到活动线程对fn调用结束才返回。对于所有调用函数fn的并发线程,数据可见性都是同步的(一致的)。
如果活动线程在执行fn时抛出异常,则会从处于”passive execution”状态的线程中挑一个线程成为活动线程继续执行fn,依此类推。一旦活动线程返回,所有”passive execution”状态的线程也返回,不会成为活动线程。(实际上once_flag相当于一个锁,使用它的线程都会在上面等待,只有一个线程允许执行。如果该线程抛出异常,那么从等待中的线程中选择一个,重复上面的流程)。tic std::once_flag oc; // 用于call_once的局部静态变量
#include <string.h> #include <assert.h> #在单例模式中建立对象,最好使用call_once或者进行加锁class object { public: static class object* pObject; static object* create_new_object() { std::call_once(oc,[&] () { pObject = new object(); }); //不得不说c++11中的call_once真的很好用,谁用谁知道。 return pObject; } private: static std::once_flag oc; object() {} ~object() {} }; class object* object::pObject = NULL; int main(int argc, char* argv[]) { object* pGlobal = object::create_new_object(); return 1; }
还有一个要注意的地方是 once_flag的生命周期,它必须要比使用它的线程的生命周期要长。所以通常定义成全局变量比较好。
5. 实例
// 1. 简单锁mutex mMutex;lock_guard<mutex> mLock(mMutex);// 2. 定时锁timed_mutex mTimeMutex;unique_lock<timed_mutex> mLock(mTimedMutex, chrono::milliseconds(200));// 3. 泛型mutex mut1;mutex mut2;unique_lock<mutex> lock1(mut1, defer_lock_t());unique_lock<mutex> lock2(mut2, defer_lock_t());lock(lock1, lock2);// 4. 双重检查锁定算法 (代替call_once的用法)class MyClass{ public: void init() { p = new int(0); cout<<"Init"<<endl;} private: int *p;}MyClass var;bool initialized = false;mutex mut;void func() { if( ! initialized) //一次检查 { unique_lock<mutex> lock1(mut); if( ! initialized) //两次检查 { var.init(); initialized = true; }else return; }else return; cout<<"OK"<<endl;}//两次检查initialized。获得锁之前和获得锁之后,确保init只调用一次。
四、条件变量
1. condition_variable
只能等待unique_lock<mutex>的条件变量 notify_one(); //唤醒等待这个条件变量的线程之一 notify_all(); //唤醒所有等待这个条件变量的线程 // 1)前提是已经获得lk的锁 // 2)调用wait会unlock lk,然后等待 // 3)当被唤醒后 lock lk以下是重载版本,非重载版本没有predvoid wait (unique_lock<mutex>& lck, Predicate pred);wait_for (unique_lock<mutex>& lck,const chrono::duration<Rep,Period>& rel_time, std::move(pred));wait_until (lck, chrono::system_clock::now() + rel_time, std::move(pred));另外,wait_for 的重载版本(predicte(2))的最后一个参数 pred 表示 wait_for 的预测条件,只有当 pred 条件为 false 时调用 wait() 才会阻塞当前线程,并且在收到其他线程的通知后只有当 pred 为 true 时才会被解除阻塞,因此相当于如下代码:超时返回cv_status::timeout
#include <iostream> #include <thread> #include <chrono> #include <mutex> #include <condition_variable> using namespace std; condition_variable cv; mutex mtx; void thread_1() { unique_lock<mutex> ulock(mtx); cv.wait(ulock); cout << "hello from thread_1" << endl; } int main(int argc, char **argv) { thread t1(thread_1); this_thread::sleep_for(chrono::seconds(1)); cv.notify_one(); t1.join(); return 0; }
2.condition_variable_any
// condition_variable_any::wait (with predicate)#include <iostream> // std::cout#include <thread> // std::thread, std::this_thread::yield#include <mutex> // std::mutex#include <condition_variable> // std::condition_variable_anystd::mutex mtx;std::condition_variable_any cv;int cargo = 0;bool shipment_available() {return cargo!=0;}void consume (int n) { for (int i=0; i<n; ++i) { mtx.lock(); cv.wait(mtx,shipment_available); // consume: std::cout << cargo << '\n'; cargo=0; mtx.unlock(); }}int main (){ std::thread consumer_thread (consume,10); // produce 10 items when needed: for (int i=0; i<10; ++i) { while (shipment_available()) std::this_thread::yield(); mtx.lock(); cargo = i+1; cv.notify_one(); mtx.unlock(); } consumer_thread.join(); return 0;}
五、信号量类
很遗憾c++并没有提供信号量( 信号灯)机制的库。所以可以自己设计一个,下面的程序是google的,只供参考。可以根据需求自己设计信号量类。
class CSemaphore {private: std::condition_variable cv; std::mutex mutex; int value;public: CSemaphore(int init) : value(init) { } void wait() {//类似P操作 std::unique_lock<std::mutex> lock(mutex); while (value < 1) { cv.wait(lock); } value--; } bool try_wait() { std::unique_lock<std::mutex> lock(mutex); if (value < 1) return false; value--; return true; } void post() {//类似V操作 { std::unique_lock<std::mutex> lock(mutex); value++; } cv.notify_one(); }};
六、线程异步调用和返回值
1.std::async
异步调用一个callable的对象,但是不保证调用时机。可以通过传入std::launch::async/deferred来制定是否立即调用和延迟(由系统决定调用时机)。async不保证内部使用线程池来实现,但是肯定使用一个后台线程执行任务。
2.std::future
类似于Java的future,获得任务执行结果,对于无返回值得结果使用future,如果调用future的get方法,可以保证async的会立即执行。async()方法返回值是future.
int sum(int a,int b){return a+b;}
//如果async的返回值没有被使用,那么async将一直阻塞
//编译器可以优化为 std::async(sum,1,1).get(); 阻塞调用
std::future f = std::async(sum,1,1);
int result = f.get();//获得结果,如果有异常还会抛出异常
//f.wait() 等运行完成
//f.wait_for(dur) 等待一定时间或运行完成
//f.wait_until(fp) 等待某一时刻或运行完成
//f.share() 生成一个shared_future 见下文
3.std::shared_future
类似于future,但是future的get方法只能被调用一次,二次调用可能出现未定义的行为(不一定抛出异常或出错)。shared_future的二次调用可以保证一样并且如有一次,可以保证异常一样。
std::shared_future f = std::async(sum,1,1).share();//需要使用share来获得。
4.std::packaged_task
类似于async,但是可以自己控制调用时机,也可以使用线程去调用任务执行。std::packaged_task<int(int,int)> task(sum);//封装函数std::future<int> f = task.get_future();//获得futuretask(1,1); //直接调用//std::thread t{std::move(task),5,5}; //线程方式调用//t.join();//need join or detach threadint result = f.get();//获得结果/////////////////////////////////////////////////////////////////////////// packaged_task example#include <iostream> // std::cout#include <future> // std::packaged_task, std::future#include <chrono> // std::chrono::seconds#include <thread> // std::thread, std::this_thread::sleep_for// count down taking a second for each value:int countdown (int from, int to) { for (int i=from; i!=to; --i) { std::cout << i << '\n'; std::this_thread::sleep_for(std::chrono::seconds(1)); } std::cout << "Lift off!\n"; return from-to;}int main (){ std::packaged_task<int(int,int)> tsk (countdown); // set up packaged_task std::future<int> ret = tsk.get_future(); // get future std::thread th (std::move(tsk),10,0); // spawn thread to count down from 10 to 0 // ... int value = ret.get(); // wait for the task to finish and get result std::cout << "The countdown lasted for " << value << " seconds.\n"; th.join(); return 0;}
七、this_thread
this_thread::get_id: 返回当前线程的id
this_thread::yield: 让调度器先运行其它的线程,这在忙于等待状态时很有用
this_thread::sleep_for: 将当前线程置于阻塞状态,时间不少于参数所指定的时间段
this_thread::sleep_util: 在指定的时刻来临前,一直将当前的线程置于阻塞状态
总结:
std::this_thread::yield() 的目的是避免一个线程(that should be used in a case where you are in a busy waiting state)频繁与其他线程争抢CPU时间片, 从而导致多线程处理性能下降.
std::this_thread::yield() 是让当前线程让渡出自己的CPU时间片(给其他线程使用)
std::this_thread::sleep_for() 是让当前休眠”指定的一段”时间.
sleep_for()也可以起到 std::this_thread::yield()相似的作用, (即:当前线程在休眠期间, 自然不会与其他线程争抢CPU时间片)但两者的使用目的是大不相同的: std::this_thread::yield() 是让线程让渡出自己的CPU时间片(给其他线程使用) sleep_for() 是线程根据某种需要, 需要等待若干时间.
八、总结
啊,感觉像长篇大论了一波,看到这里的童鞋说明还是很有耐心的哟。整理这份资料可花了我不少时间,之前学的时候并没有什么感觉,现在整理出来后发现知识点还是蛮多的哈。
上面的程序部分是来至于网络,仅供参考,只是为了说明用法并无实际意义。如果对锁机制还有不懂的地方,在我的下一篇博客中会展开说明。整理了这么多只为了让学c/c++的同学不孤单(c++的处境已经不如10年之前了,除了学校开课以外,学的人越来越少了),给你一个更方便的学习参考。让我们记住这门古老的语言,因为它的强大只有我们自己知道。以后我还会陆续推出更多总结和小案例。文中存在缺陷或不足之处在所难免,还望大佬们多多指教。
参考资料
c++ reference
网络资料整理
- c++11标准线程库用法总结
- 【C/C++】标准C++中的string类的用法总结
- 标准c++用法总结
- C标准库的setlocale()用法笔记
- C 标准库的 setlocale() 函数用法
- C标准库的setlocale()用法笔记
- C标准库的setlocale()用法笔记
- c标准库中qsort函数用法
- C标准库的setlocale()用法笔记
- C标准库的setlocale()用法笔记
- C 标准库的 setlocale() 函数用法
- C标准库的setlocale()用法笔记
- C标准库的setlocale()用法笔记
- C标准库的setlocale()用法笔记
- C标准库的setlocale()用法笔记
- C标准库的setlocale()用法笔记
- C标准库的setlocale()用法笔记
- C 标准库的 setlocale() 函数用法
- Oracle 查看表空间的大小及使用情况sql语句(oracle数据库维护精品)
- 【Leetcode-easy-543】Diameter of Binary Tree
- Python学习笔记
- MFC框架界面开发(二):框架界面的分隔窗口
- Linux操作系统课后参考答案
- c++11标准线程库用法总结
- 二分查表(又称:折半查表)
- 坚持#第214天~零基础自学云计算基础语言应用11~15节
- ClassNotFoundException:MappingJacksonHttpMessageConverter
- Android游戏入门 SurfaceView应用----手指发动小球
- 最优装载问题(贪心基础)
- mac tree命令
- mxnet 框架的搭建
- 甲级Test1002