目录
一、什么是自旋锁?
工作原理
优点与缺点
二、自旋锁的实现
三、内核API
pthread_spin_lock
pthread_spin_trylock
pthread_spin_unlock
pthread_spin_init
pthread_spin_destroy
四、自旋锁的适用场景
自旋锁是一种多线程同步机制,用于保护共享资源免受并发访问的影响。它的核心思想是:当一个线程尝试获取锁时,如果锁已经被其他线程占用,它不会立即进入休眠状态,而是在一个循环中不断检查锁是否可用(即“自旋”)。这种方式避免了线程切换的开销,特别适合短时间内的锁竞争场景。
与传统的互斥锁(Mutex)相比,自旋锁的最大区别在于等待策略:
互斥锁:当获取锁失败时,线程主动让出CPU资源进入休眠状态
自旋锁:采用忙等待(Busy-waiting)策略,线程持续检查锁状态
自旋锁通常通过一个共享的标志位(如布尔值)来表示锁的状态:
如果标志位为 false
,表示锁可用,线程可以设置标志位为 true
并进入临界区。
如果标志位为 true
,表示锁已被占用,线程会不断循环检查标志位,直到锁被释放。
这种机制的核心在于使用原子操作(如 CAS 指令)来保证操作的线程安全性。
Compare-and-Swap(比较并交换)是实现自旋锁的关键原子操作:
// 伪代码示例
int compare_and_swap(int *ptr, int expected, int new_val)
{
int actual = *ptr;
if (actual == expected)
{
*ptr = new_val;
}
return actual;
}
当多个线程同时执行CAS操作时,硬件保证只有一个线程能成功修改值。这种机制也被称为乐观锁。
优点
低延迟:自旋锁不会让线程进入休眠状态,避免了线程切换的开销,特别适合短时间锁竞争场景。
减少系统调度开销:等待锁的线程不会被阻塞,减少了上下文切换的频率。
缺点
CPU 资源浪费:如果锁的持有时间较长,等待线程会一直循环等待,导致 CPU 资源浪费。
可能引起活锁:当多个线程同时自旋等待同一个锁时,如果没有适当的退避策略,可能导致所有线程都无法进入临界区。
自旋锁的实现通常依赖于原子操作,常用的实现方式是通过 CAS(Compare-And-Swap)指令。以下是基于 C 语言的伪代码实现:
#include
#include
#include
#include
// 使用原子标志来模拟自旋锁
atomic_flag spinlock = ATOMIC_FLAG_INIT; // ATOMIC_FLAG_INIT 是 0
// 尝试获取锁
void spinlock_lock()
{
while (atomic_flag_test_and_set(&spinlock))
{
// 如果锁被占用,则忙等待
}
}
// 释放锁
void spinlock_unlock()
{
atomic_flag_clear(&spinlock);
}
// atomic_flag 的底层结构
typedef _Atomic struct
{
#if __GCC_ATOMIC_TEST_AND_SET_TRUEVAL == 1
_Bool __val;
#else
unsigned char __val;
#endif
} atomic_flag;
说明:
原子性:atomic_flag_test_and_set
是一个原子操作,确保在多线程环境中对标志位的读取和修改是不可分割的。
忙等待:如果锁被占用,线程会不断循环检查标志位,直到锁被释放。
#include
// 获取自旋锁
int pthread_spin_lock(pthread_spinlock_t *lock);
// 尝试获取自旋锁(非阻塞)
int pthread_spin_trylock(pthread_spinlock_t *lock);
// 释放自旋锁
int pthread_spin_unlock(pthread_spinlock_t *lock);
// 初始化自旋锁
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);
// 销毁自旋锁
int pthread_spin_destroy(pthread_spinlock_t *lock);
pthread_spin_lock
用途:获取自旋锁。如果锁已被其他线程占用,则线程会自旋等待,直到锁被释放。
函数原型:
int pthread_spin_lock(pthread_spinlock_t *lock);
参数:
lock
:指向自旋锁对象的指针。返回值:
成功:返回
0
。失败:返回错误码(如
EINVAL
表示参数无效)。
pthread_spin_trylock
用途:尝试获取自旋锁。如果锁已被占用,则立即返回,不会阻塞。
函数原型:
int pthread_spin_trylock(pthread_spinlock_t *lock);
参数:
lock
:指向自旋锁对象的指针。返回值:
成功:返回
0
。锁已被占用:返回
EBUSY
。失败:返回其他错误码。
pthread_spin_unlock
用途:释放自旋锁。
函数原型:
int pthread_spin_unlock(pthread_spinlock_t *lock);
参数:
lock
:指向自旋锁对象的指针。返回值:
成功:返回
0
。失败:返回错误码(如
EINVAL
表示参数无效)。
pthread_spin_init
用途:初始化自旋锁。
函数原型:
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);
参数:
lock
:指向自旋锁对象的指针。
pshared
:指定锁是否可以在不同进程间共享:
PTHREAD_PROCESS_PRIVATE
:锁仅在当前进程内共享(默认值)。
PTHREAD_PROCESS_SHARED
:锁可以在多个进程间共享。返回值:
成功:返回
0
。失败:返回错误码(如
ENOMEM
表示内存不足)。
pthread_spin_destroy
用途:销毁自旋锁,释放相关资源。
函数原型:
int pthread_spin_destroy(pthread_spinlock_t *lock);
参数:
lock
:指向自旋锁对象的指针。返回值:
成功:返回
0
。失败:返回错误码(如
EINVAL
表示参数无效)。
短暂等待:适用于锁被占用时间很短的场景,如多线程对共享数据进行简单的读写操作。
多线程锁使用:通常用于系统底层,同步多个 CPU 对共享资源的访问。
样例代码:售票系统
#include
#include
#include
#include
#include
// 共享变量:剩余票数
int ticket = 1000;
// 自旋锁
pthread_spinlock_t lock;
// 线程函数:模拟售票
void *sell_ticket(void *arg)
{
char *thread_id = (char *)arg;
while (1)
{
// 尝试获取锁
pthread_spin_lock(&lock);
if (ticket > 0)
{
// 模拟售票操作
usleep(1000);
printf("%s sells ticket: %d\n", thread_id, ticket);
ticket--;
}
else
{
// 如果票卖完了,释放锁并退出
pthread_spin_unlock(&lock);
break;
}
// 释放锁
pthread_spin_unlock(&lock);
}
return NULL;
}
int main(void)
{
// 初始化自旋锁
pthread_spin_init(&lock, PTHREAD_PROCESS_PRIVATE);
// 创建多个线程模拟售票
pthread_t t1, t2, t3, t4;
pthread_create(&t1, NULL, sell_ticket, (void *)"Thread 1");
pthread_create(&t2, NULL, sell_ticket, (void *)"Thread 2");
pthread_create(&t3, NULL, sell_ticket, (void *)"Thread 3");
pthread_create(&t4, NULL, sell_ticket, (void *)"Thread 4");
// 等待所有线程结束
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
pthread_join(t4, NULL);
// 销毁自旋锁
pthread_spin_destroy(&lock);
return 0;
}