|
tg3x2f4oox464014949133.gif
! [6 f6 S7 e7 s+ P ^4 u& J, X
点击上方蓝色字体,关注我们
' @; H; H% P$ z2 p' R0 \% F7 l7 G w5 ^+ Y9 @) P
读写锁相比互斥锁(mutex)或自旋锁(spinlock)具有更高的并行性,因为它有三种状态:读加锁状态、写加锁状态和不加锁状态。
1 u) Y0 b! b2 `4 s0 H5 h! Y, @; j, t ^
读写锁的规则和状态:
: [9 ^: h8 l; ]+ \写模式加锁状态:当一个线程获取写锁时,其他所有试图获取该锁的线程(无论是读锁还是写锁)都会被阻塞,直到写锁被释放。读模式加锁状态:当线程获取读锁时,其他试图获取读锁的线程可以并发成功获取锁,但任何试图获取写锁的线程会被阻塞,直到所有读锁被释放。
`* d$ @ f! c% i; ]$ d7 K8 u8 M7 s- I& H
读写锁的使用场景:
5 y: H5 Z( Y2 T. V) F$ t( u0 u# D适用于读操作频繁且写操作较少的情况,这样能够允许多线程并发读取,减少锁的竞争,提高系统的效率。当需要保护一个共享数据结构,同时支持多个线程读,但限制只有一个线程写时,读写锁是比简单的互斥锁更好的选择。9 o; z* c8 U# {: ]2 L- t9 ^8 j
/ h, }) ]5 ^- d1
" g7 k2 V+ s7 {" D: `* T读写锁的初始化# t/ w ]2 Q# U( E) e! ?3 R; F
在使用读写锁之前,必须对其进行初始化。! p* E) Y+ M1 h+ J6 c8 C
: @* d; i# R2 h, K+ U/ LLinux使用pthread_rwlock_t数据类型来表示读写锁,初始化方式有以下两种:1 k3 Q# ]5 h! z# {+ H2 j( l/ u
* b2 A& j) d0 N$ a1 M
6 V* B& r9 `' o5 G5 v ]& |& h/ V
静态初始化:% X5 Q0 l6 h1 W1 K w$ m) ] e
5 n: k4 |) C$ i( p: ?: T/ O1 ^pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;# n# P7 W U% \6 e* X- ^
. d5 k5 v0 T) f5 H/ j: h1 G V
动态初始化:使用pthread_rwlock_init()函数初始化:
0 ~! [' |, P0 k$ g' _/ P
. }7 E7 g; ]% U. r4 h- cint pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr);/ k. S1 `9 o3 t- f- {
参数说明:
0 L* l G }7 B+ S6 B) @, Crwlock:指向需要初始化的读写锁对象(类型为pthread_rwlock_t)。attr:指向读写锁属性的指针(类型为pthread_rwlockattr_t),可以设置为NULL,表示使用默认属性。
! @* X3 `2 v* m* s# ^9 N0 U, Y2 c5 C8 f) R, J
2 `4 P8 |8 [* V, l7 `# M: S返回值:
& ]1 l$ V5 W C, |) Q8 a# W成功返回0。失败返回非0错误码,如:
T6 K0 I7 I+ |8 ^. L( {# kEINVAL:表示无效的属性值或锁对象。EBUSY:锁已初始化。ENOMEM:系统内存不足。9 R- e- `4 H5 c ?. n
- W7 S, p3 a; h2 z6 ^
2
/ y+ u$ e. e' A+ q' Q1 Q; c销毁读写锁% {; }1 L7 u9 L5 O9 i
当不再需要使用读写锁时,应该使用pthread_rwlock_destroy()销毁它。函数原型如下:
3 t. P! {* v4 m2 {% _, r# X8 a
# M1 z Q0 s3 G2 o4 J: R. Mint pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
% u: o: y" @& L3 m参数说明:
1 r' t' |* K' j1 P9 ^8 drwlock:指向需要销毁的读写锁对象。) X9 v- }6 o$ G4 X- O1 j
- `3 e' X" A, m. c- D1 q# L) n% P+ r! U: H5 ]- n- _1 Y
返回值:' H' f/ h9 @4 r
成功返回0。失败返回非0错误码,如:EBUSY:锁被其他线程持有。
9 L1 E- T% h* P; _1 Z" y/ T4 x
- i* \3 [" D& D0 q6 p3! v3 k% I7 G+ k& A/ U5 d/ o
读写锁加锁与解锁
7 y! M/ z7 B; m$ b/ c# C$ x以读模式加锁,该函数会阻塞调用线程,直到能够成功获取读锁。7 a8 `4 i7 d+ A% Z! h# f# `
: k5 ]5 o9 L( o F如果已经有其他线程持有写锁,当前线程将会等待。 a. Z$ O7 U5 P6 n3 j9 |+ R
+ O- A4 P) t9 ~) ]0 p' J. |
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); o/ v6 [0 V: f0 y9 P
+ `6 v) J6 T/ S9 A
参数说明:
& o/ a' h6 H4 \' ]; Frwlock:指向需要加锁的读写锁对象。. y8 | P' C2 W# X( r& Z5 _; i2 ]
: J! L; ?% U0 B& B$ ?: J; P: G6 k2 c- x/ f
返回值:5 r" r! o* s! D9 h- w6 a& ?7 U3 N
成功返回0。失败返回非0错误码,如:
& w7 J* _( J- W' _) mEINVAL:无效的锁。EDEADLK:检测到死锁。EAGAIN:系统无法分配更多的读锁。9 f. \8 z3 j1 w& P5 {0 J3 V
2 f6 c3 ]$ U+ [# @* K- r/ w! h
以写模式加锁,该函数会阻塞调用线程,直到能够成功获取写锁。1 K% l, y) V6 f. G
6 A5 | V/ G& I
只有当前线程能够获取写锁,其他所有请求锁的线程(无论是读锁还是写锁)都会被阻塞。
' ~& A$ |: ?: H+ q2 ]" ]& w' u/ z3 c O' ~ {& {9 F; O
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
# D' K1 ?. a4 m _* P1 a
8 e) S2 [0 U L1 _( u! x1 i参数说明:
) A* s! v1 u0 \+ m$ \rwlock:指向需要加锁的读写锁对象。" ?7 [( E. U9 p, ?0 U# r6 n
+ a7 i# \- M7 }! k# j
" R3 L0 [& g# F# J/ }2 g返回值:
5 J% C, D9 ~" J! I( b& O成功返回0。失败返回非0错误码,如:
( I; t6 p5 l9 m, r- t: k5 FEINVAL:无效的锁。EDEADLK:检测到死锁。: P4 z- ^, Y% i
; e, Z1 o- E& a+ N% \4 }
尝试获取读锁,该函数尝试获取读锁,不会阻塞。
5 I! F& {- z2 l, B7 ^" _* u! D# y3 V$ S8 v$ [! F
如果锁被其他线程占用,立即返回失败。5 }7 B1 F! Q+ P8 |& V
+ u6 M$ }) m. A' h: H }int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
" I% d- Q. H# e% s9 e. M1 |, k2 d3 K; e8 c( R; s4 s- q/ @7 Q
参数说明:* X) i$ a$ \4 t, |$ ^. |
rwlock:指向需要加锁的读写锁对象。
+ f6 U6 v8 \( N. A$ j" X: o9 k8 d* O0 _) y3 e
2 h* }0 _& D) X; \' X& m U返回值:4 ?2 X8 l# x0 O9 _1 p1 T
成功返回0。失败返回EBUSY表示锁已被占用,当前无法获取。
/ F. p6 @# f$ W% D3 C1 F* U8 v! Z, f8 Z: q& l/ }7 T4 }5 \" _9 s. B# P
尝试获取写锁,该函数尝试获取写锁,不会阻塞。) r5 q0 M V" I7 @
5 J z5 I z" b$ x5 ?. |如果锁被其他线程占用,立即返回失败。
" ]4 f) J2 t8 j& U4 F3 G( _+ u( v4 o3 a; _& Q' `) U6 x
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
" u0 W/ v0 ^/ L3 g, ^) D1 ^- g& L) Z. @5 k' c3 W
参数说明:
# j4 `. U* _. b# B$ U3 E3 |) m! hrwlock:指向需要加锁的读写锁对象。' C5 J; w6 h% i( `5 M* i2 L( g# O
/ a2 G+ {6 F6 G6 R3 W( k% J; o4 {( W \1 V' H" S2 j
返回值:" `/ e9 Q7 i3 C. Y
成功返回0。失败返回EBUSY表示锁已被占用,当前无法获取。
/ E+ b8 ?( r9 T% u# a g" S; ` z5 @+ q/ r
该函数用于释放当前线程持有的锁,无论是读锁还是写锁。. d5 P- Q+ w# i; [
- s) F' u2 w. M
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);; D* s) {! J) R D& h0 K
' W! e2 @! u; r3 }5 Y% g参数说明:
! B- s3 R& X8 |7 U/ L# v# B. ~rwlock:指向需要解锁的读写锁对象。/ }& i4 o8 c! ^ h8 p
) R8 t" ^9 Y# F8 z/ d0 w4 e- c$ F+ M6 I
返回值:
6 o1 ?- `8 C. T0 m; i3 Q成功返回0。失败返回非0错误码,如:
; y3 p* } ^) ~- pEINVAL:无效的锁对象。EPERM:当前线程未持有该锁。" c' U7 b: g. i) O
8 k% f" s \- g k
4
% \! K8 {5 s* T; R! T读写锁的属性
2 o' d4 P3 T: g读写锁也可以有属性,使用pthread_rwlockattr_t数据类型来表示。
. b K7 e; c0 N0 t! I4 u2 c% c' [
初始化读写锁属性函数原型: o' j4 e6 T0 ]- c" f9 K* M
* C# V h% p, o. Z, P1 fint pthread_rwlockattr_init(pthread_rwlockattr_t *attr);
1 W" A2 @: e! G, o+ C6 q) q/ A2 n- |, Q3 k+ J, y( M
参数说明:
8 d* ~) }% H+ }5 Z/ H1 Kattr:指向需要初始化的读写锁属性对象。* t, R0 r8 K3 }
+ P" r$ i, @; h* g) K9 l) w. c* X( r7 i* X. w6 J+ F- j, \
返回值:! F! ?8 x# C8 w$ d2 n' p& R
成功返回0。失败返回非0错误码。
1 f5 ~, c1 x- o! R. }1 ~
+ I4 y- S8 ?- X3 d4 U读写锁的属性中最常见的是进程共享属性,使用以下函数设置或获取:
% H6 [- V& O/ w! {% \. @% l
+ u) [. @5 l Aint pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared);
4 o2 g/ U/ c' g; d( J, C' g7 |9 h8 a! ~' b/ E+ |- A
参数说明:
7 q! ^% w& A5 w/ i, h9 ?' B4 uattr:指向需要设置的读写锁属性对象。pshared:共享属性的值。取值如下: 8 Y" {5 f8 b- |3 g
PTHREAD_PROCESS_SHARED:允许多个进程共享该读写锁。PTHREAD_PROCESS_PRIVATE:仅限当前进程的线程共享读写锁(默认值)。: K. z) I+ r& i- r$ o
3 H% d) _( d& w0 |% R" r$ I o返回值:2 f" A6 ?2 f. ]* o% l2 G" f5 O
成功返回0。失败返回非0错误码。 L! X- N' [& G7 v4 q
3 I1 ~# Q/ P# S7 W
以下代码展示了如何在读写锁的保护下,允许多个线程并发读取共享资源,但只有一个线程可以修改它:
( K6 H6 D( Z; p: `2 q7 \
3 @ P( C$ {- e q( l$ j9 B5 Epthread_rwlock_t rwlock;int shared_data = 0;
' ?# D" L/ C5 L+ M( o8 h$ wvoid *reader(void *arg) { pthread_rwlock_rdlock(&rwlock); // 获取读锁 printf("Reader: Shared data is %d" X8 j0 r& r. u$ `9 r# T
", shared_data); pthread_rwlock_unlock(&rwlock); // 解锁 return NULL;}5 U9 v, K/ n8 u" p& ~; D9 W
void *writer(void *arg) { pthread_rwlock_wrlock(&rwlock); // 获取写锁 shared_data += 1; printf("Writer: Updated shared data to %d/ u4 M+ M1 P9 d) Z+ u7 g' S
", shared_data); pthread_rwlock_unlock(&rwlock); // 解锁 return NULL;}' V* \$ K) k* l* s
int main() { pthread_t r1, r2, w1;
8 [4 ` z6 {7 I$ k' q pthread_rwlock_init(&rwlock, NULL); // 初始化读写锁
1 p! z! X, T6 S1 f J pthread_create(&r1, NULL, reader, NULL); pthread_create(&w1, NULL, writer, NULL); pthread_create(&r2, NULL, reader, NULL);
, @% a$ S1 _& Y/ m, y pthread_join(r1, NULL); pthread_join(w1, NULL); pthread_join(r2, NULL);# v6 T! }# D/ @1 G' C& j. e
pthread_rwlock_destroy(&rwlock); // 销毁读写锁 return 0;}- x/ G5 G W2 o2 [: P+ T
Linux中的读写锁适用于提高读密集型应用的并发性。) {! b0 Y4 `% j6 ^: L
" R7 F- c3 R" \6 ~+ x" w+ F$ S% ?它能够让多个线程同时读取资源,从而减少锁争用,但也需要合理考虑写饥饿问题。
7 \& g9 o2 a! F1 w- }8 s
. w: x6 h8 k( C3 g: a3 }注意事项如下:
/ Q1 |, U0 H$ z) j读写锁的潜在问题:如果读操作过于频繁,可能导致写线程长时间无法获取写锁,从而引发写饥饿问题。这通常需要通过其他机制(如优先级)来控制。使用场景:当读操作远多于写操作时,读写锁能带来性能提升。如果写操作频繁,读写锁可能并不会比互斥锁表现更好。+ M. i0 r) v% w! Z& o& q- P
! |- ^: b e2 F, |( B/ r1 n$ i
3fqf4x3ac4s64014949233.jpg
4 S2 Z! p: Z' N1 p
2f2s2amoy0e64014949333.gif
1 Y# W/ z; o: b' ^1 m
点击阅读原文,更精彩~ |
|