目录
一、什么是自旋锁?
工作原理
优点与缺点
二、自旋锁的实现
三、内核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 <stdio.h>
#include <stdatomic.h>
#include <pthread.h>
#include <unistd.h>// 使用原子标志来模拟自旋锁
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;
#elseunsigned char __val;
#endif
} atomic_flag;
说明:
-
原子性:
atomic_flag_test_and_set是一个原子操作,确保在多线程环境中对标志位的读取和修改是不可分割的。 -
忙等待:如果锁被占用,线程会不断循环检查标志位,直到锁被释放。
三、内核API
#include <pthread.h>// 获取自旋锁
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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>// 共享变量:剩余票数
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;
}

