|

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
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- Npthread_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 ]" Jcond: 指向待销毁的条件变量对象。
' F1 j& c; ~6 K+ n- `$ B4 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 Oint 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# ccond: 条件变量指针,指向 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+ a4 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
+ x: E6 |1 j& T5 U W
nzdnbh02wuh64013336017.gif
$ s. h3 @ t v; |" _" N" \点击阅读原文,更精彩~ |
|