|

tg3x2f4oox464014949133.gif
4 ^/ F+ |1 u6 q; c4 X/ ]$ x: l* N
点击上方蓝色字体,关注我们' q; s- q" f' ^5 _' n( K. P
2 v' ^' g) [- j& [2 g" B读写锁相比互斥锁(mutex)或自旋锁(spinlock)具有更高的并行性,因为它有三种状态:读加锁状态、写加锁状态和不加锁状态。
" D# j& _; n% @8 J Y, z+ R3 P
- q' R% N* @+ g5 a6 I% w读写锁的规则和状态:$ R5 U0 `% g- _
写模式加锁状态:当一个线程获取写锁时,其他所有试图获取该锁的线程(无论是读锁还是写锁)都会被阻塞,直到写锁被释放。读模式加锁状态:当线程获取读锁时,其他试图获取读锁的线程可以并发成功获取锁,但任何试图获取写锁的线程会被阻塞,直到所有读锁被释放。
' O* x2 e, p* x+ _3 T
& Y, [8 n0 c$ J% {. D. M读写锁的使用场景:8 S8 b9 s* k! E
适用于读操作频繁且写操作较少的情况,这样能够允许多线程并发读取,减少锁的竞争,提高系统的效率。当需要保护一个共享数据结构,同时支持多个线程读,但限制只有一个线程写时,读写锁是比简单的互斥锁更好的选择。 D" V9 b4 S' o) e* @2 Z
6 i+ Z! S) b# L0 h- ^1
! @/ O. M4 T6 |2 f读写锁的初始化. ] p- |2 m! ?, F
在使用读写锁之前,必须对其进行初始化。8 w# }) i1 Y9 C( e' i" f
0 E4 e0 q1 O/ r6 U- K; F: MLinux使用pthread_rwlock_t数据类型来表示读写锁,初始化方式有以下两种:) O: N' W2 N6 t) R2 i
% F; f5 i! G' t" e9 T
5 I" j+ E5 j. q3 l+ b: [静态初始化:6 p& P+ ~* S Z r/ [9 o% }4 b
( `/ v0 J0 x8 G) fpthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;9 {$ z; I4 O0 V9 k7 e1 U- q# R
2 A# e- I& m7 x+ k& w% h+ @1 g动态初始化:使用pthread_rwlock_init()函数初始化:
: }+ K* d; V J& l8 n" k3 C2 \6 B4 F0 D$ ]5 q! _
int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr);
) s4 ^( g- n) W7 h" C- T参数说明:5 `1 o2 M3 y4 t6 h4 D/ u
rwlock:指向需要初始化的读写锁对象(类型为pthread_rwlock_t)。attr:指向读写锁属性的指针(类型为pthread_rwlockattr_t),可以设置为NULL,表示使用默认属性。
" g( L2 Q( o9 I8 @4 B5 x% ^# w4 D9 G- ?1 j7 U" Y6 |6 p) @
3 x! z1 [8 p. p% m
返回值:
, w! H+ i0 q5 k0 g7 f/ p成功返回0。失败返回非0错误码,如:
2 W8 n4 c' c# \) R2 ^# ^( |EINVAL:表示无效的属性值或锁对象。EBUSY:锁已初始化。ENOMEM:系统内存不足。: T# f0 M8 M7 d' c
) V5 ^! t! f/ @2
# k9 U: D- x* u+ Z B+ ~1 M- T销毁读写锁
7 h, N3 J' @( H; b2 L当不再需要使用读写锁时,应该使用pthread_rwlock_destroy()销毁它。函数原型如下:! f& `) x9 J N+ W5 S( T
% e4 ~, D! [. Q( d A$ M7 _! B
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);/ Y) j0 C9 j+ j% h& ~& C
参数说明:
- X0 m a$ J& E6 P) U7 I4 l! D8 Prwlock:指向需要销毁的读写锁对象。, c3 K2 E" n, Z& y- V8 ]. [; D
# U8 F$ t. @7 V4 h% J+ @+ O! F% }* K, ^( l. @
返回值:
( D% k* j( O8 c& r9 ]. q6 h成功返回0。失败返回非0错误码,如:EBUSY:锁被其他线程持有。) P. p/ S2 Q( U- G, f; ^
' i5 ?* ~2 t3 E& H R, V3! P' q9 \$ T8 p
读写锁加锁与解锁
" L4 J: T3 R/ G- e0 D以读模式加锁,该函数会阻塞调用线程,直到能够成功获取读锁。
, k) ]* V; o( E* T, q/ {+ m) w+ u' J
- ?8 V+ C' G" I. r如果已经有其他线程持有写锁,当前线程将会等待。# k( z9 w( {. c0 `
) [) u, y9 G1 e3 n% |2 sint pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
) b+ n" f! `7 O" P8 o
. U# Y$ K5 K: N* z: }. G$ M参数说明:
1 U S' J* b# O6 {) Q4 Arwlock:指向需要加锁的读写锁对象。4 m& x( _0 q) U% C3 V
6 E, ~9 [! a9 g4 ?3 G# s0 V5 O: M: {
返回值:
8 b$ E6 N1 ^2 L成功返回0。失败返回非0错误码,如: 1 l( i% l4 N% t! L0 o, K9 P
EINVAL:无效的锁。EDEADLK:检测到死锁。EAGAIN:系统无法分配更多的读锁。- b+ [$ p" e1 [9 v; ]! O2 c
/ }7 s5 s1 K- v& _4 o9 s4 ]6 b
以写模式加锁,该函数会阻塞调用线程,直到能够成功获取写锁。
P4 I/ b x5 M/ E4 A$ ^( r
5 F( X) [, g: H; y6 |+ y只有当前线程能够获取写锁,其他所有请求锁的线程(无论是读锁还是写锁)都会被阻塞。& |: q# b$ `/ b# I* W
' Y- @( f& ^4 \; n8 O6 Bint pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);4 L0 i, ^/ l' X3 f, B7 ]
: @( q$ r: u/ O; y& R
参数说明:
a9 \: y$ O& nrwlock:指向需要加锁的读写锁对象。' \2 U& H' ~, J" S* [7 }
3 B3 `. G8 a& r7 D4 d3 W" G o+ d& R& k2 Y+ n/ ~/ c0 ]
返回值:
$ C+ O4 |9 [+ X: H3 x5 V成功返回0。失败返回非0错误码,如:
( N1 U6 O: Q8 D- l3 h% YEINVAL:无效的锁。EDEADLK:检测到死锁。9 i" [, W2 }: d) M8 ?# R2 q$ f8 M
8 G6 w( j w9 x0 W% ~. u% u
尝试获取读锁,该函数尝试获取读锁,不会阻塞。/ a. _# y3 W; K
" y" p7 r' z7 O9 Y+ z d1 g
如果锁被其他线程占用,立即返回失败。; U) w) G# x1 W8 [
7 m$ P5 C4 h' f, v5 {: u0 tint pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
9 e7 N p( \- R$ O2 n4 D1 R. @, W3 ~( M' G# N* Y' L
参数说明:' G" ~2 z4 }2 K% p3 e$ @
rwlock:指向需要加锁的读写锁对象。0 s& n( M6 j! u0 `* Y
5 V: [3 ^# S9 O T2 C
2 M# f! B1 ?1 T) M4 V& l
返回值:' g. l) C* m, c2 q- T; p3 b
成功返回0。失败返回EBUSY表示锁已被占用,当前无法获取。7 ^* \1 S$ Z% E7 U* }
% R" q; {! a" K1 M/ }
尝试获取写锁,该函数尝试获取写锁,不会阻塞。
+ e5 `9 \% B) r7 _/ P0 f# c9 \0 e
7 Q8 B' z# ]1 r; i; f如果锁被其他线程占用,立即返回失败。& n' ~8 d! B2 l
# G6 J6 Y# ]) q. P6 X" r, H; x0 oint pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);" b3 [( d$ z1 D7 ^: w& b
: R5 y( F R; ?9 g0 M; L
参数说明:' g9 E# z+ J3 t2 ?2 L& v
rwlock:指向需要加锁的读写锁对象。
$ L% Q1 m0 r5 q- r* q
! W' J$ Q$ ^! k ]+ m4 I/ [4 x [+ F; R
返回值:7 t, N1 F H: u; Y, ], l2 Q6 C& X5 ?
成功返回0。失败返回EBUSY表示锁已被占用,当前无法获取。
_5 q; `( Z. ~& v, @3 e2 i
- X5 {$ @8 q/ f! q/ {( |$ _, T该函数用于释放当前线程持有的锁,无论是读锁还是写锁。
6 M: Q6 B* m0 v Z _3 u# @
, p" A. b7 b! r1 T H9 Xint pthread_rwlock_unlock(pthread_rwlock_t *rwlock);# L, F; `1 ~; X" @& m
7 V3 }) J& T' o/ c! {4 j% _- `" Z' T. g参数说明:
0 J) | a. U8 ?8 m8 u$ Zrwlock:指向需要解锁的读写锁对象。* j% q8 Z. A4 f: o7 c9 f# L# A* e* \
1 Z* {& ^' z" H; `& Z! j, m3 S
, I" R+ x$ m1 [) ]" u
返回值:
/ o4 H* N# A6 s成功返回0。失败返回非0错误码,如:
9 _8 i0 i$ N5 ^- T9 DEINVAL:无效的锁对象。EPERM:当前线程未持有该锁。
& n$ I' p$ u; V0 [/ z; j5 ~9 w! ?- D2 f, G7 {+ X
4/ B \) w; d6 |1 p5 R, j
读写锁的属性& m R, j! _' u, t
读写锁也可以有属性,使用pthread_rwlockattr_t数据类型来表示。% V# R: m% G/ i4 L. W! u }' a
7 U/ A8 i8 R9 L: K& ~4 J) D$ m6 Q初始化读写锁属性函数原型:
% J# j9 e: F9 V8 s$ r) J" A' s9 g0 M" H0 I$ R7 {
int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);
! ~0 Q1 ]9 l4 f
% Q* @# |$ }( a/ p% r2 u1 {, |参数说明:
8 u2 n7 b3 L$ x* E$ [2 Y# gattr:指向需要初始化的读写锁属性对象。
" {, B' Y: m; P H3 x+ H: O1 N6 D* q' P6 B0 f ?6 Q. O: }
0 f1 j5 ~ k9 C: F返回值:& I5 r1 ? t. P) e I: n6 c
成功返回0。失败返回非0错误码。( o2 E/ Z: D. \) F3 ?7 i
" B+ }3 i5 X# J3 x5 X; ^
读写锁的属性中最常见的是进程共享属性,使用以下函数设置或获取:/ q x5 H( V' `6 s* u+ q
@+ [2 Z4 g, dint pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared);' Y6 P. T$ u# q' ~+ |+ `
/ e) j! s. k% |; u2 U" b$ Q8 ^# }参数说明:2 o4 @% Y+ K8 N! O% J: K
attr:指向需要设置的读写锁属性对象。pshared:共享属性的值。取值如下: ) J4 L& p0 S4 v3 f0 j
PTHREAD_PROCESS_SHARED:允许多个进程共享该读写锁。PTHREAD_PROCESS_PRIVATE:仅限当前进程的线程共享读写锁(默认值)。- B. O+ y0 o. v3 g2 Q8 ^
- d) X2 g) T2 t* n0 x
返回值:) c7 U) ~- X% Y ~9 W8 Y5 W
成功返回0。失败返回非0错误码。2 t- }, W, I3 F! }( E$ H! I
& z7 B1 E2 }6 N/ U& A
以下代码展示了如何在读写锁的保护下,允许多个线程并发读取共享资源,但只有一个线程可以修改它:4 o& g( R# p; d! K$ B
) |9 C, p3 |( s9 C' ?! rpthread_rwlock_t rwlock;int shared_data = 0;& ~* F- o' ~( _5 b
void *reader(void *arg) { pthread_rwlock_rdlock(&rwlock); // 获取读锁 printf("Reader: Shared data is %d
) I& [2 X, r6 Y( |4 e; `", shared_data); pthread_rwlock_unlock(&rwlock); // 解锁 return NULL;}
, a$ j- d# ^9 t9 D/ q6 u4 bvoid *writer(void *arg) { pthread_rwlock_wrlock(&rwlock); // 获取写锁 shared_data += 1; printf("Writer: Updated shared data to %d
3 v) ~+ D$ v8 I", shared_data); pthread_rwlock_unlock(&rwlock); // 解锁 return NULL;}
* t0 S' a0 U3 v: G; N7 S: m* Yint main() { pthread_t r1, r2, w1;
% u9 m: N: m" b. m pthread_rwlock_init(&rwlock, NULL); // 初始化读写锁. \7 d. e& c6 |$ n1 f5 H
pthread_create(&r1, NULL, reader, NULL); pthread_create(&w1, NULL, writer, NULL); pthread_create(&r2, NULL, reader, NULL);# k. d4 ?1 N4 E/ _1 O' f$ l
pthread_join(r1, NULL); pthread_join(w1, NULL); pthread_join(r2, NULL);; K! q5 a J; e0 J; M$ T& O7 f
pthread_rwlock_destroy(&rwlock); // 销毁读写锁 return 0;} c6 [. _7 Y6 C9 A) J0 E
Linux中的读写锁适用于提高读密集型应用的并发性。/ n, S4 G+ K3 O
' e0 S& _5 @: L; j6 [它能够让多个线程同时读取资源,从而减少锁争用,但也需要合理考虑写饥饿问题。: `# K4 ^3 z8 Y4 p% g8 ]7 `, v
4 R9 D/ E- h) P3 p; a注意事项如下:
% d" x0 ]+ W/ _3 P' B8 d1 f读写锁的潜在问题:如果读操作过于频繁,可能导致写线程长时间无法获取写锁,从而引发写饥饿问题。这通常需要通过其他机制(如优先级)来控制。使用场景:当读操作远多于写操作时,读写锁能带来性能提升。如果写操作频繁,读写锁可能并不会比互斥锁表现更好。
8 p2 P: B( r K" W& \6 W p& s4 ?% }
3fqf4x3ac4s64014949233.jpg
2 ~" |; |* U" Z5 ]5 a7 L
2f2s2amoy0e64014949333.gif
: L, e5 v- w6 P点击阅读原文,更精彩~ |
|