POSIX 自旋锁
自旋锁是为实现保护共享资源而提出一种轻量级的锁机制。其实,自旋锁与互斥锁比较类似,它们都是为了解决对共享资源的互斥访问。
无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个拥有者,也就说,在任何时刻最多只能有一个线程获得锁。但是两者在调度机制上有很大的不同。对于互斥锁,如果互斥锁已经被占有,申请者只能进入睡眠状态。但是自旋锁不会引起申请者睡眠,如果自旋锁已经被别的线程占有,申请者就一直在那里循环判断该自旋锁的拥有者是否已经释放该锁,“自旋”一词就是因此而得名。
由此我们可以看出,自旋锁是一种比较低级的保护数据结构或代码片段的原始方式,这种锁机制可能存在以下两个问题:
- 死锁:申请者试图递归地获得自旋锁必然会引发死锁。
- 消耗过多 CPU 资源 :如果申请不成功,申请者将一直循环判断,这无疑降低了 CPU 的使用率。
由此可见,自旋锁适用于锁使用者保持锁时间比较短的情况。信号量适用于保持锁时间较长的情况。
需要注意的是,被自旋锁保护的临界区代码执行时不能因为任何原因放弃 CPU,因此,在自旋锁保护的区域内不能调用任何可能引发系统任务调度的 API。
POSIX 自旋锁的类型为 pthread_spinlock_t。使用时定义一个 pthread_spinlock_t 类型的变量,如:
pthread_spinlock_t spin;
一个 POSIX 自旋锁必须要调用 pthread_spin_init 函数初始化之后才能使用。
线程如果需要等待一个自旋锁,可以调用 pthread_spin_lock 函数,中断服务程序不可以调用任何 POSIX 自旋锁 API。释放一个自旋锁可以调用 pthread_spin_unlock 函数。
当一个自旋锁使用完毕后(并确保以后也不再使用),应该调用 pthread_spin_destroy 函数将其删除,SylixOS 会回收该自旋锁占用的内核资源。
自旋锁
- 自旋锁的初始化和删除
#include <pthread.h>
int pthread_spin_init(pthread_spinlock_t *pspinlock, int pshare);
函数 pthread_spin_init 原型分析:
- 此函数成功时返回 0,失败时返回错误号。
- 参数 pspinlock 是 POSIX 自旋锁的指针。
- 参数 pshare 标识了 POSIX 自旋锁是否进程共享。
#include <pthread.h>
int pthread_spin_destroy(pthread_spinlock_t *pspinlock);
函数 pthread_spin_destroy 原型分析:
- 此函数成功时返回 0,失败时返回错误号。
- 参数 pspinlock 是 POSIX 自旋锁的指针。
- 自旋锁的加锁与解锁
#include <pthread.h>
int pthread_spin_lock(pthread_spinlock_t *pspinlock);
int pthread_spin_unlock(pthread_spinlock_t *pspinlock);
int pthread_spin_trylock(pthread_spinlock_t *pspinlock);
以上函数原型分析:
- 函数成功时返回 0,失败时返回错误号。
- 参数 pspinlock 是 POSIX 自旋锁的指针。
pthread_spin_trylock 是 pthread_spin_lock 的“尝试等待”版本,在 POSIX 自旋锁已经被占有时,pthread_spin_lock 将“自旋”,而 pthread_spin_trylock 将立即返回。
这时,POSIX 自旋锁的解锁操作只能调用 pthread_spin_unlock 函数。
如果中断服务程序也可能使用 POSIX 自旋锁保护的资源,应该使用带中断屏蔽版本的 POSIX 自旋锁API,否则会发生死锁:
#include <pthread.h>
int pthread_spin_lock_irq_np(pthread_spinlock_t *pspinlock,
pthread_int_t *irqctx);
int pthread_spin_unlock_irq_np(pthread_spinlock_t *pspinlock,
pthread_int_t irqctx);
int pthread_spin_trylock_irq_np(pthread_spinlock_t *pspinlock,
pthread_int_t *irqctx);
以上几个函数原型分析:
- 函数成功时返回 0,失败时返回错误号。
- 参数 pspinlock 是 POSIX 自旋锁的指针。
- 参数 irqctx 是 pthread_int_t 类型的指针,用于保存和恢复CPU的中断屏蔽寄存器。
pthread_spin_trylock_irq_np 是 pthread_spin_lock_irq_np 的“尝试等待”版本,在 POSIX 自旋锁已经被占有时,pthread_spin_lock_irq_np 将“自旋”,而 pthread_spin_trylock_irq_np 将立即返回。
这时,POSIX 自旋锁的解锁操作只能调用 pthread_spin_unlock_irq_np 函数。
下面程序展示了 POSIX 自旋锁的使用,程序创建两个线程和一个 POSIX 自旋锁,两个线程分别对变量 count 进行打印和自增操作,使用 POSIX 自旋锁作为访问变量 count 的互斥手段。
#include <stdio.h>
#include <pthread.h>
static pthread_spinlock_t lock;
static int count = 0;
static void *thread_a (void *arg)
{
int value;
while (1) {
pthread_spin_lock(&lock);
value = count;
pthread_spin_unlock(&lock);
printf("thread_a(): count = %d\n", value);
sleep(1);
}
return (NULL);
}
static void *thread_b (void *arg)
{
while (1) {
pthread_spin_lock(&lock);
count++;
pthread_spin_unlock(&lock);
sleep(1);
}
return (NULL);
}
int main (int argc, char *argv[])
{
pthread_t threada_tid;
pthread_t threadb_tid;
int ret;
ret = pthread_spin_init(&lock, FALSE);
if (ret != 0) {
fprintf(stderr, "mutex create failed.\n");
return (-1);
}
ret = pthread_create(&threada_tid, NULL, thread_a, NULL);
if (ret != 0) {
fprintf(stderr, "pthread create failed.\n");
return (-1);
}
ret = pthread_create(&threadb_tid, NULL, thread_b, NULL);
if (ret != 0) {
fprintf(stderr, "pthread create failed.\n");
return (-1);
}
pthread_join(threada_tid, NULL);
pthread_join(threadb_tid, NULL);
pthread_spin_destroy(&lock);
return (0);
}
在 SylixOS Shell 下运行程序:
# ./POSIX_Spinlock
thread_a(): count = 0
thread_a(): count = 1
thread_a(): count = 2
thread_a(): count = 3
thread_a(): count = 4
thread_a(): count = 5
thread_a(): count = 6
......
# ts
NAME TID PID PRI STAT LOCK SAFE DELAY PAGEFAILS FPU CPU
---------------- ------- ----- --- ---- ---- ---- ---------- --------- --- ---
POSIX_Spinlock 401008f 44 200 JOIN 0 0 1 USE 0
pthread 4010090 44 200 SLP 0 626 0 0
pthread 4010091 44 200 SLP 0 626 0 0