|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
, D: `8 ?- a% m7 ^; a存在共享资源(共享一个文件,一块内存等等)的时候,为了防止并发访问时共享资源的数据不一致,引入了同步机制。
4 w$ E, w* p. D, z A8 x" P6 c1 `+ m+ h: W4 t# @
主要内容:
! e3 F% \" f* C( _; x, g7 p: b% _
& Q5 c* d1 M& c4 k同步的概念6 z, G/ E7 J, r( l2 Z8 o* N
同步的方法-加锁
h7 L2 P4 S, w& Y9 K死锁
, L. B9 B$ t8 N9 E$ C, g9 ^锁的粒度
3 e" j3 f8 O5 t6 B8 b' K
& r. }" U( [" r7 L$ n6 E: R
+ t3 c( ]4 m7 f0 r& U( S3 f! E, j1. 同步的概念
& ?3 J# g. v+ N4 t- s& p1 ?了解同步之前,先了解另外2个概念:. `8 q4 a7 T3 H5 R/ P# Z; K
, T) T+ y$ J/ p0 v) \
临界区 - 也称为临界段,就是访问和操作共享数据的代码段。' P1 p2 o+ R: g4 Y" b/ i
竞争条件 - 2个或2个以上线程在临界区里同时执行的时候,就构成了竞争条件。/ D, q9 H& {" Z2 x- v
- V5 R& ]+ |5 n. }! r, _4 i
8 ~# c) B8 ~0 Y& b! T
所谓同步,其实防止在临界区中形成竞争条件。1 s" W6 T9 L$ W1 m3 q0 }" f5 d
, O' F$ z: A }; F1 `0 G; I6 r如果临界区里是原子操作(即整个操作完成前不会被打断),那么自然就不会出竞争条件。
3 s/ k5 j! w0 L( _3 r ?0 L' j: _; I I A. q1 S
但在实际应用中,临界区中的代码往往不会那么简单,所以为了保持同步,引入了锁机制。/ R) I1 d' M" r2 P* p
0 e }" z' |, C4 Z$ R1 w
& w; A# T' d0 s7 |% H% A# o, @4 Y; w
& ^2 m6 p# @$ L. C2. 同步的方法-加锁
6 | }! q* t# S5 E为了给临界区加锁,保证临界区数据的同步,首先了解一下内核中哪些情况下会产生并发。- w2 V% ^. X, b! A$ q8 F& ?
q9 C' Q$ F- g1 Y + T: ?9 e: T' S
$ e' P: n9 M, n# {$ h
内核中造成竞争条件的原因:% _1 g! p: H4 D% i) O% b' w5 d7 a
1 Q7 s @7 d1 Y+ y, ]/ }, y3 p竞争原因" q' ?: ^6 ]4 [) J, f4 H- \
0 ?% M! V% J2 P说明
% W- O7 ]2 |- c+ i" w1 y7 D8 v- m3 D `6 T. e
中断 中断随时会发生,也就会随时打断当前执行的代码。如果中断和被打断的代码在相同的临界区,就产生了竞争条件! k ~0 v! N/ a* _8 G/ P
软中断和tasklet 软中断和tasklet也会随时被内核唤醒执行,也会像中断一样打断正在执行的代码5 [+ h- s' ?, q! [( A& ]1 W. W
内核抢占 内核具有抢占性,发生抢占时,如果抢占的线程和被抢占的线程在相同的临界区,就产生了竞争条件
6 H8 y' W4 }- r" q8 T. E. C5 V; s睡眠及用户空间的同步 用户进程睡眠后,调度程序会唤醒一个新的用户进程,新的用户进程和睡眠的进程可能在同一个临界区中
) ]8 [, `* e, k/ S对称多处理 2个或多个处理器可以同时执行相同的代码& o5 C& x3 s g! x
, E# l% U0 ?3 e& Q7 F
) e% e. Q7 j% e2 p, z% Y为了在编写内核代码时避免出现竞争条件,在编写代码之前就要考虑好临界区在哪,以及怎么加锁。
0 _* h9 Z7 F$ h
: ~$ p% J' \. f5 V在编写完代码后再加锁是非常困难的,很可能还会导致部分代码重写。& E, G; a- x6 n# _
# a6 E8 t6 u% p8 W* O1 ^1 t
- p+ @9 |1 a% H7 Y" y8 N9 U; ~2 [! b% {$ y @
编写内核代码时,时时记着下面这些问题:
3 ?8 e$ ?5 b" k# L( F! P# A* X) x
; H! P, z! A6 {" f这个数据是不是全局的?除了当前线程以外,其他线程能不能访问它?6 Z- a: z& V3 K$ o7 z* z3 C
这个数据会不会在进程上下文或者中断上下文中共享?它是不是要在两个不同的中断处理程序中共享?
$ s V. |8 V0 Y, \进程在访问数据时可不可能被抢占?被调度的新程序会不会访问同一数据?
% b: I: a) [9 i0 C- F7 e当前进程会不会睡眠(或者阻塞)在某些资源上,如果是,它会让共享数据处于何种状态?. p E: e. [, P. U5 J# p1 F2 E
怎样防止数据失控?
& \8 { z( e* `% o如果这个函数又在另一个处理器上被调度将会发生什么?
9 z8 ]$ {) r& }) U& t" B
6 N& j: h5 M( {* ~
; C, z7 }4 U# @3. 死锁5 q' p$ E, M6 s4 E, f
死锁就是所有线程都在相互等待释放资源,导致谁也无法继续执行下去。
& E% ~' ^2 g( _) ~/ F8 {- u7 ~( a7 b
/ B# D6 O3 T$ M4 u+ U! v下面一些简单的规则可以帮助我们避免死锁:
6 g" F# v6 h1 d* p5 ?
- s1 F2 x6 N# j' |, `$ o5 z如果有多个锁的话,尽量确保每个线程都是按相同的顺序加锁,按加锁相反的顺序解锁。(即加锁a->b->c,解锁c->b->a)4 |3 k& M' q8 B9 ~. N. ]* h9 d( t
防止发生饥饿。即设置一个超时时间,防止一直等待下去。
) J( f% ^' W3 s$ D) K7 y不要重复请求同一个锁。6 n8 h' T: X9 Q2 Q7 m4 j) W" X+ F
设计应力求简单。加锁的方案越复杂就越容易出现死锁。
* q, m2 M- d4 U% A6 S
- _ u- n; q& M- u5 M" S6 u3 L( L' f0 h' B: T u
4. 锁的粒度
3 W1 T* Q7 K, |7 v; `4 f在加锁的时候,不仅要避免死锁,还需要考虑加锁的粒度。
- k6 C$ {5 [& R% d3 n1 `+ @
s& U7 X1 C" \( z锁的粒度对系统的可扩展性有很大影响,在加锁的时候,要考虑一下这个锁是否会被多个线程频繁的争用。) D4 y. k, b) Z
9 p) P0 E; L! n! U' s, A6 X* h H8 c
如果锁有可能会被频繁争用,就需要将锁的粒度细化。
8 u+ @8 T( o2 ]7 ~* W6 r/ {
' j1 e3 v8 Q# p细化后的锁在多处理器的情况下,性能会有所提升。
/ l# ?; y& H( a8 i1 ]! C" _7 k9 x6 y# j# P% i$ _5 |% n
- }: d: E/ y, o( l8 @( W' ]8 c
/ G1 \+ W( y# l2 `举个例子说明一下:比如给一个链表加锁,同时有A,B,C 3个线程频繁访问这个链表。. e+ g5 o; v5 L& I% S
0 ~5 x) D& }$ K8 v- e那么当A,B,C 3个线程同时访问这个链表时,如果A获得了锁,那么B,C线程只能等待A释放了锁后才能访问这个链表。4 ^! Z* L( E3 q! l1 U( h# A
9 E5 g+ X; w* f6 T. k i4 O
# \; d K, T, T+ ~/ H! m
- m0 w/ r% X. {) f7 z! f% @如果A,B,C 3个线程访问的是这个链表的不同节点(比如A是修改节点listA,B是删除节点listB,C是追加节点listC),
& l4 r8 Z. }" ` P/ G% K7 _7 x5 r4 b4 a% y8 Q
并且这3个节点不是连续的,那么3个线程同时运行是不会有问题的。
; ~' z+ l# R: {; \/ A, I% J! y$ A# }+ K) g8 t
, b$ v: ^+ o/ {
1 R W7 N2 W( p' \7 L% T这种情况下就可以细化这个锁,把加在链表上的锁去掉,改成把锁加在链表的每个节点上。(也就是锁粒度的细化)4 P) j5 E( s- Z* Y7 g
/ v0 c5 c( _1 B8 d7 e
那么,上述的情况下,A,B,C 3个线程就可以同时访问各自的节点,特别是在多处理器的情况下,性能会有显著提高。. x! z9 t( `* f* k
) \# J: a, a! G) K
# g2 n# }; M6 i n: g
6 M' Z/ j1 p* |# k" x( e9 w最后还有一点需要提醒的是,锁的粒度越细,系统开销越大,程序也越复杂,所以对于争用不是很频繁的锁,就没有必要细化了。 |
|