1、概念
1.1 定义
自旋锁(Spinlock)是一种特殊的锁机制,当线程尝试获取锁而锁不可用时,线程会进入忙等待(即循环检查锁是否可用),而不是进入睡眠状态。这种机制适用于锁持有时间非常短的场景,因为它避免了线程上下文切换的开销。然而,如果锁持有时间较长,自旋锁可能会导致CPU资源的浪费。
1.2 特点
- 忙等待:自旋锁会在获取锁时不断循环检查锁的状态,直到获取到锁为止。这种忙等待的方式可以减少线程切换的开销,适用于对锁的占用时间较短的情况。
- 无阻塞:自旋锁不会将线程阻塞,因此适用于对锁的占用时间较短、竞争不激烈的情况。
- 适用于多核CPU:自旋锁在多核CPU上效果更好,因为在一个核上的线程忙等待时,其他核上的线程可以继续执行。
1.3 应用场景
- 短期占用:适用于对锁的占用时间较短的情况,避免线程切换的开销。
- 低竞争:适用于竞争不激烈的情况,避免线程频繁地阻塞和唤醒。
- 多核CPU:在多核CPU上,自旋锁的效率更高,因为可以充分利用多核并行执行的优势
2、常用接口
2.1 pthread_spin_init
初始化一个自旋锁。
pthread_spin_init(pthread_spinlock_t *lock, int pshared)
- 入参:
- lock:指向要初始化的自旋锁的指针。
- pshared:指定锁的共享属性,通常设置为PTHREAD_PROCESS_PRIVATE。
- 返回值:若成功,返回0;否则返回错误码。
2.2 pthread_spin_lock
获取自旋锁。
pthread_spin_lock(pthread_spinlock_t *lock)
- 入参:
- lock:指向要获取的自旋锁的指针。
- 返回值:若成功,返回0;否则返回错误码。
2.3 pthread_spin_trylock
尝试获取自旋锁,如果锁已被其他线程占用,则立即返回。
pthread_spin_trylock(pthread_spinlock_t *lock)
- 入参:
- lock:指向要尝试获取的自旋锁的指针。
- 返回值:若成功获取锁,返回0;若锁已被占用,返回EBUSY;其他情况返回错误码。
2.4 pthread_spin_unlock
释放自旋锁。
pthread_spin_unlock(pthread_spinlock_t *lock)
- 作用:释放自旋锁。
- 入参:
- lock:指向要释放的自旋锁的指针。
- 返回值:若成功,返回0;否则返回错误码。
2.5 pthread_spin_destroy
销毁一个已经初始化的自旋锁。
pthread_spin_destroy(pthread_spinlock_t *lock)
- 作用:销毁一个已经初始化的自旋锁。
- 入参:
- lock:指向要销毁的自旋锁的指针。
- 返回值:若成功,返回0;否则返回错误码。
3、编程测试
分别测试使用自旋锁和不使用自旋锁操作全局变量的情况。定义一个全局共享变量shared_resource
和两个线程都将访问并修改它。使用pthread_spinlock_t
类型的变量spinlock
作为自旋锁。在thread_function
函数中,线程首先尝试使用pthread_spin_lock
函数获取自旋锁,如果锁不可用,线程会忙等待直到锁可用。获取锁后,线程访问并修改共享资源,然后释放锁,使用pthread_spin_unlock
函数。最后,主线程等待两个工作线程完成,并输出最终的共享资源值。
先将自旋锁代码注释掉,测试程序:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
// 全局共享变量
int shared_resource = 0;
// 自旋锁
pthread_spinlock_t spinlock;
// 线程函数
void* thread_function(void* arg)
{
int thread_id = *(int*)arg;
// 尝试获取自旋锁
// pthread_spin_lock(&spinlock);
// 访问共享资源
for (int i = 0; i < 100000; i++)
{
shared_resource += 1;
}
printf("Thread %d finished, shared_resource = %d\n", thread_id, shared_resource);
// 释放自旋锁
// pthread_spin_unlock(&spinlock);
return NULL;
}
int main()
{
// 初始化自旋锁
if (pthread_spin_init(&spinlock, 0) != 0)
{
printf("Spinlock initialization failed\n");
return 1;
}
// 创建两个线程
pthread_t thread1, thread2;
int thread1_id = 1, thread2_id = 2;
if (pthread_create(&thread1, NULL, thread_function, &thread1_id) != 0)
{
printf("Thread 1 creation failed\n");
return 1;
}
if (pthread_create(&thread2, NULL, thread_function, &thread2_id) != 0)
{
printf("Thread 2 creation failed\n");
return 1;
}
// 等待线程结束
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
// 销毁自旋锁
pthread_spin_destroy(&spinlock);
printf("Final shared_resource value: %d\n", shared_resource);
return 0;
}
测试结果异常,结果不可控:
去掉注释测试自旋锁功能:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
// 全局共享变量
int shared_resource = 0;
// 自旋锁
pthread_spinlock_t spinlock;
// 线程函数
void* thread_function(void* arg)
{
int thread_id = *(int*)arg;
// 尝试获取自旋锁
pthread_spin_lock(&spinlock);
// 访问共享资源
for (int i = 0; i < 100000; i++)
{
shared_resource += 1;
}
printf("Thread %d finished, shared_resource = %d\n", thread_id, shared_resource);
// 释放自旋锁
pthread_spin_unlock(&spinlock);
return NULL;
}
int main()
{
// 初始化自旋锁
if (pthread_spin_init(&spinlock, 0) != 0)
{
printf("Spinlock initialization failed\n");
return 1;
}
// 创建两个线程
pthread_t thread1, thread2;
int thread1_id = 1, thread2_id = 2;
if (pthread_create(&thread1, NULL, thread_function, &thread1_id) != 0)
{
printf("Thread 1 creation failed\n");
return 1;
}
if (pthread_create(&thread2, NULL, thread_function, &thread2_id) != 0)
{
printf("Thread 2 creation failed\n");
return 1;
}
// 等待线程结束
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
// 销毁自旋锁
pthread_spin_destroy(&spinlock);
printf("Final shared_resource value: %d\n", shared_resource);
return 0;
}
测试结果正常:
4、总结
本文讲解了Linux线程同步中使用的自旋锁的定义和应用场景,列举了编程中常用的接口,并编写测试用例进行测试。