你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

c++11关于并发的新特性

[复制链接]
gaosmile 发布时间:2020-5-21 16:17
c++11关于并发引入了好多好东西,这里按照如下顺序介绍:
  • std::thread相关
  • std::mutex相关
  • std::lock相关
  • std::atomic相关
  • std::call_once相关
  • volatile相关
  • std::condition_variable相关
  • std::future相关
  • async相关
    6 p' \9 `: D6 t8 @
std::thread相关
c++11之前你可能使用pthread_xxx来创建线程,繁琐且不易读,c++11引入了std::thread来创建线程,支持对线程join或者detach。直接看代码:
  1. 8 A& T7 P! h9 x$ ^4 z, f
  2. #include <iostream>( K5 u% D' P; V
  3. #include <thread>7 ]3 j$ ?  V  h% {$ U* F3 x

  4. , b  q$ {$ }$ y. W* |4 `
  5. using namespace std;3 E% g0 E* p! w9 Q! O6 X
  6. 4 r. m% b, X* l  }4 G% j& y
  7. int main() {9 b4 O( O( b! p* c
  8.    auto func = []() {, S9 ~/ N4 l% w# _
  9.        for (int i = 0; i < 10; ++i) {
    5 F' [$ y2 R4 m3 U
  10.            cout << i << " ";
    ' k! B* y0 k7 h6 U8 }
  11.       }0 U5 o1 u1 ?$ M8 f/ Y
  12.        cout << endl;8 N; {( ~5 R' ^/ [+ w3 U1 B6 Y
  13.   };
    . {3 v* L# x; F
  14.    std::thread t(func);" S$ S: k  H) K  E( {& y5 p
  15.    if (t.joinable()) {
    . P" K- f* q/ u8 W" K
  16.        t.detach();
    ' ^+ J  O" m& a) b  g0 K
  17.   }! z0 y) N" l2 I  Z2 n/ j
  18.    auto func1 = [](int k) {7 u# Y7 ^. s% x& }; j+ ?) `( g
  19.        for (int i = 0; i < k; ++i) {
    ) w7 p- x$ N+ `! T# t, s( M
  20.            cout << i << " ";  @: N1 y2 E3 V' m! N
  21.       }, a6 N4 Q# V1 H+ |/ j% ~% X
  22.        cout << endl;0 m1 a1 |! H1 p6 X
  23.   };
    , u: Q) ~; Q4 W& I3 N
  24.    std::thread tt(func1, 20);
    1 ~. c2 u( g% C7 D# t5 F' t" E
  25.    if (tt.joinable()) { // 检查线程可否被join) F2 h7 L: K7 ^1 I
  26.        tt.join();
    9 Z# I% ?4 g% }. A/ H: L, I3 O2 K
  27.   }
    5 `7 Y9 d( B  g; I9 C) g
  28.    return 0;" e& |  N3 O9 n. m& X; U" E. S9 m( h+ P% E' o
  29. }
复制代码
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可导致程序出错的情况出现:
  1. 6 g# n# ]. c% Y3 Y/ @5 Q& ~* [
  2. class ThreadGuard {
    ( }& z% I0 q6 V1 l
  3.   public:' ~6 c  r, y8 u5 A2 z4 T4 |2 B7 ^" q
  4.    enum class DesAction { join, detach };$ h- l( Y1 G& z, B- D% Q

  5. ' t- q3 c( o5 _* e3 X% i
  6.    ThreadGuard(std::thread&& t, DesAction a) : t_(std::move(t)), action_(a){};
    / }4 K3 v) J* g0 V4 ?/ x
  7. " c, l& R, _0 a9 |
  8.    ~ThreadGuard() {! Y$ f4 K; H- @" m, O: H' O+ a
  9.        if (t_.joinable()) {/ ^0 n8 n5 r6 f3 g7 u: k
  10.            if (action_ == DesAction::join) {
    3 v$ |8 m- f4 s4 T
  11.                t_.join();
    * o7 G0 J& O! V- o: c: C: F0 F
  12.           } else {
    + n; a6 H, W, }
  13.                t_.detach();
    6 j2 Y2 Y; V" x, W
  14.           }2 D  n& h2 u9 v  N
  15.   }* P- d, u6 O% x  u& ~2 e  V
  16.   }
    9 b" g. r# A2 a. Y' y8 d" W% y
  17. / f$ `( `1 v+ l; y+ ?( F$ C8 E9 ^
  18.    ThreadGuard(ThreadGuard&&) = default;; \- x4 L* ^7 I9 U) V# C
  19.    ThreadGuard& operator=(ThreadGuard&&) = default;
      q/ q+ f  n1 X# V& }( Y$ p

  20. - v- K$ _. m, j
  21.    std::thread& get() { return t_; }
    6 L* B/ R5 S, Z

  22. 8 Z" W! S8 u: N: W7 A
  23.   private:
    5 ?  q5 }/ F9 A1 C7 H* r
  24.    std::thread t_;
    3 ]- P8 U  k! @4 F5 c  h
  25.    DesAction action_;
    ) L2 ?& u; v- Z$ l& k1 ]! |1 ]$ _
  26. };4 y/ W$ v3 P4 s. ?% a

  27. 4 i1 Q9 H2 E; T  A6 c0 ^9 B
  28. int main() {8 t- b1 ^- J, ]* n
  29.    ThreadGuard t(std::thread([]() {
    : u/ k7 K* }" W& H. ]9 W
  30.        for (int i = 0; i < 10; ++i) {3 I( ]* b& m( S, k/ Q# M
  31.            std::cout << "thread guard " << i << " ";
    # U5 s# j& O' X$ a" A6 {
  32.       }
    9 {2 n+ M! O2 e: E0 \
  33.        std::cout << std::endl;}), ThreadGuard::DesAction::join);
    4 O6 s/ w' d( M  U; [0 W; C
  34.    return 0;
    , L: L* l6 R, ^& P
  35. }
