|
np2ooh45p1p6409305.gif
2 v1 s$ W( `, u, d5 n' Z' {6 l$ C" U点击上方蓝色字体,关注我们
/ P) |4 a# r3 F
! q& i* E3 R6 V1 `/ o! f自旋锁的特点是适用于锁的持有时间非常短的场景,因为它在等待期间不会主动放弃 CPU,而是不断尝试获取锁,这在多核系统中可以避免由于线程调度带来的上下文切换开销。1 q | k# ?/ w1 \5 |
1 n- @' o2 Y' @/ `9 _- z工作原理:
1 J7 V* h5 Y0 [5 p) t8 e8 P1 U0 y加锁:线程尝试获取锁,如果成功,则进入临界区。如果锁已经被占用,线程会不停地轮询检查锁是否释放。忙等(自旋):如果锁被占用,线程会持续忙等,不会主动让出 CPU。这样避免了上下文切换,但消耗了 CPU 资源,因此自旋锁适用于锁定时间较短的场景。解锁:当临界区的任务完成后,线程释放锁,其他正在忙等的线程可以继续尝试获取锁。9 @& J% U+ r7 R% e
自旋锁常用于以下情况:
( _8 R3 D" b' \- |& U# N" K p需要保护的代码段执行时间非常短,能够迅速释放锁。不希望线程进入睡眠状态或导致上下文切换,尤其是在内核中的中断处理程序或者性能要求高的系统。多核系统中,并发访问的共享资源保护,避免线程在上下文切换中浪费时间。6 |8 x, o) g0 D* Y
3 q' _, b9 E* a2 K/ f' j/ q+ }. a
自旋锁与互斥锁的比较如下:, C& W L4 j+ b
0 S9 M; }3 L" |1 b* f2 Q2 N. T5 R8 A9 C
6 ]. |# _ @( H, o0 C3 q$ V
实现方式上的区别:6 G" {9 |' i+ O' D$ E8 k( c
自旋锁是一种轻量级锁机制,它本质上是在忙等状态下获取锁。当线程无法获取锁时,线程不会进入睡眠或等待状态,而是会不断地检查锁的状态,直到可以成功获取锁。互斥锁则是一种更高层的锁,通常在无法获取锁时会导致线程进入阻塞状态。互斥锁可以让操作系统将当前线程挂起,等待锁可用时再唤醒。
) I; q" Q) k9 _# S! K8 x# W4 F' i9 Z" r& u7 o
开销上的区别:3 B- @, ~, z. C0 S# n
自旋锁的主要优势在于没有上下文切换的开销,特别适用于锁持有时间很短的场景。由于自旋锁不会导致线程休眠,所以在处理器繁忙时可能会浪费 CPU 时间。如果等待时间较长,忙等会消耗过多的 CPU 资源,反而导致效率下降。互斥锁的开销较大,因为线程在获取不到锁时会陷入休眠,直到获取到锁时才会被唤醒。休眠和唤醒的代价很高,特别是在频繁锁定/解锁的场景中会影响性能。8 T, Z- ^* A S }7 \; K; c
; k' A$ D' A! U' V0 z
适用场景上的区别:
% f3 a% @/ t4 a自旋锁常用于内核中或者需要避免上下文切换的场景。特别是在中断上下文中,自旋锁更为合适,因为中断处理程序不能被阻塞或休眠。它适用于那些执行时间极短的临界区,锁的持有时间必须足够短,才能避免因自旋导致 CPU 资源浪费。互斥锁更适用于用户态的程序或者锁定时间较长的临界区。当程序无法获取到互斥锁时,系统可以调度其他线程运行,直到锁被释放,适合长时间的等待操作。
$ \, I. n7 C" v. i( V6 r* ~* K$ s. F# D/ z/ W3 q& q
死锁问题:# O7 ]' _# s/ W( E) H9 q
如果对同一个自旋锁进行两次加锁操作,必然会导致死锁,因为自旋锁不具备递归性。互斥锁则可以通过特定的类型来避免死锁。例如 PTHREAD_MUTEX_ERRORCHECK 类型的互斥锁在检测到重复加锁时,会返回错误而不是陷入死锁状态。
' H! D3 p' ^- r, m( f; Y1
~, j( X' N( T" U自旋锁初始化与销毁
3 c7 l9 T; p* ^: e0 u. _: K自旋锁需要在使用前进行初始化,并在不再使用时销毁。 `9 i' ]' \; g* M+ k w7 k
" Y, Y+ h, Y. \ G
初始化自旋锁函数如下:
8 S9 T1 o" S/ @$ o0 t. J7 s& z
/ a# \6 F$ c3 X% W& B2 W- eint pthread_spin_init(pthread_spinlock_t *lock, int pshared);参数:
' N x3 }2 \) v' Wlock:指向需要初始化的自旋锁对象。pshared:自旋锁的共享属性,可以取值:
0 t2 W2 g8 ~1 p/ ]PTHREAD_PROCESS_SHARED:允许在多个进程中的线程之间共享自旋锁。PTHREAD_PROCESS_PRIVATE:自旋锁只能在同一进程内的线程之间使用。
) g7 }1 n6 ~4 A$ ~9 u+ [& S; |9 Z) y+ |
返回值:成功时返回 0,失败时返回非零错误码。
9 s4 _, |" X H" W! s+ H
1 A& X2 c% O+ T1 g销毁自旋锁函数如下:! P ]% Z1 I' a# n
$ n* R b G3 X7 X+ q; e& z5 `int pthread_spin_destroy(pthread_spinlock_t *lock);参数:lock:指向要销毁的自旋锁对象。
& y+ k) D: @5 g$ e返回值:成功时返回 0,失败时返回非零错误码。
1 }. v# F3 n/ `! j+ F2
$ V: _8 e) E, i6 F( Q自旋锁加锁与解锁
- B9 E. }) ]2 v! ]% O+ Y$ V加锁函数如下:
. K3 x8 D7 l2 H+ j- i1 L5 s7 }4 e% }" g; o6 I& D u
int pthread_spin_lock(pthread_spinlock_t *lock);参数:lock:指向要加锁的自旋锁对象。4 R: |/ X/ n' ~, [4 ^! V: a- G" A2 A
返回值:成功时返回 0;如果锁已经被其他线程占用,则线程会忙等,直到成功获取锁,最终返回 0。
$ u0 P v, L2 D5 z+ A( |5 t D7 L, h9 r8 P. c5 s( E
尝试加锁函数如下:( e) O+ w4 K; s7 ^, L( P4 o
* f% r3 Q% W# u" I' v0 o- Bint pthread_spin_trylock(pthread_spinlock_t *lock);参数:lock:指向要加锁的自旋锁对象。. C- d9 ?0 F( s, q
返回值:
% i) ]: k% O" Z4 r/ ?6 q成功时返回 0。如果锁已被占用,立即返回 EBUSY。
9 P+ a' a9 o0 q7 c/ T) w" G
3 ?) {9 r9 P2 q解锁函数如下:
/ o! I& g7 ^* B/ L
" j" L/ d0 ]$ E& ?int pthread_spin_unlock(pthread_spinlock_t *lock);参数:lock:指向要解锁的自旋锁对象。
W2 Z4 f3 T8 ^, z& C$ F7 r' m返回值:成功时返回 0,失败时返回非零错误码。
" L' ]! b4 E# Y1 @4 t w8 `& Y* A
. h- T. c+ F5 e2 _5 k* s下面是一个完整的示例,展示如何使用自旋锁,包括初始化、加锁、解锁和销毁:
; n( O( x$ G0 g) S; Ipthread_spinlock_t spinlock; // 定义自旋锁int shared_data = 0; // 共享数据void *thread_func(void *arg) { pthread_spin_lock(&spinlock); // 加锁 shared_data++; printf("Thread %ld: shared_data = %d: h$ i/ `) e3 |* v
", (long)arg, shared_data); pthread_spin_unlock(&spinlock); // 解锁 return NULL;}int main() { pthread_t threads[2]; // 初始化自旋锁 if (pthread_spin_init(&spinlock, PTHREAD_PROCESS_PRIVATE) != 0) { perror("Failed to initialize spinlock"); return 1; } // 创建两个线程 pthread_create(&threads[0], NULL, thread_func, (void *)1); pthread_create(&threads[1], NULL, thread_func, (void *)2); // 等待线程结束 pthread_join(threads[0], NULL); pthread_join(threads[1], NULL); // 销毁自旋锁 if (pthread_spin_destroy(&spinlock) != 0) { perror("Failed to destroy spinlock"); return 1; } return 0;}自旋锁的主要问题在于,它在获取不到锁时不会释放 CPU,而是持续消耗资源。! Y$ R( _& N0 N2 c& f$ e- q
# H+ Z' P2 g- t4 P
如果锁持有时间较长,CPU 的利用效率会急剧下降。
* s0 Q: o) T9 D6 |2 `% L: f; A0 I/ y! R
因此,自旋锁不适合用于长时间锁定的场景,只适合那些临界区极短的操作。9 H6 B. U! t5 V. @9 {) B4 t, }
! i: t/ F+ [0 u( z/ r! @) a
通过对 pthread_spin_* 函数的合理使用,可以有效管理多线程访问共享资源的同步问题。1 {7 o- e% g: `8 ^4 f9 _' e
! _7 E6 ?7 x% ^1 ^, z9 w* S
确保在适当的地方进行加锁和解锁,以防止死锁和资源竞争。' b! @* V8 n7 b# X! N
hoh2d0pqbid6409405.jpg
) g$ W$ Y: T; `
3kgmvp55hgf6409505.gif
' J; Z! F8 \+ g, [" D. s* C. L% U点击阅读原文,更精彩~ |
|