|
c++11关于并发引入了好多好东西,这里按照如下顺序介绍: std::thread相关c++11之前你可能使用pthread_xxx来创建线程,繁琐且不易读,c++11引入了std::thread来创建线程,支持对线程join或者detach。直接看代码: - 8 A& T7 P! h9 x$ ^4 z, f
- #include <iostream>( K5 u% D' P; V
- #include <thread>7 ]3 j$ ? V h% {$ U* F3 x
, b q$ {$ }$ y. W* |4 `- using namespace std;3 E% g0 E* p! w9 Q! O6 X
- 4 r. m% b, X* l }4 G% j& y
- int main() {9 b4 O( O( b! p* c
- auto func = []() {, S9 ~/ N4 l% w# _
- for (int i = 0; i < 10; ++i) {
5 F' [$ y2 R4 m3 U - cout << i << " ";
' k! B* y0 k7 h6 U8 } - }0 U5 o1 u1 ?$ M8 f/ Y
- cout << endl;8 N; {( ~5 R' ^/ [+ w3 U1 B6 Y
- };
. {3 v* L# x; F - std::thread t(func);" S$ S: k H) K E( {& y5 p
- if (t.joinable()) {
. P" K- f* q/ u8 W" K - t.detach();
' ^+ J O" m& a) b g0 K - }! z0 y) N" l2 I Z2 n/ j
- auto func1 = [](int k) {7 u# Y7 ^. s% x& }; j+ ?) `( g
- for (int i = 0; i < k; ++i) {
) w7 p- x$ N+ `! T# t, s( M - cout << i << " "; @: N1 y2 E3 V' m! N
- }, a6 N4 Q# V1 H+ |/ j% ~% X
- cout << endl;0 m1 a1 |! H1 p6 X
- };
, u: Q) ~; Q4 W& I3 N - std::thread tt(func1, 20);
1 ~. c2 u( g% C7 D# t5 F' t" E - if (tt.joinable()) { // 检查线程可否被join) F2 h7 L: K7 ^1 I
- tt.join();
9 Z# I% ?4 g% }. A/ H: L, I3 O2 K - }
5 `7 Y9 d( B g; I9 C) g - return 0;" e& | N3 O9 n. m& X; U" E. S9 m( h+ P% E' o
- }
复制代码 3 \+ H2 L1 V4 T2 Z+ T
上述代码中,函数func和func1运行在线程对象t和tt中,从刚创建对象开始就会新建一个线程用于执行函数,调用join函数将会阻塞主线程,直到线程函数执行结束,线程函数的返回值将会被忽略。如果不希望线程被阻塞执行,可以调用线程对象的detach函数,表示将线程和线程对象分离。
5 ^2 `( {# R& m2 U( s1 Z0 H; }
如果没有调用join或者detach函数,假如线程函数执行时间较长,此时线程对象的生命周期结束调用析构函数清理资源,这时可能会发生错误,这里有两种解决办法,一个是调用join(),保证线程函数的生命周期和线程对象的生命周期相同,另一个是调用detach(),将线程和线程对象分离,这里需要注意,如果线程已经和对象分离,那我们就再也无法控制线程什么时候结束了,不能再通过join来等待线程执行完。 . }: p% I% y7 h5 L! B
这里可以对thread进行封装,避免没有调用join或者detach可导致程序出错的情况出现: - 6 g# n# ]. c% Y3 Y/ @5 Q& ~* [
- class ThreadGuard {
( }& z% I0 q6 V1 l - public:' ~6 c r, y8 u5 A2 z4 T4 |2 B7 ^" q
- enum class DesAction { join, detach };$ h- l( Y1 G& z, B- D% Q
' t- q3 c( o5 _* e3 X% i- ThreadGuard(std::thread&& t, DesAction a) : t_(std::move(t)), action_(a){};
/ }4 K3 v) J* g0 V4 ?/ x - " c, l& R, _0 a9 |
- ~ThreadGuard() {! Y$ f4 K; H- @" m, O: H' O+ a
- if (t_.joinable()) {/ ^0 n8 n5 r6 f3 g7 u: k
- if (action_ == DesAction::join) {
3 v$ |8 m- f4 s4 T - t_.join();
* o7 G0 J& O! V- o: c: C: F0 F - } else {
+ n; a6 H, W, } - t_.detach();
6 j2 Y2 Y; V" x, W - }2 D n& h2 u9 v N
- }* P- d, u6 O% x u& ~2 e V
- }
9 b" g. r# A2 a. Y' y8 d" W% y - / f$ `( `1 v+ l; y+ ?( F$ C8 E9 ^
- ThreadGuard(ThreadGuard&&) = default;; \- x4 L* ^7 I9 U) V# C
- ThreadGuard& operator=(ThreadGuard&&) = default;
q/ q+ f n1 X# V& }( Y$ p
- v- K$ _. m, j- std::thread& get() { return t_; }
6 L* B/ R5 S, Z
8 Z" W! S8 u: N: W7 A- private:
5 ? q5 }/ F9 A1 C7 H* r - std::thread t_;
3 ]- P8 U k! @4 F5 c h - DesAction action_;
) L2 ?& u; v- Z$ l& k1 ]! |1 ]$ _ - };4 y/ W$ v3 P4 s. ?% a
4 i1 Q9 H2 E; T A6 c0 ^9 B- int main() {8 t- b1 ^- J, ]* n
- ThreadGuard t(std::thread([]() {
: u/ k7 K* }" W& H. ]9 W - for (int i = 0; i < 10; ++i) {3 I( ]* b& m( S, k/ Q# M
- std::cout << "thread guard " << i << " ";
# U5 s# j& O' X$ a" A6 { - }
9 {2 n+ M! O2 e: E0 \ - std::cout << std::endl;}), ThreadGuard::DesAction::join);
4 O6 s/ w' d( M U; [0 W; C - return 0;
, L: L* l6 R, ^& P - }
复制代码
& C4 M, p! @7 w; Yc++11还提供了获取线程id,或者系统cpu个数,获取thread native_handle,使得线程休眠等功能 - 6 [2 R2 @. d( i l
- std::thread t(func);2 m0 h& I. A4 Q: D4 C% F
- cout << "当前线程ID " << t.get_id() << endl;
% _. U5 ?. n0 v& n9 s; c. A - cout << "当前cpu个数 " << std::thread::hardware_concurrency() << endl;
, M7 E& q4 \2 r* o - auto handle = t.native_handle();// handle可用于pthread相关操作' R" x# i9 v8 G# v9 h& ]+ O _; O( N
- std::this_thread::sleep_for(std::chrono::seconds(1));
复制代码 ; z8 }' y7 R; B; `
std::mutex相关 std::mutex是一种线程同步的手段,用于保存多线程同时操作的共享数据。 mutex分为四种: std::mutex:独占的互斥量,不能递归使用,不带超时功能 std::recursive_mutex:递归互斥量,可重入,不带超时功能 std::timed_mutex:带超时的互斥量,不能递归 std::recursive_timed_mutex:带超时的互斥量,可以递归使用
+ l6 \& m, M' b( K9 P1 }/ m
拿一个std::mutex和std::timed_mutex举例吧,别的都是类似的使用方式: std::mutex: - 6 Y7 C5 }8 e. I+ g* O
- #include <iostream>
4 ]) P+ ^( @; ~ R: i r2 I. h1 y - #include <mutex>
* |" h( B4 a5 v) C - #include <thread>
5 Y3 p% I% n t4 ]& V- p
- R* G4 }$ ]9 a2 ] L4 J+ Q- using namespace std;
3 [0 N& R- J2 D$ J" {3 t! c) r - std::mutex mutex_;
/ [ p. @3 C/ n9 h' Y$ W9 `. J - 0 q* o) Y5 P1 h3 V
- int main() {, P: D5 O- D6 Z% L1 x# L; |
- auto func1 = [](int k) {
" \+ c4 q0 T% t# u5 G - mutex_.lock();
4 F% `% p, q% J% w - for (int i = 0; i < k; ++i) {' V" s: \3 k/ v1 X, s5 ~
- cout << i << " ";
[1 p/ @' q$ ]) U5 v - }$ ] o/ u/ z) x* ]' {( z( `
- cout << endl;
: D |7 }8 z$ {$ X& L) a% X4 z - mutex_.unlock();2 T" J! h. y- ]! X* y3 ^, m
- };/ z, X4 I# V6 o! X! K3 n
- std::thread threads[5];
2 P. }" `/ D. k) y- O4 C9 i- r - for (int i = 0; i < 5; ++i) {( v! J" e: i; n5 H; P0 H0 A
- threads[i] = std::thread(func1, 200);; d$ K& \5 o4 [$ ?. ~6 c, a, Q
- }( A. ]* [; A4 r2 [1 Y
- for (auto& th : threads) {
, Z$ F+ G: F: E/ m# M+ n9 O - th.join();+ {/ z! }; p" ]
- }
- A. H& }( C( f - return 0;- c4 P' Z( O! m: Z) y* l$ W
- }
复制代码
0 f0 _& {: k% f i- Tstd::timed_mutex:
- [/ Q0 l" Y8 }1 n% b! }6 Z- #include <iostream>% [; \# @" n' C; \' b; H6 K
- #include <mutex>
& a) E0 y7 F" Q, g - #include <thread>! T& l' X! l8 Y" ^! v: O
- #include <chrono>
: z6 Z5 i- k8 [) ^, H- A' ]2 b. y8 G - / a% d, U7 @% I) X& u
- using namespace std;4 V& |8 w, E) o7 m' k
- std::timed_mutex timed_mutex_;; I& w' d* }/ ]6 S( _
U; W; I8 H1 r- O' M$ n% Q9 k! T. K" y- int main() {
' Z$ ]: k, ]: X& M y+ {5 E - auto func1 = [](int k) {
5 I: M+ \& F `( k' d - timed_mutex_.try_lock_for(std::chrono::milliseconds(200));
! b8 J' F; l) U( s. Y - for (int i = 0; i < k; ++i) {
. {& j) x) X" S- R( n: W( R - cout << i << " ";) I0 W% v- R i
- }& O2 A' H. |7 w0 `/ H1 I
- cout << endl;+ t& S; w; B. P- b' j1 K
- timed_mutex_.unlock();8 k* G* Y6 w6 \$ Q+ f
- };4 ?! s% f: \$ E& k& _& Q$ {
- std::thread threads[5];9 P, }0 R! p5 P
- for (int i = 0; i < 5; ++i) {
8 n$ k. l9 B0 h! m0 ^ - threads[i] = std::thread(func1, 200);4 A3 ]) Z) q2 M. q" [/ v
- }
& t; ]5 \5 R$ L' n5 k7 S6 b! } - for (auto& th : threads) {
. P: U. Q" l3 O2 c @/ c - th.join();
0 ~& |# P% j5 q% V6 x& F* M/ h - }
# }, s2 s7 c0 } R# y$ _ - return 0;
4 Q# e: \8 t% f0 H1 @( p - }
复制代码
( f+ N3 b# G8 ~) @' Qstd::lock相关 这里主要介绍两种RAII方式的锁封装,可以动态的释放锁资源,防止线程由于编码失误导致一直持有锁。 c++11主要有std::lock_guard和std::unique_lock两种方式,使用方式都类似,如下:
4 F7 {2 X# @/ J$ U& S- #include <iostream>
' B e6 ?2 N/ S8 h. p6 n- Q+ ] - #include <mutex>5 ?8 x+ D3 q. B/ i' g+ v- g M
- #include <thread>
. v6 Z0 m- O$ z: `6 b2 i0 S/ P - #include <chrono>
6 C* f& M. v$ H7 [( R - : a% t9 ]2 ?/ Q' o, g
- using namespace std;
+ U7 ], z( _8 q5 B1 d } - std::mutex mutex_;
( e. {& J$ s: y: @& \+ s
/ a2 m f) `5 U3 {9 r4 _: S+ n \- int main() {1 O0 Y$ q8 r+ \0 j% ]$ x
- auto func1 = [](int k) {
. R4 @ ^. u4 j$ O% T - // std::lock_guard<std::mutex> lock(mutex_);( `1 C. U3 O0 O
- std::unique_lock<std::mutex> lock(mutex_);
' c! } w J7 L2 e - for (int i = 0; i < k; ++i) {
3 G* |, n+ O3 Z - cout << i << " ";, N" s: a2 G, A, W- Z5 U' _1 E! w
- }
/ H# u! ~$ W, J) w W+ N - cout << endl;/ p/ S1 v5 N* m0 X [8 Z
- };0 `# K+ ]5 j, Y) C. ?: I
- std::thread threads[5];: I) U# D) \& j
- for (int i = 0; i < 5; ++i) {
; X5 m0 D5 i# O$ B) G; N - threads[i] = std::thread(func1, 200);: T8 o L# D$ e+ D
- }9 z7 [ _; O/ p( H0 P0 @
- for (auto& th : threads) {
8 a: S+ c# C9 W9 r! s* y4 O$ S! N - th.join();# Q/ L! g" W2 `
- }+ n. A3 \' P9 V& k& Q
- return 0;+ x- l% j7 K y6 y0 \5 M
- }
复制代码 0 s1 Q; Q- @% P
std::lock_gurad相比于std::unique_lock更加轻量级,少了一些成员函数,std::unique_lock类有unlock函数,可以手动释放锁,所以条件变量都配合std::unique_lock使用,而不是std::lock_guard,因为条件变量在wait时需要有手动释放锁的能力,具体关于条件变量后面会讲到。 std::atomic相关c++11提供了原子类型std::atomic<T>,理论上这个T可以是任意类型,但是我平时只存放整形,别的还真的没用过,整形有这种原子变量已经足够方便,就不需要使用std::mutex来保护该变量啦。看一个计数器的代码:
) B5 n8 i( k- g8 ]- struct OriginCounter { // 普通的计数器
, T7 Y9 f0 M3 S8 P - int count;/ ~7 g: i% V* c$ A' c
- std::mutex mutex_;+ ~" y$ K6 U: I
- void add() { p4 t2 A6 _" {: g
- std::lock_guard<std::mutex> lock(mutex_);
8 P) k: M7 K1 _+ l4 u1 _ - ++count;' \; K" `" S. I- H& L
- }4 y i) K: D) r3 \
6 x! W. W3 c( |' v' ]- void sub() {
0 k R0 w: m, f% m2 [+ G - std::lock_guard<std::mutex> lock(mutex_);
! T. n0 }; P. `$ a, H - --count;
+ @$ r8 a: z/ Q7 ]8 y - }! s4 t- B' D, ?% G- v
- $ c1 z; A' B t' `1 E+ i0 u4 M
- int get() {7 O F) J3 v# \* S1 i; D- x7 f8 K
- std::lock_guard<std::mutex> lock(mutex_);
3 p+ }) e- Z) X' Q" l! i - return count;
3 ], i7 R2 n/ {8 u! w E- O - }- J$ I( g/ o% Y; K: q
- };
/ p$ y3 t8 v5 U
, E3 n* r% d" l% q- struct NewCounter { // 使用原子变量的计数器: m* g* |; e O! {! W
- std::atomic<int> count;5 r7 J9 D6 I$ ?; Y
- void add() {
( a9 _& P. c" ^" q3 E+ I - ++count;
; Q" ?# O# [7 w H$ J2 H" y - // count.store(++count);这种方式也可以8 u; }/ r9 `" S& s2 C
- }
$ e4 S( I, A) g+ S0 ?! ~) F6 I - 5 R- d9 |6 @# n# Z4 f
- void sub() {
- }4 y& N( v+ z5 T8 D1 z0 H6 e c - --count;6 ^ Q+ |* N9 o$ C4 q
- // count.store(--count);
9 j- I5 ^. ~- _8 n' L9 Z - }+ ^( f: K$ e) F, \- g! j
& P) H) d6 j, h/ N; R- int get() {/ r* b! K0 N" I, b2 y
- return count.load();9 V' K9 k4 h6 { T7 y
- }) T$ Z9 Y2 b+ p
- };
复制代码 8 T8 v. n* S% x* w$ Q5 i
是不是使用原子变量更加方便了呢? std::call_once相关c++11提供了std::call_once来保证某一函数在多线程环境中只调用一次,它需要配合std: nce_flag使用,直接看使用代码吧: - 6 \( J* ?" o* v
- std::once_flag onceflag;( b8 ~9 n& I. X
- ! L5 T8 H5 E* ^6 D5 W9 E
- void CallOnce() {- K8 {. Y; W( ]- C
- std::call_once(onceflag, []() {# c6 _/ z, C9 ]; K9 R3 s
- cout << "call once" << endl;; }/ }; `6 F5 q, ?; E
- });4 b4 k6 J( c" |+ j9 N+ i
- }# u$ c" p' Q& I: t7 J) y }: D
$ g" \% g# B( B Q. p/ [& R. Z- int main() {
2 T8 _. S8 A% |6 s - std::thread threads[5];
0 U* F/ B: C* \8 }1 R! I7 T - for (int i = 0; i < 5; ++i) {9 @0 C+ _$ o ~) o: _3 R ?7 P
- threads[i] = std::thread(CallOnce);( A8 d, t) r; y2 `9 x6 R
- }9 z' s! n [" q- b
- for (auto& th : threads) {
! D L" n9 F, I5 t. | |2 K( Q - th.join();+ D2 S5 L, C4 ?! d! K0 f! {
- }0 D& H0 z8 r* w6 M
- return 0;
u# C" S$ a U* x - }
复制代码 4 L* H& @8 Z* h4 Q
volatile相关 貌似把volatile放在并发里介绍不太合适,但是貌似很多人都会把volatile和多线程联系在一起,那就一起介绍下吧。 volatile通常用来建立内存屏障,volatile修饰的变量,编译器对访问该变量的代码通常不再进行优化,看下面代码:
4 _/ V0 N- a: y- j# @9 A, B( D- int *p = xxx; f T4 ^' a3 T8 Q4 z
- int a = *p;$ n4 U0 W6 K2 I: V+ M
- int b = *p;
复制代码 2 j, L$ ?; ^2 z+ e9 i
a和b都等于p指向的值,一般编译器会对此做优化,把*p的值放入寄存器,就是传说中的工作内存(不是主内存),之后a和b都等于寄存器的值,但是如果中间p地址的值改变,内存上的值改变啦,但a,b还是从寄存器中取的值(不一定,看编译器优化结果),这就不符合需求,所以在此对p加volatile修饰可以避免进行此类优化。 注意:volatile不能解决多线程安全问题,针对特种内存才需要使用volatile,它和atomic的特点如下:
2 X5 X& T9 y+ V! c# D• std::atomic用于多线程访问的数据,且不用互斥量,用于并发编程中
1 W, U. D3 Y9 n: J3 j• volatile用于读写操作不可以被优化掉的内存,用于特种内存中# o0 V6 P* N1 ^1 f
' T" H) ]5 R6 Z
$ \: |0 T/ R! _ z# ~std::condition_variable相关+ u9 I9 x2 @9 y# c
条件变量是c++11引入的一种同步机制,它可以阻塞一个线程或者个线程,直到有线程通知或者超时才会唤醒正在阻塞的线程,条件变量需要和锁配合使用,这里的锁就是上面介绍的std::unique_lock。 这里使用条件变量实现一个CountDownLatch:
# ?2 h9 W% H5 J% e; @- class CountDownLatch {7 y% D0 J+ g. y/ V, _
- public:
1 z7 G5 r8 V& x- E - explicit CountDownLatch(uint32_t count) : count_(count);
3 U, B: L! V+ A [
# A# D! s: ~# T: ]/ Q- void CountDown() {) W! ^' p. y: T) \, q: M
- std::unique_lock<std::mutex> lock(mutex_);
7 p% v/ n0 C6 U% L) D* g2 |3 y - --count_;! s/ S; c: c7 u" _
- if (count_ == 0) {
/ e! H$ ^! m# S! l4 r6 j8 d5 d5 R3 L+ @ - cv_.notify_all();
% t4 `7 M5 |3 S0 r2 r' ^( X& ~ - }
* C }8 t( ?, O1 y4 C- K& u - }
# P0 P; s J/ |4 }$ @- U" x
/ P7 n% ]3 K% K/ ?2 j \- void Await(uint32_t time_ms = 0) {
4 q9 y; k) Z- X - std::unique_lock<std::mutex> lock(mutex_);4 g! n) F r, ?5 H$ |3 V
- while (count_ > 0) {
b4 w8 Q, U: l' Y- m' I0 b- b$ _$ p - if (time_ms > 0) {
4 \2 F; o6 [0 N" h! g+ @ - cv_.wait_for(lock, std::chrono::milliseconds(time_ms));8 e, F; Q4 C. W) V
- } else {
" T; X& ] L6 F D; u+ s& K1 x - cv_.wait(lock);
& ?! y9 t; @7 {2 }) g3 g x - }, |* Z. g) ? @2 o; o! h7 T: j z
- }6 x$ y2 ^! k' U( x% h
- }" j$ K- B( n' E4 j) T) ^
- 9 V& o; ]' y5 h, j. q
- uint32_t GetCount() const {( y! X2 W8 A- c$ Y
- std::unique_lock<std::mutex> lock(mutex_);5 z+ |3 S( B% N$ H
- return count_;
- L7 O. C% n% }- e - }" x! T: {8 P8 i0 c
- 8 f9 c7 p/ e: N! \ N; ~) _; Q$ g& a
- private:
4 H4 x- k2 E0 {& p% @ - std::condition_variable cv_;
" ~6 `4 H. M/ R* N* Q - mutable std::mutex mutex_;
) R8 G# G W: w) \6 q. p - uint32_t count_ = 0;- d" o# n# ~5 R: t1 e) z) x* ~7 z
- };9 z0 H& v; \4 K1 s# |
复制代码关于条件变量其实还涉及到通知丢失和虚假唤醒问题,因为不是本文的主题,这里暂不介绍,大家有需要可以留言。 std::future相关c++11关于异步操作提供了future相关的类,主要有std::future、std::promise和std::packaged_task,std::future比std::thread高级些,std::future作为异步结果的传输通道,通过get()可以很方便的获取线程函数的返回值,std::promise用来包装一个值,将数据和future绑定起来,而std::packaged_task则用来包装一个调用对象,将函数和future绑定起来,方便异步调用。而std::future是不可以复制的,如果需要复制放到容器中可以使用std::shared_future。 std::promise与std::future配合使用
& n3 {8 P8 |8 O/ S- #include <functional>
1 y6 h- T, [9 r! k! u - #include <future>( ]# `, c3 f E+ f. M% W# h
- #include <iostream>9 k0 p2 e# V$ v2 K2 M/ m- J
- #include <thread>
; D" l# A2 `) o! h, ^% W
' A s9 p% E3 ]7 |% Y4 l7 ]; U" U- using namespace std;
i2 v* a' @1 ?3 m- J
- n6 R/ O. e+ @1 }; p3 P* I( a+ U- void func(std::future<int>& fut) {
- l. A0 L. y5 B/ d- W4 o - int x = fut.get();$ i$ X3 s- e, e; [4 T1 d' N0 _
- cout << "value: " << x << endl;! q" J8 S! R4 o6 O3 {0 o
- }
3 h8 X' h0 C4 D) W% ?& w% [ - ; [7 W, _# j8 i$ n
- int main() {% o) ]8 k- A: x7 Q" N, B' c1 [
- std::promise<int> prom;
# L1 [! r5 ~" _- Y - std::future<int> fut = prom.get_future();
" ^( Y5 _# Q0 `0 q( L - std::thread t(func, std::ref(fut));
2 V9 t- b6 R& }8 H3 A+ n1 h) Q - prom.set_value(144);: s5 Q! \6 a! D g5 N
- t.join();
/ h' f+ f- c* P. ~5 e# b - return 0;
% W& o' y3 V& ~ j4 r - }
复制代码 ( b3 e$ p% [9 \; V/ ?: [
std::packaged_task与std::future配合使用
- ^* I: x6 V% f* U- #include <functional>- Q2 ] Z+ U% Z8 [( T1 ^
- #include <future>1 ~4 [% b* N5 P+ u) n8 ^ E
- #include <iostream>; M2 U4 S0 K9 h- d8 z: o F
- #include <thread>7 \4 e5 m3 _/ k$ X6 h
- # M; N' c$ Z7 O" h) ?1 n
- using namespace std;2 ?( i! i* P) o' o E
+ B4 [+ b1 v2 Z- int func(int in) {4 l' q- t) E5 k8 _; D
- return in + 1;
. V% x# {, r0 A, A. X, r9 O - }
% r% {9 N1 n1 L* N' ?3 S, O+ X' ]
% A) j* a- e# ]* q- int main() {
5 B" J% v7 h& p6 s, A* U - std::packaged_task<int(int)> task(func);; g" M) j/ f/ _5 q9 N* G
- std::future<int> fut = task.get_future();
$ }" o. F$ ]) k8 o' z9 E; ? - std::thread(std::move(task), 5).detach();
' T0 L/ A9 B+ O7 n7 i - cout << "result " << fut.get() << endl;, I0 P; `5 V$ v" B+ w4 P! \
- return 0;& @: j, i# O5 R$ a
- }
复制代码 ' o3 Q6 z! N0 i6 j
. T. X5 C, b, j& |& h
更多关于future的使用可以看我之前写的关于线程池和定时器的文章。 三者之间的关系 std::future用于访问异步操作的结果,而std::promise和std::packaged_task在future高一层,它们内部都有一个future,promise包装的是一个值,packaged_task包装的是一个函数,当需要获取线程中的某个值,可以使用std::promise,当需要获取线程函数返回值,可以使用std::packaged_task。 async相关async是比future,packaged_task,promise更高级的东西,它是基于任务的异步操作,通过async可以直接创建异步的任务,返回的结果会保存在future中,不需要像packaged_task和promise那么麻烦,关于线程操作应该优先使用async,看一段使用代码:
5 `; N. @# ~8 x0 [) e' i- #include <functional>/ X0 D7 x* I" l- G
- #include <future>
& D. q( w! N& g: @ - #include <iostream>
/ Z! K8 e; W6 f8 Y& v - #include <thread>
# e8 l. ~ F! p* F5 s& n1 a - - ?: ~0 X1 `2 d% M- A5 C& O! {
- using namespace std;& M0 w9 M8 r* G& {& G# h9 t
- % s$ y4 V" A% V
- int func(int in) { return in + 1; }
" v& J# F+ y' h; J {& v( | - , T; D6 ~+ L) J8 Y; s$ n
- int main() {, V8 V2 `7 _0 a- a; m
- auto res = std::async(func, 5);) k5 m8 C) S: e
- // res.wait();1 m/ ?( ^% G9 ~( D Q
- cout << res.get() << endl; // 阻塞直到函数返回) }5 T) E8 i. t
- return 0;& N$ ?0 V I$ {% V0 j) \
- }
复制代码 6 E6 }/ u/ G3 M* r5 b( I1 {
使用async异步执行函数是不是方便多啦。 async具体语法如下:
4 C0 p! x5 g3 b, d3 r6 I; P- async(std::launch::async | std::launch::deferred, func, args...);
复制代码 + \. B: L; p$ ~) ^. ]$ ~' C
第一个参数是创建策略: 如果不明确指定创建策略,以上两个都不是async的默认策略,而是未定义,它是一个基于任务的程序设计,内部有一个调度器(线程池),会根据实际情况决定采用哪种策略。 若从 std::async 获得的 std::future 未被移动或绑定到引用,则在完整表达式结尾, std::future的析构函数将阻塞直至异步计算完成,实际上相当于同步操作: - ! ^+ L5 B) g8 y
- std::async(std::launch::async, []{ f(); }); // 临时量的析构函数等待 f()
3 T# J. A9 G O+ s$ b6 R - std::async(std::launch::async, []{ g(); }); // f() 完成前不开始
复制代码
8 U5 j$ Z m7 |! }5 H7 p" E5 O
1 [. Z+ O( T2 P$ E4 Q注意:关于async启动策略这里网上和各种书籍介绍的五花八门,这里会以cppreference为主。 • 有时候我们如果想真正执行异步操作可以对async进行封装,强制使用std::launch::async策略来调用async。
) ?. z0 G1 |5 w; j% q1 E- template <typename F, typename... Args>% R: O; [6 H) a: s) I& ~9 m1 C! t
- inline auto ReallyAsync(F&& f, Args&&... params) {2 v* z4 {# G1 ^+ s; H
- return std::async(std::launch::async, std::forward<F>(f), std::forward<Args>(params)...);. d+ x0 v9 _+ o7 b, S6 L$ m( R
- }
复制代码
' k. m A/ v+ ~- m0 I, N$ p; v6 g9 }( Y9 N
总结- H) P; `9 h: n1 x5 ~
) @8 S' N: I, H% K" i
, K& `* r8 ~9 t$ `& E•std::thread使线程的创建变得非常简单,还可以获取线程id等信息。) f% r O& F. R8 T
•std::mutex通过多种方式保证了线程安全,互斥量可以独占,也可以重入,还可以设置互斥量的超时时间,避免一直阻塞等锁。
( c* n8 I5 `* p @/ B" w• std::lock通过RAII技术方便了加锁和解锁调用,有std::lock_guard和std::unique_lock。( B% v+ L6 }# {. R
• std::atomic提供了原子变量,更方便实现实现保护,不需要使用互斥量
2 U# k' a7 [5 w' V, C: o• std::call_once保证函数在多线程环境下只调用一次,可用于实现单例。/ j, R1 r) U/ R7 Z* @) ^
• volatile常用于读写操作不可以被优化掉的内存中。6 C: b6 v$ N! D4 l) ]9 ]' M
• std::condition_variable提供等待的同步机制,可阻塞一个或多个线程,等待其它线程通知后唤醒。
' R/ Z4 [3 `4 M, c. v) c4 O• std::future用于异步调用的包装和返回值。
1 e Q# _) j: a7 k% m0 a9 F• async更方便的实现了异步调用,异步调用优先使用async取代创建线程。关于c++11关于并发的新特性就介绍到这里
& @2 G5 b: f+ ^; b' j5 m: y. y0 s |