电子产业一站式赋能平台

PCB联盟网

搜索
查看: 74|回复: 0
收起左侧

嵌入式Linux:线程同步(条件变量)

[复制链接]

853

主题

853

帖子

8351

积分

高级会员

Rank: 5Rank: 5

积分
8351
发表于 2025-1-14 08:00:00 | 显示全部楼层 |阅读模式

fj44uke5reg64013335717.gif

fj44uke5reg64013335717.gif
4 }2 H- D" X2 g" n, i  U
点击上方蓝色字体,关注我们
! Z/ j" I9 T* ]- v6 n. T# D9 J# D/ k5 Q6 i' H
条件变量和互斥锁通常一起使用,以保证对共享资源的安全访问。  Q" K' w8 l; K5 R
6 m9 Q) H% u% _7 w# U+ l2 U

ru2tmh15ug364013335817.png

ru2tmh15ug364013335817.png
1 ?5 k- o+ \! v; T
通过条件变量,线程可以避免忙等待(busy-waiting),从而提高效率。: J& A! E  B# X8 K  G

3 Q! i& ?( W( |# O" w( X条件变量使得线程可以通过以下方式同步:
$ j" I1 a) r$ A: @9 S" Z4 S8 z
  • 等待某个条件满足:当某个线程在等待某个条件时,它可以进入阻塞状态,并释放持有的互斥锁,以允许其他线程操作共享资源。
  • 通知条件满足:当其他线程改变了共享资源的状态,且满足了等待线程的条件,它可以通过发送信号(signal)来通知那些正在等待的线程,使它们被唤醒并继续执行。6 v% {; G$ u$ f. v' I4 m
    8 F9 k" R4 Q* Z5 H% m* c$ _
    条件变量通常与互斥锁结合使用,因为在检查或修改某些共享资源时,需要保护这些资源的并发访问,防止竞争条件。& j! }( ~; d% q& j. @) F4 c+ M/ B

    7 Z) n/ [# N9 T( t& B. T+ [& l互斥锁负责保护共享资源,条件变量负责在线程间传递状态信息。/ _+ n8 S( h4 E

    4 x8 t, ~4 i( K具体步骤如下:& ^& j  J( Q8 L/ k
  • 线程通过互斥锁访问共享资源。
  • 当条件未满足时,线程通过条件变量进入等待,并释放互斥锁,允许其他线程继续操作资源。
  • 其他线程修改共享资源并发出条件满足的信号,通知条件变量唤醒等待线程。
  • 被唤醒的线程重新获得互斥锁并检查条件是否满足,如果满足则继续执行,否则继续等待。1 f) t* I: y9 F! K! G
    3 y8 D* r% [4 k/ _' _* i
    条件变量的使用步骤:
    4 s( o- Z* J& v, X6 b' G
  • 初始化条件变量和互斥锁。
  • 在线程中使用互斥锁对共享资源进行保护。
  • 如果条件未满足,调用pthread_cond_wait()使线程进入阻塞状态,同时释放互斥锁,等待其他线程通知条件满足。
  • 其他线程修改共享资源后,调用pthread_cond_signal()或pthread_cond_broadcast()通知等待线程。
  • 被唤醒的线程重新获得互斥锁并继续检查条件。) t# S$ |- N( a' p! {$ {, z& m; R
      _; s9 g- c7 O9 c9 I7 L
    1* f# y$ G# s- S# T' f
    条件变量的初始化和销毁! ^: k5 j. v8 s6 a9 p6 Z
    条件变量使用pthread_cond_t数据类型来表示,和互斥锁类似,条件变量在使用前必须初始化。
    ' A# x1 b7 P) b; }* n' [) `! s% p' T; @9 K9 d
    可以通过两种方式进行初始化:8 L- {0 P4 H; e5 A. |

    + C( O; [3 l' n4 \# o; t7 G; l6 ?+ x# t1、使用宏PTHREAD_COND_INITIALIZER,类似于互斥锁的静态初始化方式:
    % h  m1 T* r3 s. w) p4 Y
    # L  P- a9 n: n3 t
  • pthread_cond_t cond = PTHREAD_COND_INITIALIZER;1 [1 A) l; |6 G" B/ h
    2、使用pthread_cond_init()函数动态初始化条件变量:$ k- Y6 R7 G5 n. L
    9 b+ e' R6 Y0 w1 z
  • pthread_cond_init(&cond, NULL); // attr 为 NULL 时使用默认属性
    " e! A9 [; @% ^, `  R2 [* |参数
    $ f6 r5 L3 d' r$ j- J$ N
  • cond: 指向待初始化的条件变量对象,类型为 pthread_cond_t*。
  • attr: 条件变量的属性,可以为 NULL 表示使用默认属性。
    8 x3 E" ?( U3 h; p  E( P" ?% k* o) }

    ; _7 u, k5 W" K: D+ z" Y
    : b& z. `( ~" \2 k' f2 ^0 u
    返回值5 f' W5 k' ~% [5 R2 c! R( O
  • 成功返回 0;
  • 失败返回非零错误码,如 EINVAL(无效属性)。
    . P; n' v( R7 ~9 u+ U2 L% d  ~

    , [# p% L2 i( Q2 ~. K) B: P当不再使用条件变量时,应使用pthread_cond_destroy()进行销毁,以释放系统资源,销毁前需要确保没有线程在等待该条件变量。: K& k0 }( @% S! K' G1 C. T" q, K

    % W! k: z2 B7 \
  • pthread_cond_destroy(&cond);" s$ f2 d! ?" K; U" F! Q
    , Q- k$ W1 Z* n0 G; F9 }/ C1 o6 {+ Z6 K
    参数
    ( u" d, Q1 ~3 [- Y7 B4 p
  • cond: 指向待销毁的条件变量对象。! t% w5 v* O' o% `4 g
    ' j# i0 q# l7 a* O1 V7 g
    ) X/ S, z3 \- r( E
    返回值
    + B% a- A; D) c2 {6 b
  • 成功返回 0;
  • 失败返回非零错误码,如 EBUSY(有线程在等待该条件变量)。
    # Q8 m5 i2 Z; J  P! P

    + [/ h: P$ _! {8 k& K: z# W
    ' P. h9 P( X. x% a, \' T0 k
    注意事项7 r9 ~, ]3 O0 X- E- Z) D2 A" k# i
  • 只能销毁已经初始化的条件变量。
  • 条件变量销毁时,不能有线程在等待它,否则将导致未定义行为。" E( ?& A( ^0 ?

    , L8 j4 H( a& b0 U! C! W2
    ! w9 w7 J* D% o- C2 x发送信号和等待条件变量% @7 g/ ^& x% l
    条件变量的核心功能就是发送信号和等待条件。2 z, o! e/ ?+ R0 r' R% |: C1 u
    7 m3 n' d1 |9 @. V7 `' y
    在多线程程序中,线程通过pthread_cond_wait()等待条件,而其他线程通过pthread_cond_signal()或pthread_cond_broadcast()发出信号。% K* i$ M+ K& @7 k/ V
    ) M5 i3 {! w4 s( S2 I9 h
    pthread_cond_wait():使线程等待条件满足,并在等待期间释放互斥锁。该函数会阻塞调用线程,直到另一个线程调用 pthread_cond_signal() 或 pthread_cond_broadcast() 通知该条件变量。
    , r& H6 o0 N5 r- b1 q; v4 M% G5 m4 U: s* m( x  W
  • int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
      [2 C# L5 a1 M8 K. _4 i

    ; T+ H' b  G. `3 m3 _参数: g! ~2 u" n+ T" A7 t2 ]. I
  • cond: 条件变量指针,指向 pthread_cond_t 类型的条件变量。
  • mutex: 互斥锁指针,指向 pthread_mutex_t 类型的互斥锁。在线程进入等待状态时,pthread_cond_wait 会自动释放该互斥锁,并在线程被唤醒时重新加锁。
    + C/ q3 J7 ^6 {: q' Z5 x

    $ ?0 l  h" k) H( F5 e返回值
    6 |' p2 V( N2 w1 y. `
  • 成功返回 0;
  • 失败返回非零错误码。
    1 L- _! u8 U; K% D

    . S1 L8 p! J% K5 Q/ P

    * Q# Q1 W' G: d- u$ a- }. y2 R! \注意事项
    3 w! i. ^8 O$ t0 W1 k, P
  • 在调用 pthread_cond_wait() 之前,必须先锁住互斥锁,以避免条件检查和等待之间的竞争。
  • 线程被唤醒时,会重新锁住传入的互斥锁。
    8 K9 t* p- y3 D- a/ t
    / `% L5 K8 Y0 w# l: W
      k: X3 C, R; h& w  {# h
    pthread_cond_signal():用于通知至少一个等待该条件变量的线程,使其从 pthread_cond_wait() 的阻塞状态中唤醒。如果没有线程在等待条件变量,该信号会被丢弃。) R  e$ q& P, s4 r! d$ ~
    1 G6 r( s8 e4 b# ^, \! M4 q& t
  • int pthread_cond_signal(pthread_cond_t *cond);
    : b- c( _. \# o/ O9 `8 D' t! \$ B' l

    3 J  U1 F4 _) g参数
    ; }2 ?" [: A# r1 d! ~
  • cond: 条件变量指针,指向 pthread_cond_t 类型的条件变量。+ G3 F0 @+ |) F- X' }

    9 Z( V" V6 F& u4 ^

    6 R3 W" X% V# V" @5 L: p' X返回值% ?$ W- N' ]5 k, W
  • 成功返回 0;
  • 失败返回非零错误码。
    ' }* j2 s# e) U! c! r

    7 w8 W# D) A) m) m. X

    % Z) h- \3 O! {) ?' E2 j注意事项; {9 p% x" E/ e
  • 该函数只能唤醒一个等待的线程。
  • 如果没有线程等待,信号不会累积,因此没有线程在等待时,调用该函数不会有任何效果。8 A7 s& T: s+ I$ r) b" A
    0 R# s* C6 Z7 n, d' I+ l" i
    pthread_cond_broadcast():用于唤醒所有等待该条件变量的线程。适用于需要唤醒多个线程的场景。9 t& B$ p3 j' D, ]0 R+ R2 k

    ! \9 c% ~) ?! Y' H" D4 g4 t
  • int pthread_cond_broadcast(pthread_cond_t *cond);
    5 y- `" _! c; C! ?( B6 J! a
    ; M% ?/ ^% ?3 S
    参数
    0 k) p  N9 ~# N0 N7 \& x- }+ c6 m5 c
  • cond: 条件变量指针,指向 pthread_cond_t 类型的条件变量。1 i3 O: b% u0 @3 Y. N

    / O1 Y5 ^" q3 {4 [6 C+ y/ r. e返回值
    + P' T$ D9 F0 \, H6 B! A
  • 成功返回 0;
  • 失败返回非零错误码。
    4 W: f. H2 ]3 S. o
    - B2 L$ b  @1 C$ s0 W/ Y$ W: @! Z
    假设有多个消费者线程在等待产品,如果使用pthread_cond_signal(),只有一个线程会被唤醒。
    ( Y, b7 [2 J' {! `
    : u  B9 k: u$ a, x) _- x! V9 @' G如果我们希望所有消费者都被唤醒,则使用pthread_cond_broadcast()。
    " X8 }' U% D+ T6 r0 A- _/ T" I" @6 w! j( ~% h- r6 t! D4 \1 H; @
    但一般来说,pthread_cond_signal()更高效,除非确实需要所有线程都被唤醒。, _' f5 G, P# W1 D0 D, g" f: i

    7 K# s$ s* j' B! D" Q/ L. h4 s" F4 e( S
  • pthread_cond_signal(&cond);   // 唤醒一个线程pthread_cond_broadcast(&cond); // 唤醒所有等待的线程
    : C. q# Q7 U$ x. N7 Q3 V0 ]" S3
    7 r0 t  }+ e8 ]7 U条件变量中的判断条件
    8 J- s/ G  `, F0 d# P在使用条件变量时,通常涉及某个共享变量(如上例中的buffer)。
    4 @; E" W6 h' [( w" p* [" E* S! p5 k* e; I
    在检测条件时,必须使用while循环而非if语句。这是因为:
    $ W1 `1 e5 O3 A6 [$ b, X
  • 虚假唤醒:线程可能被意外唤醒,但条件仍不满足。用while循环可以确保条件再次检查,而不是立即继续执行。
  • 竞争条件:多个线程可能同时被唤醒,但只有一个线程会成功获取锁并修改条件状态。其他线程必须再次等待。
    . f9 t9 p( ?" f* n2 r  H7 J

    0 L4 s5 b1 Y" T- d. y这样设计能确保线程在条件不满足时不会继续执行,从而避免竞争条件带来的问题。
    4 B6 ]: C9 P2 i: g) h
    - G" q0 n+ }' ?! E' V" ]
  • pthread_mutex_lock(&mutex);while (buffer == 0) {    pthread_cond_wait(&cond, &mutex);}8 D; W) C$ u; n8 \
    以下示例展示了生产者-消费者模型,其中生产者线程和消费者线程通过条件变量进行同步:
    1 j% s6 T! w2 B7 K4 S, V+ y% G+ C
  • pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;pthread_cond_t cond = PTHREAD_COND_INITIALIZER;int buffer = 0;  // 缓冲区,0表示空,1表示有产品
    * }0 I3 W) s: P0 F& Qvoid *producer(void *arg) {    pthread_mutex_lock(&mutex);  // 加锁    while (buffer == 1) {        pthread_cond_wait(&cond, &mutex);  // 缓冲区满,等待    }    buffer = 1;  // 生产产品    printf("Produced an item
    " F' q4 @: a, v9 N' N$ m");    pthread_cond_signal(&cond);  // 通知消费者    pthread_mutex_unlock(&mutex);  // 解锁    return NULL;}
    , l8 _& f7 v- m6 y: E! Z$ avoid *consumer(void *arg) {    pthread_mutex_lock(&mutex);  // 加锁    while (buffer == 0) {        pthread_cond_wait(&cond, &mutex);  // 缓冲区空,等待    }    buffer = 0;  // 消费产品    printf("Consumed an item
    ! k# q! ~" }$ P, M- k6 H. l- p");    pthread_cond_signal(&cond);  // 通知生产者    pthread_mutex_unlock(&mutex);  // 解锁    return NULL;}
    " Z; r) }( v( K0 K0 H: y3 z; D- J7 Tint main() {    pthread_t prod, cons;
    , c# @- x$ l" r8 i3 l2 H    // 创建生产者和消费者线程    pthread_create(&prod, NULL, producer, NULL);    pthread_create(&cons, NULL, consumer, NULL);4 M0 P! ]0 q0 v$ p1 i
        // 等待线程执行完毕    pthread_join(prod, NULL);    pthread_join(cons, NULL);
    7 m9 J4 }  p5 a    // 销毁互斥锁和条件变量    pthread_mutex_destroy(&mutex);    pthread_cond_destroy(&cond);8 ?; b+ E' G. R5 a
        return 0;}8 K% U8 ^4 V. D1 Q/ `
    Linux中的条件变量是线程同步的强大工具,允许线程等待特定条件满足后再执行操作,避免了无效的忙等待。
    + g& I/ f% c* G. W
    " {; u! N2 {% [) N1 `2 E通过与互斥锁协作,条件变量可以有效地协调线程之间对共享资源的访问,保证并发环境下的安全性和效率。
    1 X3 x3 Z% N( i$ I; E
  • 条件变量与互斥锁结合使用:条件变量用于等待和通知条件变化,互斥锁则用于保护共享资源的访问。
  • 条件变量不保存状态:如果没有线程在等待条件变量,信号会丢失。
  • pthread_cond_wait()应放在循环中:因为多个线程可能竞争资源,因此从 pthread_cond_wait() 返回后应重新检查条件。
  • 选择合适的通知方式:pthread_cond_signal() 唤醒一个等待线程,pthread_cond_broadcast() 唤醒所有等待线程。
    ! l7 N; I% s6 Q+ R
    8 Q+ V/ N( G; u0 ]) G& V

    a2ir40us24n64013335917.jpg

    a2ir40us24n64013335917.jpg

    / N" K: ~# C' K) N

    nzdnbh02wuh64013336017.gif

    nzdnbh02wuh64013336017.gif

    6 X6 s3 I4 U( q5 \) o* ]+ N点击阅读原文,更精彩~
  • 回复

    使用道具 举报

    发表回复

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则


    联系客服 关注微信 下载APP 返回顶部 返回列表