|
|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
' i4 c7 p: f) n. u% [
存在共享资源(共享一个文件,一块内存等等)的时候,为了防止并发访问时共享资源的数据不一致,引入了同步机制。+ u; N7 D) O+ B- Q3 b8 h- ?
. s9 `' A$ A( X7 Q! N& E; K* N, [主要内容:
4 d- z0 e# B; Q# L# ^' X- x: ~2 l, e+ {0 h9 \0 Z% N6 G9 k4 [3 v
同步的概念( E+ u9 v( B* G# N P( J& ?' K
同步的方法-加锁
! E" K% L) H: j- ~死锁) c. c D# ?9 E
锁的粒度
& S% ? p! }. E ; j6 C; J# e+ |) K5 J* ^& L2 e
, t3 H' \( u# U# O7 s6 a8 v/ j
1. 同步的概念
$ \# q! M, A" n了解同步之前,先了解另外2个概念:) Z5 J* x4 S- Z! L
5 X! E$ g; G; B
临界区 - 也称为临界段,就是访问和操作共享数据的代码段。$ F9 d; F; v; |4 z- {4 I) ^
竞争条件 - 2个或2个以上线程在临界区里同时执行的时候,就构成了竞争条件。# p) u/ y# p( a% [9 c2 U! M7 G
- G. i. M: z& I/ t0 V
: R! g5 L; M6 u# h所谓同步,其实防止在临界区中形成竞争条件。
7 c2 e" f1 K; q' d+ N# K5 u" ?; Y2 s! [; V+ Y; i# g
如果临界区里是原子操作(即整个操作完成前不会被打断),那么自然就不会出竞争条件。$ m- H; w: o; x: v, R1 D$ F
1 `5 |! q/ h* M4 S: s2 e但在实际应用中,临界区中的代码往往不会那么简单,所以为了保持同步,引入了锁机制。+ D. G3 ]) e* O, o4 f( {2 p7 Y
0 S" }# C' j9 f+ |9 r
. x/ h/ @2 l4 o2 m c
% f7 }8 G% y7 C$ j" y( g2. 同步的方法-加锁
2 V A: }9 {" I2 z为了给临界区加锁,保证临界区数据的同步,首先了解一下内核中哪些情况下会产生并发。
+ A4 f" Y3 Z R8 { u& p
' D. u3 |; W; Q% r" { 3 d t2 J% H; S
* _6 u; B# J- F
内核中造成竞争条件的原因:
9 h9 J7 \2 u! B& i0 f5 F+ p- y8 M! Q
竞争原因3 l# ~7 M" y2 m6 e- W' j) v
_! F" p a% _+ `3 f. H; f/ D
说明+ V: q( m( v6 G6 a; n9 c
V. {( g% R* H2 y0 O
中断 中断随时会发生,也就会随时打断当前执行的代码。如果中断和被打断的代码在相同的临界区,就产生了竞争条件1 O; R7 @9 r6 N l
软中断和tasklet 软中断和tasklet也会随时被内核唤醒执行,也会像中断一样打断正在执行的代码
( y% f) d. x: Y9 c1 k. u内核抢占 内核具有抢占性,发生抢占时,如果抢占的线程和被抢占的线程在相同的临界区,就产生了竞争条件5 b/ E! z7 ]: E! y- u2 x1 [% u
睡眠及用户空间的同步 用户进程睡眠后,调度程序会唤醒一个新的用户进程,新的用户进程和睡眠的进程可能在同一个临界区中; e/ Z, p/ v/ Z
对称多处理 2个或多个处理器可以同时执行相同的代码( p# r& s. G$ x) k
2 O8 E+ }7 w `. S( _' G' w1 c9 T0 u
为了在编写内核代码时避免出现竞争条件,在编写代码之前就要考虑好临界区在哪,以及怎么加锁。
3 @9 i G) s' t! Z+ t$ c
+ M9 A( K9 u' v5 p在编写完代码后再加锁是非常困难的,很可能还会导致部分代码重写。, v( ~3 F" R4 G3 M& Q7 R9 a
+ v4 H0 c( d, P$ T2 N! r# ^' K- v+ ] - Z( Y F3 L' S9 \
. Z" z# k7 |% B4 ^3 }" a6 m/ h编写内核代码时,时时记着下面这些问题:
9 `- Q2 ~- O9 m+ Y3 A- z/ ` v$ N
这个数据是不是全局的?除了当前线程以外,其他线程能不能访问它?
+ u3 k4 Q" ?& ~7 G* R$ q: E: ^这个数据会不会在进程上下文或者中断上下文中共享?它是不是要在两个不同的中断处理程序中共享?
2 s# C# O; B$ q( p4 P1 |进程在访问数据时可不可能被抢占?被调度的新程序会不会访问同一数据?
4 x( |. o) P7 \9 l4 j8 x. {当前进程会不会睡眠(或者阻塞)在某些资源上,如果是,它会让共享数据处于何种状态?$ B$ [& f1 H: b5 w* q, z5 N9 M
怎样防止数据失控?
4 H( x$ ?( e, K如果这个函数又在另一个处理器上被调度将会发生什么?
5 j6 j* z6 G# p, E2 t
3 w. C: u% s& ]8 M- i& g- a4 C6 [! @6 h* _
3. 死锁
0 L; R' J: Z' U7 n& Q+ j死锁就是所有线程都在相互等待释放资源,导致谁也无法继续执行下去。
# y4 n! q& R9 d2 W" t
6 c# s3 Q( _: h6 F) j7 {2 w2 _" S' G下面一些简单的规则可以帮助我们避免死锁:
) ]+ f& H" ~' ^9 q- l9 v
, N2 z# |" m8 U1 e0 p1 \如果有多个锁的话,尽量确保每个线程都是按相同的顺序加锁,按加锁相反的顺序解锁。(即加锁a->b->c,解锁c->b->a)
: {' o( Z; i# N* F$ w防止发生饥饿。即设置一个超时时间,防止一直等待下去。8 p" b: {0 i1 F. j: g; j9 i
不要重复请求同一个锁。! S9 B7 U: `3 }% D, D7 b/ z
设计应力求简单。加锁的方案越复杂就越容易出现死锁。3 x# M# K9 ^+ t: e
, p; k' h0 s6 D! J
" s* M7 z( n' x4. 锁的粒度/ Y: g% R4 ?3 A5 g. F, Z( W; ^# |! y
在加锁的时候,不仅要避免死锁,还需要考虑加锁的粒度。+ |2 z/ W6 g( d- I% P
& Y2 X% K6 J2 r: h: F# {锁的粒度对系统的可扩展性有很大影响,在加锁的时候,要考虑一下这个锁是否会被多个线程频繁的争用。
5 m6 H \/ t% A5 D S
- f. M: {% \' R5 j- N) e. T如果锁有可能会被频繁争用,就需要将锁的粒度细化。
4 E, m- O5 J) ?' s! B( a$ d- l! I0 U
细化后的锁在多处理器的情况下,性能会有所提升。3 F5 c( i2 L0 H' P- i5 }
6 u( R0 h% d$ D2 A5 F3 r% F
* D4 D4 m4 o" E$ M. X$ S5 k/ o2 r$ G7 b8 T
举个例子说明一下:比如给一个链表加锁,同时有A,B,C 3个线程频繁访问这个链表。
5 j; d6 D; h3 W8 W( W7 _
! K' q, x( g1 R- N* I! o那么当A,B,C 3个线程同时访问这个链表时,如果A获得了锁,那么B,C线程只能等待A释放了锁后才能访问这个链表。6 T# x/ w5 C, U+ i
- V+ ?; r3 W* I# ~
- |+ ] ]& B$ n% o: s) n* p" b- F
如果A,B,C 3个线程访问的是这个链表的不同节点(比如A是修改节点listA,B是删除节点listB,C是追加节点listC),
4 m9 Q$ [0 J& T- _5 ]& a8 M5 f4 z
并且这3个节点不是连续的,那么3个线程同时运行是不会有问题的。
' g$ Y# o' X- d' R( i1 C3 J2 a6 R# f( O6 w% P9 P( e% U
* O. s* N" B& s. H
* n0 V0 T- h! _这种情况下就可以细化这个锁,把加在链表上的锁去掉,改成把锁加在链表的每个节点上。(也就是锁粒度的细化)
( H1 i( c+ Y' D0 Y- A1 M {' O/ F6 X" H+ g0 }5 L5 j, j0 R
那么,上述的情况下,A,B,C 3个线程就可以同时访问各自的节点,特别是在多处理器的情况下,性能会有显著提高。
: n- s! o \0 m- Q5 @# P p- X) o* B9 R* m8 w" G* }! H
+ \; l; P; Q+ K$ V% b- x+ I: R V, Z
O3 k% O& S- }- j" M9 f# ?最后还有一点需要提醒的是,锁的粒度越细,系统开销越大,程序也越复杂,所以对于争用不是很频繁的锁,就没有必要细化了。 |
|