电子产业一站式赋能平台

PCB联盟网

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

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

[复制链接]

1001

主题

1001

帖子

8807

积分

高级会员

Rank: 5Rank: 5

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

np2ooh45p1p6409305.gif

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! K
    8 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- e
  • int pthread_spin_init(pthread_spinlock_t *lock, int pshared);参数:
    ' N  x3 }2 \) v' W
  • lock:指向需要初始化的自旋锁对象。
  • 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- B
  • int 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; I
  • 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: 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

    hoh2d0pqbid6409405.jpg

    ) g$ W$ Y: T; `

    3kgmvp55hgf6409505.gif

    3kgmvp55hgf6409505.gif

    ' J; Z! F8 \+ g, [" D. s* C. L% U点击阅读原文,更精彩~
  • 回复

    使用道具 举报

    发表回复

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

    本版积分规则


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