复制代码

& C4 M, p! @7 w; Y
c++11还提供了获取线程id,或者系统cpu个数,获取thread native_handle,使得线程休眠等功能
  1. 6 [2 R2 @. d( i  l
  2. std::thread t(func);2 m0 h& I. A4 Q: D4 C% F
  3. cout << "当前线程ID " << t.get_id() << endl;
    % _. U5 ?. n0 v& n9 s; c. A
  4. cout << "当前cpu个数 " << std::thread::hardware_concurrency() << endl;
    , M7 E& q4 \2 r* o
  5. auto handle = t.native_handle();// handle可用于pthread相关操作' R" x# i9 v8 G# v9 h& ]+ O  _; O( N
  6. 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:
  1. 6 Y7 C5 }8 e. I+ g* O
  2. #include <iostream>
    4 ]) P+ ^( @; ~  R: i  r2 I. h1 y
  3. #include <mutex>
    * |" h( B4 a5 v) C
  4. #include <thread>
    5 Y3 p% I% n  t4 ]& V- p

  5. - R* G4 }$ ]9 a2 ]  L4 J+ Q
  6. using namespace std;
    3 [0 N& R- J2 D$ J" {3 t! c) r
  7. std::mutex mutex_;
    / [  p. @3 C/ n9 h' Y$ W9 `. J
  8. 0 q* o) Y5 P1 h3 V
  9. int main() {, P: D5 O- D6 Z% L1 x# L; |
  10.    auto func1 = [](int k) {
    " \+ c4 q0 T% t# u5 G
  11.        mutex_.lock();
    4 F% `% p, q% J% w
  12.        for (int i = 0; i < k; ++i) {' V" s: \3 k/ v1 X, s5 ~
  13.            cout << i << " ";
      [1 p/ @' q$ ]) U5 v
  14.       }$ ]  o/ u/ z) x* ]' {( z( `
  15.        cout << endl;
    : D  |7 }8 z$ {$ X& L) a% X4 z
  16.        mutex_.unlock();2 T" J! h. y- ]! X* y3 ^, m
  17.   };/ z, X4 I# V6 o! X! K3 n
  18.    std::thread threads[5];
    2 P. }" `/ D. k) y- O4 C9 i- r
  19.    for (int i = 0; i < 5; ++i) {( v! J" e: i; n5 H; P0 H0 A
  20.        threads[i] = std::thread(func1, 200);; d$ K& \5 o4 [$ ?. ~6 c, a, Q
  21.   }( A. ]* [; A4 r2 [1 Y
  22.    for (auto& th : threads) {
    , Z$ F+ G: F: E/ m# M+ n9 O
  23.        th.join();+ {/ z! }; p" ]
  24.   }
    - A. H& }( C( f
  25.    return 0;- c4 P' Z( O! m: Z) y* l$ W
  26. }
复制代码

0 f0 _& {: k% f  i- T
std::timed_mutex:

  1. - [/ Q0 l" Y8 }1 n% b! }6 Z
  2. #include <iostream>% [; \# @" n' C; \' b; H6 K
  3. #include <mutex>
    & a) E0 y7 F" Q, g
  4. #include <thread>! T& l' X! l8 Y" ^! v: O
  5. #include <chrono>
    : z6 Z5 i- k8 [) ^, H- A' ]2 b. y8 G
  6. / a% d, U7 @% I) X& u
  7. using namespace std;4 V& |8 w, E) o7 m' k
  8. std::timed_mutex timed_mutex_;; I& w' d* }/ ]6 S( _

  9.   U; W; I8 H1 r- O' M$ n% Q9 k! T. K" y
  10. int main() {
    ' Z$ ]: k, ]: X& M  y+ {5 E
  11.    auto func1 = [](int k) {
    5 I: M+ \& F  `( k' d
  12.        timed_mutex_.try_lock_for(std::chrono::milliseconds(200));
    ! b8 J' F; l) U( s. Y
  13.        for (int i = 0; i < k; ++i) {
    . {& j) x) X" S- R( n: W( R
  14.            cout << i << " ";) I0 W% v- R  i
  15.       }& O2 A' H. |7 w0 `/ H1 I
  16.        cout << endl;+ t& S; w; B. P- b' j1 K
  17.        timed_mutex_.unlock();8 k* G* Y6 w6 \$ Q+ f
  18.   };4 ?! s% f: \$ E& k& _& Q$ {
  19.    std::thread threads[5];9 P, }0 R! p5 P
  20.    for (int i = 0; i < 5; ++i) {
    8 n$ k. l9 B0 h! m0 ^
  21.        threads[i] = std::thread(func1, 200);4 A3 ]) Z) q2 M. q" [/ v
  22.   }
    & t; ]5 \5 R$ L' n5 k7 S6 b! }
  23.    for (auto& th : threads) {
    . P: U. Q" l3 O2 c  @/ c
  24.        th.join();
    0 ~& |# P% j5 q% V6 x& F* M/ h
  25.   }
    # }, s2 s7 c0 }  R# y$ _
  26.    return 0;
    4 Q# e: \8 t% f0 H1 @( p
  27. }
