电子产业一站式赋能平台

PCB联盟网

搜索
查看: 84|回复: 0
收起左侧

嵌入式Linux:线程同步(自旋锁)

[复制链接]

989

主题

989

帖子

8575

积分

高级会员

Rank: 5Rank: 5

积分
8575
发表于 2025-3-17 08:02:00 | 显示全部楼层 |阅读模式

np2ooh45p1p6409305.gif

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, U
    1 C$ L. j8 b4 j) b
    自旋锁与互斥锁的比较如下:" e7 h3 a8 `1 h, s& a" `- [6 F

    2 F, U0 |' i/ n: r( a/ Z6 I
    4 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$ c
  • lock:指向需要初始化的自旋锁对象。
  • 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

    hoh2d0pqbid6409405.jpg
    % {( j+ S2 P7 v8 l# b" I, j

    3kgmvp55hgf6409505.gif

    3kgmvp55hgf6409505.gif
    ) j' v  d  |; \$ R9 m1 U( t! f. J
    点击阅读原文,更精彩~
  • 回复

    使用道具 举报

    发表回复

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则


    联系客服 关注微信 下载APP 返回顶部 返回列表