电子产业一站式赋能平台

PCB联盟网

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

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

[复制链接]

890

主题

890

帖子

9228

积分

高级会员

Rank: 5Rank: 5

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

fj44uke5reg64013335717.gif

fj44uke5reg64013335717.gif

/ A; n' p/ t& v+ M点击上方蓝色字体,关注我们( B$ `9 J' [7 J" G# D& e* Y2 t
9 ?" q0 W5 f8 ?4 `; m
条件变量和互斥锁通常一起使用,以保证对共享资源的安全访问。
2 T  s6 w+ P( g/ ]0 C% H; \
2 W  r2 P* ]9 M! f; E. A7 \# X

ru2tmh15ug364013335817.png

ru2tmh15ug364013335817.png

7 t2 _2 J) w2 j/ p7 _0 a0 [5 |0 ?通过条件变量,线程可以避免忙等待(busy-waiting),从而提高效率。
7 J/ x" X* N+ y) W, y
8 F6 y9 u, Y6 H- `! p) L条件变量使得线程可以通过以下方式同步:( R- \9 d& F6 t( J5 h+ e- x+ J6 B( s
  • 等待某个条件满足:当某个线程在等待某个条件时,它可以进入阻塞状态,并释放持有的互斥锁,以允许其他线程操作共享资源。
  • 通知条件满足:当其他线程改变了共享资源的状态,且满足了等待线程的条件,它可以通过发送信号(signal)来通知那些正在等待的线程,使它们被唤醒并继续执行。
    : s6 ?  W# g6 p1 e; e

    & b* q. [' j9 D$ H条件变量通常与互斥锁结合使用,因为在检查或修改某些共享资源时,需要保护这些资源的并发访问,防止竞争条件。+ G) F+ t& v& |( U1 v! ?

    ( p$ V4 X! l) N% O7 _. N互斥锁负责保护共享资源,条件变量负责在线程间传递状态信息。
    " \& w/ f5 e7 t. _3 O; t
    # Z, I  d2 ]# F8 x8 U6 o具体步骤如下:
    - o/ G) {8 I  ~! u) g5 O7 {1 t
  • 线程通过互斥锁访问共享资源。
  • 当条件未满足时,线程通过条件变量进入等待,并释放互斥锁,允许其他线程继续操作资源。
  • 其他线程修改共享资源并发出条件满足的信号,通知条件变量唤醒等待线程。
  • 被唤醒的线程重新获得互斥锁并检查条件是否满足,如果满足则继续执行,否则继续等待。
    ; A4 E7 }& M! p) s6 p. a
    ( ]1 s, s8 J7 b( r7 b
    条件变量的使用步骤:
    # ]8 C3 \" ?+ p0 L; {* v
  • 初始化条件变量和互斥锁。
  • 在线程中使用互斥锁对共享资源进行保护。
  • 如果条件未满足,调用pthread_cond_wait()使线程进入阻塞状态,同时释放互斥锁,等待其他线程通知条件满足。
  • 其他线程修改共享资源后,调用pthread_cond_signal()或pthread_cond_broadcast()通知等待线程。
  • 被唤醒的线程重新获得互斥锁并继续检查条件。
    ) z: D" B8 u' }- k# W
    + ?. {  W/ P5 B- `
    1. [2 L  n; Y9 h% N; ]7 s
    条件变量的初始化和销毁, C; l1 |4 A  B7 @( X
    条件变量使用pthread_cond_t数据类型来表示,和互斥锁类似,条件变量在使用前必须初始化。
    $ a9 U! y8 A# ?; j% [* B2 Y% n9 \" s! r# F& M
    可以通过两种方式进行初始化:
    9 p+ t$ o- u8 [, A: m
    % G5 H. F- c4 J9 F% L+ [1、使用宏PTHREAD_COND_INITIALIZER,类似于互斥锁的静态初始化方式:
    ! H/ \1 v; a2 C
    5 O/ l* f$ I6 i( t5 j3 k- N
  • pthread_cond_t cond = PTHREAD_COND_INITIALIZER;. B9 X" f6 {  L5 z
    2、使用pthread_cond_init()函数动态初始化条件变量:
    9 F. O4 M) y3 f' @: q
    2 I0 K* E5 q% }! G4 b0 f( `
  • pthread_cond_init(&cond, NULL); // attr 为 NULL 时使用默认属性$ N) R6 v+ o* O& D+ b5 F6 x
    参数% y8 U: C. Q1 z+ _9 r
  • cond: 指向待初始化的条件变量对象,类型为 pthread_cond_t*。
  • attr: 条件变量的属性,可以为 NULL 表示使用默认属性。2 d6 I, _4 r5 E9 z2 A; Q1 u
    3 P5 B/ R- H) \$ O
    . U2 K' P6 }. H4 l) o
    返回值
    $ ]( N# m' {9 A; {1 Z4 ~
  • 成功返回 0;
  • 失败返回非零错误码,如 EINVAL(无效属性)。
    + }3 k8 m; k% Q, y. R; U

    3 e8 O/ k9 p( B# ~当不再使用条件变量时,应使用pthread_cond_destroy()进行销毁,以释放系统资源,销毁前需要确保没有线程在等待该条件变量。! m8 z, o2 M* P( _! I9 g
    8 R& m- M0 R' C) t3 p9 N; p% y
  • pthread_cond_destroy(&cond);
    5 s7 _! m! S- R8 e# U

    ( ^! h1 h# b, F' ^- ?# y6 G参数
    ! [' s0 J0 C  ]" J
  • cond: 指向待销毁的条件变量对象。
    ' F1 j& c; ~6 K+ n- `$ B
    4 v, O* g7 X! N: z$ Z. m% @, v
    0 n+ m0 I" E8 n, m. K
    返回值' `8 D! A8 l, f% Y! p
  • 成功返回 0;
  • 失败返回非零错误码,如 EBUSY(有线程在等待该条件变量)。, f  V5 x0 P8 ~
    0 {( u( A2 S! l8 @( q, w

    - |8 [3 c2 K/ I注意事项5 b: o( P2 u' X% e; z
  • 只能销毁已经初始化的条件变量。
  • 条件变量销毁时,不能有线程在等待它,否则将导致未定义行为。; C3 a" E0 _6 h1 R

    5 u$ _# d$ _& u% q. p2: t' e7 \8 j- r0 s1 h% `2 K' S
    发送信号和等待条件变量( i. w, M) @' C) t; Q5 |; T/ L
    条件变量的核心功能就是发送信号和等待条件。  h& s* M: ^. H: o' B
    6 _. n. R$ m4 ?) s
    在多线程程序中,线程通过pthread_cond_wait()等待条件,而其他线程通过pthread_cond_signal()或pthread_cond_broadcast()发出信号。
    - D! o0 M! u" l2 f: A5 p7 s0 P4 k! K2 G
    pthread_cond_wait():使线程等待条件满足,并在等待期间释放互斥锁。该函数会阻塞调用线程,直到另一个线程调用 pthread_cond_signal() 或 pthread_cond_broadcast() 通知该条件变量。
    * y# h8 ]9 ?, S" F7 H& z* B% C
    & p# ?( C. g9 C0 O
  • int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
    3 p6 T4 N9 P( c9 l
    * w9 ?. f( S, l. c4 G2 ]0 m
    参数4 ?, r) h" M& |! h
  • cond: 条件变量指针,指向 pthread_cond_t 类型的条件变量。
  • mutex: 互斥锁指针,指向 pthread_mutex_t 类型的互斥锁。在线程进入等待状态时,pthread_cond_wait 会自动释放该互斥锁,并在线程被唤醒时重新加锁。5 R1 u- {5 ~% Y. j, m
    0 H$ E4 ~( g) u1 D+ f4 N
    返回值
    # }- K- U" D, k. W6 Z' p( S+ h4 r, g
  • 成功返回 0;
  • 失败返回非零错误码。
    - U* b8 Z& G4 g3 |: B/ s

    + B! o. a! P6 }  l+ }

    + s+ c# P$ u. r, B; L" {注意事项$ z9 k# {4 l1 `" |% m
  • 在调用 pthread_cond_wait() 之前,必须先锁住互斥锁,以避免条件检查和等待之间的竞争。
  • 线程被唤醒时,会重新锁住传入的互斥锁。- w+ `2 `: X* }9 Z

    & Z& {" G" ]5 O5 |) N

    $ v+ A# s2 C* M  r: B( H. y& epthread_cond_signal():用于通知至少一个等待该条件变量的线程,使其从 pthread_cond_wait() 的阻塞状态中唤醒。如果没有线程在等待条件变量,该信号会被丢弃。
      a9 X. L$ i' }2 P! a& }4 W; j: l/ O* W" U& `0 K9 X
  • int pthread_cond_signal(pthread_cond_t *cond);
    + P5 N7 b5 G' n; d3 t! t

    4 p2 X' U2 r4 `3 p! j& V参数
    ( F+ j" j& v  H( @& B2 k# c
  • cond: 条件变量指针,指向 pthread_cond_t 类型的条件变量。* p$ p6 T- i: m/ j5 y+ S$ t: y
    4 O6 V( i: O' C

    & O* H3 w! R3 J& x# T6 X8 ^返回值9 ?$ p" F. M5 V) o$ c  r# T* H$ }
  • 成功返回 0;
  • 失败返回非零错误码。
    : m! U, d# C. r1 q+ a
    4 W- X% q$ R4 ~5 y0 h: W. J

    ) Q; j4 V8 z/ ?' o+ ^注意事项
    ( h+ L7 |. I! A, Z* ^4 K, u
  • 该函数只能唤醒一个等待的线程。
  • 如果没有线程等待,信号不会累积,因此没有线程在等待时,调用该函数不会有任何效果。
    $ @$ R" O$ H1 m; n% r

    ! j" [' i4 F3 {8 E9 N- `+ ~  |pthread_cond_broadcast():用于唤醒所有等待该条件变量的线程。适用于需要唤醒多个线程的场景。
    0 m: U6 G4 g6 w' r1 s
    ( B# R3 C, s! u- O! \
  • int pthread_cond_broadcast(pthread_cond_t *cond);
    7 b0 K  z3 s8 G+ D6 P1 P$ R5 g
    " r! @! a9 ~: N* W3 X) d
    参数- p( S% w0 a4 A/ T
  • cond: 条件变量指针,指向 pthread_cond_t 类型的条件变量。
    ; ?9 C, o4 L5 Z: e; n

      @; h8 ?5 t+ P7 N返回值
    : H) ]0 b9 M( K3 n6 ?- H- R: _- i6 y5 F
  • 成功返回 0;
  • 失败返回非零错误码。# f' Z9 t: o: n! ?

      P3 _0 T+ X. R) J. ]4 K' {, L假设有多个消费者线程在等待产品,如果使用pthread_cond_signal(),只有一个线程会被唤醒。: b0 V: R8 O% f3 o0 Q: H  \
    ! B0 s' w: y- m
    如果我们希望所有消费者都被唤醒,则使用pthread_cond_broadcast()。
    , s5 ~" T7 Z9 A$ v8 C) c$ D$ |# P8 c7 \! Z, ^9 F
    但一般来说,pthread_cond_signal()更高效,除非确实需要所有线程都被唤醒。
    8 u5 e7 [' c8 }2 C! d/ }& f
    ; C4 s1 J# f5 O0 ~
  • pthread_cond_signal(&cond);   // 唤醒一个线程pthread_cond_broadcast(&cond); // 唤醒所有等待的线程: n( f- `% R) e3 v
    3
    ! [' h5 x; H* h/ u2 d条件变量中的判断条件' b1 {$ ~. O: t+ R! I5 I
    在使用条件变量时,通常涉及某个共享变量(如上例中的buffer)。
    ) Y7 r9 o% O  U6 N8 X- a1 ~
    5 b" a0 ?. |* y0 D& T6 X1 J在检测条件时,必须使用while循环而非if语句。这是因为:
    ) |7 ^  B1 j0 ^" p* ]1 _3 X6 S
  • 虚假唤醒:线程可能被意外唤醒,但条件仍不满足。用while循环可以确保条件再次检查,而不是立即继续执行。
  • 竞争条件:多个线程可能同时被唤醒,但只有一个线程会成功获取锁并修改条件状态。其他线程必须再次等待。8 m5 L  F$ M* _  d
    4 P' C* k- d  M3 d
    这样设计能确保线程在条件不满足时不会继续执行,从而避免竞争条件带来的问题。% l+ y; f3 S) z& n# J3 M
    + ?4 @5 q) H) N
  • pthread_mutex_lock(&mutex);while (buffer == 0) {    pthread_cond_wait(&cond, &mutex);}
    - ]9 u3 B/ v! J, {以下示例展示了生产者-消费者模型,其中生产者线程和消费者线程通过条件变量进行同步:* O$ E# j6 p: u8 S! _" a

    ! t: D+ H# }! \
  • pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;pthread_cond_t cond = PTHREAD_COND_INITIALIZER;int buffer = 0;  // 缓冲区,0表示空,1表示有产品3 C; w7 y: O0 i3 c# c4 y
    void *producer(void *arg) {    pthread_mutex_lock(&mutex);  // 加锁    while (buffer == 1) {        pthread_cond_wait(&cond, &mutex);  // 缓冲区满,等待    }    buffer = 1;  // 生产产品    printf("Produced an item
    # q, o5 q, T) T, H1 q, M");    pthread_cond_signal(&cond);  // 通知消费者    pthread_mutex_unlock(&mutex);  // 解锁    return NULL;}
    8 ?# k9 f, {8 ?7 B  {! Y1 Yvoid *consumer(void *arg) {    pthread_mutex_lock(&mutex);  // 加锁    while (buffer == 0) {        pthread_cond_wait(&cond, &mutex);  // 缓冲区空,等待    }    buffer = 0;  // 消费产品    printf("Consumed an item$ f: d9 i% K+ p+ u
    ");    pthread_cond_signal(&cond);  // 通知生产者    pthread_mutex_unlock(&mutex);  // 解锁    return NULL;}+ Z& R' h- h4 ?% X/ t
    int main() {    pthread_t prod, cons;
    7 s& b0 W& B' C% Z    // 创建生产者和消费者线程    pthread_create(&prod, NULL, producer, NULL);    pthread_create(&cons, NULL, consumer, NULL);6 l' ?2 w. ?' o! F, p
        // 等待线程执行完毕    pthread_join(prod, NULL);    pthread_join(cons, NULL);
    8 H- A2 o+ }& M3 K4 T! A; e7 F    // 销毁互斥锁和条件变量    pthread_mutex_destroy(&mutex);    pthread_cond_destroy(&cond);
    0 U& O: L3 Q; B: O4 ]    return 0;}
    0 Z  T/ k  v) Z& FLinux中的条件变量是线程同步的强大工具,允许线程等待特定条件满足后再执行操作,避免了无效的忙等待。6 ]$ Q+ {# H6 F: K5 b) D* C4 f% d2 e

    ) ^  R# t- q  k- A通过与互斥锁协作,条件变量可以有效地协调线程之间对共享资源的访问,保证并发环境下的安全性和效率。
    8 @( N7 f$ c- W. E
  • 条件变量与互斥锁结合使用:条件变量用于等待和通知条件变化,互斥锁则用于保护共享资源的访问。
  • 条件变量不保存状态:如果没有线程在等待条件变量,信号会丢失。
  • pthread_cond_wait()应放在循环中:因为多个线程可能竞争资源,因此从 pthread_cond_wait() 返回后应重新检查条件。
  • 选择合适的通知方式:pthread_cond_signal() 唤醒一个等待线程,pthread_cond_broadcast() 唤醒所有等待线程。4 Z; x1 y7 i% I( `; C- s( {

    9 Y) X" o5 Z% a5 V1 q

    a2ir40us24n64013335917.jpg

    a2ir40us24n64013335917.jpg

    + x: E6 |1 j& T5 U  W

    nzdnbh02wuh64013336017.gif

    nzdnbh02wuh64013336017.gif

    $ s. h3 @  t  v; |" _" N" \点击阅读原文,更精彩~
  • 回复

    使用道具 举报

    发表回复

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

    本版积分规则

    关闭

    站长推荐上一条 /1 下一条


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