复制代码

( f+ N3 b# G8 ~) @' Q
std::lock相关
这里主要介绍两种RAII方式的锁封装,可以动态的释放锁资源,防止线程由于编码失误导致一直持有锁。
c++11主要有std::lock_guard和std::unique_lock两种方式,使用方式都类似,如下:

  1. 4 F7 {2 X# @/ J$ U& S
  2. #include <iostream>
    ' B  e6 ?2 N/ S8 h. p6 n- Q+ ]
  3. #include <mutex>5 ?8 x+ D3 q. B/ i' g+ v- g  M
  4. #include <thread>
    . v6 Z0 m- O$ z: `6 b2 i0 S/ P
  5. #include <chrono>
    6 C* f& M. v$ H7 [( R
  6. : a% t9 ]2 ?/ Q' o, g
  7. using namespace std;
    + U7 ], z( _8 q5 B1 d  }
  8. std::mutex mutex_;
    ( e. {& J$ s: y: @& \+ s

  9. / a2 m  f) `5 U3 {9 r4 _: S+ n  \
  10. int main() {1 O0 Y$ q8 r+ \0 j% ]$ x
  11.    auto func1 = [](int k) {
    . R4 @  ^. u4 j$ O% T
  12.        // std::lock_guard<std::mutex> lock(mutex_);( `1 C. U3 O0 O
  13.        std::unique_lock<std::mutex> lock(mutex_);
    ' c! }  w  J7 L2 e
  14.        for (int i = 0; i < k; ++i) {
    3 G* |, n+ O3 Z
  15.            cout << i << " ";, N" s: a2 G, A, W- Z5 U' _1 E! w
  16.       }
    / H# u! ~$ W, J) w  W+ N
  17.        cout << endl;/ p/ S1 v5 N* m0 X  [8 Z
  18.   };0 `# K+ ]5 j, Y) C. ?: I
  19.    std::thread threads[5];: I) U# D) \& j
  20.    for (int i = 0; i < 5; ++i) {
    ; X5 m0 D5 i# O$ B) G; N
  21.        threads[i] = std::thread(func1, 200);: T8 o  L# D$ e+ D
  22.   }9 z7 [  _; O/ p( H0 P0 @
  23.    for (auto& th : threads) {
    8 a: S+ c# C9 W9 r! s* y4 O$ S! N
  24.        th.join();# Q/ L! g" W2 `
  25.   }+ n. A3 \' P9 V& k& Q
  26.    return 0;+ x- l% j7 K  y6 y0 \5 M
  27. }
复制代码
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来保护该变量啦。看一个计数器的代码:

  1. ) B5 n8 i( k- g8 ]
  2. struct OriginCounter { // 普通的计数器
    , T7 Y9 f0 M3 S8 P
  3.    int count;/ ~7 g: i% V* c$ A' c
  4.    std::mutex mutex_;+ ~" y$ K6 U: I
  5.    void add() {  p4 t2 A6 _" {: g
  6.        std::lock_guard<std::mutex> lock(mutex_);
    8 P) k: M7 K1 _+ l4 u1 _
  7.        ++count;' \; K" `" S. I- H& L
  8.   }4 y  i) K: D) r3 \

  9. 6 x! W. W3 c( |' v' ]
  10.    void sub() {
    0 k  R0 w: m, f% m2 [+ G
  11.        std::lock_guard<std::mutex> lock(mutex_);
    ! T. n0 }; P. `$ a, H
  12.        --count;
    + @$ r8 a: z/ Q7 ]8 y
  13.   }! s4 t- B' D, ?% G- v
  14. $ c1 z; A' B  t' `1 E+ i0 u4 M
  15.    int get() {7 O  F) J3 v# \* S1 i; D- x7 f8 K
  16.        std::lock_guard<std::mutex> lock(mutex_);
    3 p+ }) e- Z) X' Q" l! i
  17.        return count;
    3 ], i7 R2 n/ {8 u! w  E- O
  18.   }- J$ I( g/ o% Y; K: q
  19. };
    / p$ y3 t8 v5 U

  20. , E3 n* r% d" l% q
  21. struct NewCounter { // 使用原子变量的计数器: m* g* |; e  O! {! W
  22.    std::atomic<int> count;5 r7 J9 D6 I$ ?; Y
  23.    void add() {
    ( a9 _& P. c" ^" q3 E+ I
  24.        ++count;
    ; Q" ?# O# [7 w  H$ J2 H" y
  25.        // count.store(++count);这种方式也可以8 u; }/ r9 `" S& s2 C
  26.   }
    $ e4 S( I, A) g+ S0 ?! ~) F6 I
  27. 5 R- d9 |6 @# n# Z4 f
  28.    void sub() {
    - }4 y& N( v+ z5 T8 D1 z0 H6 e  c
  29.        --count;6 ^  Q+ |* N9 o$ C4 q
  30.        // count.store(--count);
    9 j- I5 ^. ~- _8 n' L9 Z
  31.   }+ ^( f: K$ e) F, \- g! j

  32. & P) H) d6 j, h/ N; R
  33.    int get() {/ r* b! K0 N" I, b2 y
  34.        return count.load();9 V' K9 k4 h6 {  T7 y
  35.   }) T$ Z9 Y2 b+ p
  36. };
