|
np2ooh45p1p6409305.gif
, W9 C" O; i# H' e
点击上方蓝色字体,关注我们
' t+ e; ^' r+ m) M! {- T8 Z- Y; c) h3 [) T2 i
自旋锁的特点是适用于锁的持有时间非常短的场景,因为它在等待期间不会主动放弃 CPU,而是不断尝试获取锁,这在多核系统中可以避免由于线程调度带来的上下文切换开销。% _% R1 Q9 N7 L- B0 D
! o0 F. a W1 ?: X9 l5 ~ Y工作原理:' w( q( s9 P2 a3 r0 S7 L* J
加锁:线程尝试获取锁,如果成功,则进入临界区。如果锁已经被占用,线程会不停地轮询检查锁是否释放。忙等(自旋):如果锁被占用,线程会持续忙等,不会主动让出 CPU。这样避免了上下文切换,但消耗了 CPU 资源,因此自旋锁适用于锁定时间较短的场景。解锁:当临界区的任务完成后,线程释放锁,其他正在忙等的线程可以继续尝试获取锁。
1 v: X% i5 _7 h/ C' }* s: o自旋锁常用于以下情况:( G: {# b3 l" P+ I- _& M
需要保护的代码段执行时间非常短,能够迅速释放锁。不希望线程进入睡眠状态或导致上下文切换,尤其是在内核中的中断处理程序或者性能要求高的系统。多核系统中,并发访问的共享资源保护,避免线程在上下文切换中浪费时间。
9 W$ [' n& I( S, U1 C$ L. j8 b4 j) b
自旋锁与互斥锁的比较如下:" e7 h3 a8 `1 h, s& a" `- [6 F
2 F, U0 |' i/ n: r( a/ Z6 I4 v- X# `6 c- a5 N; w! S
实现方式上的区别:
- f% m. R* s, Q4 J自旋锁是一种轻量级锁机制,它本质上是在忙等状态下获取锁。当线程无法获取锁时,线程不会进入睡眠或等待状态,而是会不断地检查锁的状态,直到可以成功获取锁。互斥锁则是一种更高层的锁,通常在无法获取锁时会导致线程进入阻塞状态。互斥锁可以让操作系统将当前线程挂起,等待锁可用时再唤醒。
- Z; u- j0 A2 k: c- P
5 O3 Z7 w! t0 d+ s开销上的区别:+ }3 J+ v; [# ` t% e
自旋锁的主要优势在于没有上下文切换的开销,特别适用于锁持有时间很短的场景。由于自旋锁不会导致线程休眠,所以在处理器繁忙时可能会浪费 CPU 时间。如果等待时间较长,忙等会消耗过多的 CPU 资源,反而导致效率下降。互斥锁的开销较大,因为线程在获取不到锁时会陷入休眠,直到获取到锁时才会被唤醒。休眠和唤醒的代价很高,特别是在频繁锁定/解锁的场景中会影响性能。$ K8 E, d5 h+ M% U$ e* N7 Y7 I
|( m3 q; Y# t& J: x适用场景上的区别:
: A& h# k" g. f7 J: g自旋锁常用于内核中或者需要避免上下文切换的场景。特别是在中断上下文中,自旋锁更为合适,因为中断处理程序不能被阻塞或休眠。它适用于那些执行时间极短的临界区,锁的持有时间必须足够短,才能避免因自旋导致 CPU 资源浪费。互斥锁更适用于用户态的程序或者锁定时间较长的临界区。当程序无法获取到互斥锁时,系统可以调度其他线程运行,直到锁被释放,适合长时间的等待操作。9 A, R4 D7 R9 T" [. _$ v& D2 X4 x
q! ^0 b s- R5 s0 j r( D
死锁问题:" x9 ]7 c* U0 U" d2 ^" j; O
如果对同一个自旋锁进行两次加锁操作,必然会导致死锁,因为自旋锁不具备递归性。互斥锁则可以通过特定的类型来避免死锁。例如 PTHREAD_MUTEX_ERRORCHECK 类型的互斥锁在检测到重复加锁时,会返回错误而不是陷入死锁状态。
. [" W1 R$ q/ [. m6 d) \* _+ l1
' N( L1 `/ e( f2 O5 C9 P自旋锁初始化与销毁9 |/ x1 E3 n9 m4 P$ I
自旋锁需要在使用前进行初始化,并在不再使用时销毁。
S# |* k3 ?1 [6 U8 _7 U' W, T x3 X9 F7 J( ^& V
初始化自旋锁函数如下:
- k; x! j3 }, ?: h j) \$ x' U) m" W0 K7 a" m$ d
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);参数:
% o; {: h+ B* g2 a* R$ clock:指向需要初始化的自旋锁对象。pshared:自旋锁的共享属性,可以取值:8 o7 O% [* Z% Z
PTHREAD_PROCESS_SHARED:允许在多个进程中的线程之间共享自旋锁。PTHREAD_PROCESS_PRIVATE:自旋锁只能在同一进程内的线程之间使用。1 H1 o1 t+ p+ R3 H: P9 w2 Q9 r$ ?
. W2 v$ d* U& l1 Z) b2 c返回值:成功时返回 0,失败时返回非零错误码。
: q1 ?7 m6 q+ m5 b% ]! ]' W+ J# _; O7 d7 |2 R4 q
销毁自旋锁函数如下:/ k; s1 \! w1 q& h
4 k/ e; o# Q# P4 u0 s0 t7 u2 m: x
int pthread_spin_destroy(pthread_spinlock_t *lock);参数:lock:指向要销毁的自旋锁对象。! t! w; r/ q4 o
返回值:成功时返回 0,失败时返回非零错误码。
* ^2 F7 p* Y/ C# v! v8 L2
. p1 w5 |1 {, f) |& p5 |8 b自旋锁加锁与解锁# Y4 D0 `: G# l7 ?7 y
加锁函数如下:
% l5 ~) F4 N/ ~+ {5 ]" G7 A3 T9 W+ o5 y
int pthread_spin_lock(pthread_spinlock_t *lock);参数:lock:指向要加锁的自旋锁对象。) s( Y+ ?+ I5 ^+ v! q& n: H
返回值:成功时返回 0;如果锁已经被其他线程占用,则线程会忙等,直到成功获取锁,最终返回 0。$ V4 C7 b' d( M
8 A- k- x: Z/ l# ~2 O9 e
尝试加锁函数如下:
7 U, N" b- Q( @. h+ r) Z `" P4 j T1 ?, l* ~5 p0 ?- B7 c
int pthread_spin_trylock(pthread_spinlock_t *lock);参数:lock:指向要加锁的自旋锁对象。
6 v+ W }9 X" N! b返回值: U: @; T# Z4 Y: ~
成功时返回 0。如果锁已被占用,立即返回 EBUSY。
5 e' x) L4 J9 F0 ~' ^6 y/ c$ R# h) x1 b8 Q- {4 v. n4 A! ]6 u
解锁函数如下:" d0 B, `. ?$ _7 X# [- x
5 U: Z |7 T9 g d
int pthread_spin_unlock(pthread_spinlock_t *lock);参数:lock:指向要解锁的自旋锁对象。
; ~8 j$ B0 u3 q j3 X& g返回值:成功时返回 0,失败时返回非零错误码。) ?& s: m0 f' A- \
' ~, M/ {# Q: y' a7 ]
下面是一个完整的示例,展示如何使用自旋锁,包括初始化、加锁、解锁和销毁:* Y( H# v, E4 n0 H' C' Z- {
pthread_spinlock_t spinlock; // 定义自旋锁int shared_data = 0; // 共享数据void *thread_func(void *arg) { pthread_spin_lock(&spinlock); // 加锁 shared_data++; printf("Thread %ld: shared_data = %d
. e- D b0 m' b. _& v- U9 _", (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,而是持续消耗资源。9 v( i9 P0 s: S! s5 _2 B5 S
0 f* j2 O! x2 F% E8 p# m2 t9 q# M
如果锁持有时间较长,CPU 的利用效率会急剧下降。
' N7 r- n2 G0 U. K% W8 C+ Q) h* N. j
因此,自旋锁不适合用于长时间锁定的场景,只适合那些临界区极短的操作。7 Z& w2 U: F: b1 i! A
. s" E- c7 {# L4 j; E0 S, m
通过对 pthread_spin_* 函数的合理使用,可以有效管理多线程访问共享资源的同步问题。% Z8 T! n' Z. K, l
0 ?7 |+ R' L* G) s4 }6 ~ c
确保在适当的地方进行加锁和解锁,以防止死锁和资源竞争。# ~% Q- E$ X# @& r* n4 w& K6 [
hoh2d0pqbid6409405.jpg
% {( j+ S2 P7 v8 l# b" I, j
3kgmvp55hgf6409505.gif
) j' v d |; \$ R9 m1 U( t! f. J
点击阅读原文,更精彩~ |
|