|

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
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 tpthread_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$ Ncond: 指向待初始化的条件变量对象,类型为 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 pcond: 指向待销毁的条件变量对象。! 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 tint 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 ccond: 条件变量指针,指向 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( Spthread_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+ R8 Q+ V/ N( G; u0 ]) G& V
a2ir40us24n64013335917.jpg
/ N" K: ~# C' K) N
nzdnbh02wuh64013336017.gif
6 X6 s3 I4 U( q5 \) o* ]+ N点击阅读原文,更精彩~ |
|