复制代码
8 T8 v. n* S% x* w$ Q5 i
是不是使用原子变量更加方便了呢?
std::call_once相关
c++11提供了std::call_once来保证某一函数在多线程环境中只调用一次,它需要配合std:nce_flag使用,直接看使用代码吧:
  1. 6 \( J* ?" o* v
  2. std::once_flag onceflag;( b8 ~9 n& I. X
  3. ! L5 T8 H5 E* ^6 D5 W9 E
  4. void CallOnce() {- K8 {. Y; W( ]- C
  5.    std::call_once(onceflag, []() {# c6 _/ z, C9 ]; K9 R3 s
  6.        cout << "call once" << endl;; }/ }; `6 F5 q, ?; E
  7.   });4 b4 k6 J( c" |+ j9 N+ i
  8. }# u$ c" p' Q& I: t7 J) y  }: D

  9. $ g" \% g# B( B  Q. p/ [& R. Z
  10. int main() {
    2 T8 _. S8 A% |6 s
  11.    std::thread threads[5];
    0 U* F/ B: C* \8 }1 R! I7 T
  12.    for (int i = 0; i < 5; ++i) {9 @0 C+ _$ o  ~) o: _3 R  ?7 P
  13.        threads[i] = std::thread(CallOnce);( A8 d, t) r; y2 `9 x6 R
  14.   }9 z' s! n  [" q- b
  15.    for (auto& th : threads) {
    ! D  L" n9 F, I5 t. |  |2 K( Q
  16.        th.join();+ D2 S5 L, C4 ?! d! K0 f! {
  17.   }0 D& H0 z8 r* w6 M
  18.    return 0;
      u# C" S$ a  U* x
  19. }
复制代码
4 L* H& @8 Z* h4 Q
volatile相关
貌似把volatile放在并发里介绍不太合适,但是貌似很多人都会把volatile和多线程联系在一起,那就一起介绍下吧。
volatile通常用来建立内存屏障,volatile修饰的变量,编译器对访问该变量的代码通常不再进行优化,看下面代码:

  1. 4 _/ V0 N- a: y- j# @9 A, B( D
  2. int *p = xxx;  f  T4 ^' a3 T8 Q4 z
  3. int a = *p;$ n4 U0 W6 K2 I: V+ M
  4. 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:

  1. # ?2 h9 W% H5 J% e; @
  2. class CountDownLatch {7 y% D0 J+ g. y/ V, _
  3.    public:
    1 z7 G5 r8 V& x- E
  4.     explicit CountDownLatch(uint32_t count) : count_(count);
    3 U, B: L! V+ A  [

  5. # A# D! s: ~# T: ]/ Q
  6.     void CountDown() {) W! ^' p. y: T) \, q: M
  7.         std::unique_lock<std::mutex> lock(mutex_);
    7 p% v/ n0 C6 U% L) D* g2 |3 y
  8.         --count_;! s/ S; c: c7 u" _
  9.         if (count_ == 0) {
    / e! H$ ^! m# S! l4 r6 j8 d5 d5 R3 L+ @
  10.             cv_.notify_all();
    % t4 `7 M5 |3 S0 r2 r' ^( X& ~
  11.         }
    * C  }8 t( ?, O1 y4 C- K& u
  12.     }
    # P0 P; s  J/ |4 }$ @- U" x

  13. / P7 n% ]3 K% K/ ?2 j  \
  14.     void Await(uint32_t time_ms = 0) {
    4 q9 y; k) Z- X
  15.         std::unique_lock<std::mutex> lock(mutex_);4 g! n) F  r, ?5 H$ |3 V
  16.         while (count_ > 0) {
      b4 w8 Q, U: l' Y- m' I0 b- b$ _$ p
  17.             if (time_ms > 0) {
    4 \2 F; o6 [0 N" h! g+ @
  18.                 cv_.wait_for(lock, std::chrono::milliseconds(time_ms));8 e, F; Q4 C. W) V
  19.             } else {
    " T; X& ]  L6 F  D; u+ s& K1 x
  20.                 cv_.wait(lock);
    & ?! y9 t; @7 {2 }) g3 g  x
  21.             }, |* Z. g) ?  @2 o; o! h7 T: j  z
  22.         }6 x$ y2 ^! k' U( x% h
  23.     }" j$ K- B( n' E4 j) T) ^
  24. 9 V& o; ]' y5 h, j. q
  25.     uint32_t GetCount() const {( y! X2 W8 A- c$ Y
  26.         std::unique_lock<std::mutex> lock(mutex_);5 z+ |3 S( B% N$ H
  27.       return count_;
    - L7 O. C% n% }- e
  28.     }" x! T: {8 P8 i0 c
  29. 8 f9 c7 p/ e: N! \  N; ~) _; Q$ g& a
  30.    private:
    4 H4 x- k2 E0 {& p% @
  31.     std::condition_variable cv_;
    " ~6 `4 H. M/ R* N* Q
  32.     mutable std::mutex mutex_;
    ) R8 G# G  W: w) \6 q. p
  33.     uint32_t count_ = 0;- d" o# n# ~5 R: t1 e) z) x* ~7 z
  34. };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配合使用

  1. & n3 {8 P8 |8 O/ S
  2. #include <functional>
    1 y6 h- T, [9 r! k! u
  3. #include <future>( ]# `, c3 f  E+ f. M% W# h
  4. #include <iostream>9 k0 p2 e# V$ v2 K2 M/ m- J
  5. #include <thread>
    ; D" l# A2 `) o! h, ^% W

  6. ' A  s9 p% E3 ]7 |% Y4 l7 ]; U" U
  7. using namespace std;
      i2 v* a' @1 ?3 m- J

  8. - n6 R/ O. e+ @1 }; p3 P* I( a+ U
  9. void func(std::future<int>& fut) {
    - l. A0 L. y5 B/ d- W4 o
  10.     int x = fut.get();$ i$ X3 s- e, e; [4 T1 d' N0 _
  11.     cout << "value: " << x << endl;! q" J8 S! R4 o6 O3 {0 o
  12. }
    3 h8 X' h0 C4 D) W% ?& w% [
  13. ; [7 W, _# j8 i$ n
  14. int main() {% o) ]8 k- A: x7 Q" N, B' c1 [
  15.     std::promise<int> prom;
    # L1 [! r5 ~" _- Y
  16.     std::future<int> fut = prom.get_future();
    " ^( Y5 _# Q0 `0 q( L
  17.     std::thread t(func, std::ref(fut));
    2 V9 t- b6 R& }8 H3 A+ n1 h) Q
  18.     prom.set_value(144);: s5 Q! \6 a! D  g5 N
  19.     t.join();
    / h' f+ f- c* P. ~5 e# b
  20.     return 0;
    % W& o' y3 V& ~  j4 r
  21. }
复制代码
( b3 e$ p% [9 \; V/ ?: [
std::packaged_task与std::future配合使用

  1. - ^* I: x6 V% f* U
  2. #include <functional>- Q2 ]  Z+ U% Z8 [( T1 ^
  3. #include <future>1 ~4 [% b* N5 P+ u) n8 ^  E
  4. #include <iostream>; M2 U4 S0 K9 h- d8 z: o  F
  5. #include <thread>7 \4 e5 m3 _/ k$ X6 h
  6. # M; N' c$ Z7 O" h) ?1 n
  7. using namespace std;2 ?( i! i* P) o' o  E

  8. + B4 [+ b1 v2 Z
  9. int func(int in) {4 l' q- t) E5 k8 _; D
  10.     return in + 1;
    . V% x# {, r0 A, A. X, r9 O
  11. }
    % r% {9 N1 n1 L* N' ?3 S, O+ X' ]

  12. % A) j* a- e# ]* q
  13. int main() {
    5 B" J% v7 h& p6 s, A* U
  14.     std::packaged_task<int(int)> task(func);; g" M) j/ f/ _5 q9 N* G
  15.     std::future<int> fut = task.get_future();
    $ }" o. F$ ]) k8 o' z9 E; ?
  16.     std::thread(std::move(task), 5).detach();
    ' T0 L/ A9 B+ O7 n7 i
  17.     cout << "result " << fut.get() << endl;, I0 P; `5 V$ v" B+ w4 P! \
  18.     return 0;& @: j, i# O5 R$ a
  19. }
复制代码
' 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,看一段使用代码:

  1. 5 `; N. @# ~8 x0 [) e' i
  2. #include <functional>/ X0 D7 x* I" l- G
  3. #include <future>
    & D. q( w! N& g: @
  4. #include <iostream>
    / Z! K8 e; W6 f8 Y& v
  5. #include <thread>
    # e8 l. ~  F! p* F5 s& n1 a
  6. - ?: ~0 X1 `2 d% M- A5 C& O! {
  7. using namespace std;& M0 w9 M8 r* G& {& G# h9 t
  8. % s$ y4 V" A% V
  9. int func(int in) { return in + 1; }
    " v& J# F+ y' h; J  {& v( |
  10. , T; D6 ~+ L) J8 Y; s$ n
  11. int main() {, V8 V2 `7 _0 a- a; m
  12.     auto res = std::async(func, 5);) k5 m8 C) S: e
  13.     // res.wait();1 m/ ?( ^% G9 ~( D  Q
  14.     cout << res.get() << endl; // 阻塞直到函数返回) }5 T) E8 i. t
  15.     return 0;& N$ ?0 V  I$ {% V0 j) \
  16. }
复制代码
6 E6 }/ u/ G3 M* r5 b( I1 {
使用async异步执行函数是不是方便多啦。
async具体语法如下:

  1. 4 C0 p! x5 g3 b, d3 r6 I; P
  2. async(std::launch::async | std::launch::deferred, func, args...);
复制代码
+ \. B: L; p$ ~) ^. ]$ ~' C
第一个参数是创建策略:
  • std::launch::async表示任务执行在另一线程
  • std::launch::deferred表示延迟执行任务,调用get或者wait时才会执行,不会创建线程,惰性执行在当前线程。
    6 d# x6 `2 [+ T# u
如果不明确指定创建策略,以上两个都不是async的默认策略,而是未定义,它是一个基于任务的程序设计,内部有一个调度器(线程池),会根据实际情况决定采用哪种策略。
若从 std::async 获得的 std::future 未被移动或绑定到引用,则在完整表达式结尾, std::future的析构函数将阻塞直至异步计算完成,实际上相当于同步操作:
  1. ! ^+ L5 B) g8 y
  2. std::async(std::launch::async, []{ f(); }); // 临时量的析构函数等待 f()
    3 T# J. A9 G  O+ s$ b6 R
  3. 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。

  1. ) ?. z0 G1 |5 w; j% q1 E
  2. template <typename F, typename... Args>% R: O; [6 H) a: s) I& ~9 m1 C! t
  3. inline auto ReallyAsync(F&& f, Args&&... params) {2 v* z4 {# G1 ^+ s; H
  4.     return std::async(std::launch::async, std::forward<F>(f), std::forward<Args>(params)...);. d+ x0 v9 _+ o7 b, S6 L$ m( R
  5. }
复制代码

' 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
赞 收藏 1 评论1 发布时间:2020-5-21 16:17

举报

1个回答
yklstudent 回答时间:2020-5-21 20:31:43
纯粹为了C艹,顶下
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版