引言
本文为第十二篇,线程同步之自旋锁,在上一篇文章介绍了互斥量,通过互斥量解决线程同步的问题。本文是另一个解决线程同步的方法—自旋锁
自旋锁
自旋锁的工作原理跟互斥量的工作原理其实是一模一样的,它也是在访问临界资源之前加一个锁,完成之后再将锁给释放掉。但是互斥量和自旋锁还是有一点区别的
- 自旋锁也是一种多线程同步的变量
- 使用了自旋锁的线程会反复检查锁变量是否可用,如果不可用就会循环反复的检查
- 因此自旋锁不会让出CPU,是一种忙等待状态
因此自旋锁其实就是:死循环等待锁被释放
自旋锁好处
- 自旋锁避免了进程或线程上下文切换的开销(如果这个锁占用的时间不是很长,这个代价还是很小的)
- 操作系统内部很多地方都是使用自旋锁,而不是互斥量
- 自旋锁不适合在单核CPU使用(因为自旋锁在等待的时候并不会释放CPU,如果在单核CPU使用的话会引起其它的进程或线程无法执行)
自旋锁和互斥量(锁)的比较
- 自旋锁是一种非阻塞锁,也就是说,如果某线程需要获取自旋锁,但该锁已经被其他线程占用时,该线程不会被挂起,而是在不断的消耗CPU的时间,不停的试图获取自旋锁
- 互斥量是阻塞锁,当某线程无法获取互斥量时,该线程会被直接挂起,该线程不再消耗CPU时间,当其他线程释放互斥量后,操作系统会激活那个被挂起的线程,让其投入运行
两种锁适用的场景
- 如果是多核处理器,如果预计线程等待锁的时间很短,短到比线程两次上下文切换时间要少的情况下,使用自旋锁是划算的
- 如果是多核处理器,如果预计线程等待锁的时间较长,至少比两次线程上下文切换的时间要长,建议使用互斥量
- 如果是单核处理器,一般建议不要使用自旋锁。因为,在同一时间只有一个线程是处在运行状态,那如果运行线程发现无法获取锁,只能等待解锁,但因为自身不挂起,所以那个获取到锁的线程没有办法进入运行状态,只能等到运行线程把操作系统分给它的时间片用完,才能有机会被调度。这种情况下使用自旋锁的代价很高
建议:如果加锁的代码经常被调用,但竞争情况很少发生时,应该优先考虑使用自旋锁,自旋锁的开销比较小,互斥量的开销较大
自旋锁的代码示例
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
$include<vector>
//自旋锁定义
pthread_spinlock_t spin_lock;
//临界资源
int num=0;
//生产者
void *producer(void*){
int times = 100000000;//循环一百万次
while(times--){
//加自旋锁
pthread_spin_lock(&spin_lock);
num += 1;//每次生产一个产品
//解锁
pthread_spin_unlock(&spin_lock);
}
}
//消费者
void *consumer(void*){
int times = 100000000;
while(times--){
//加自旋锁
pthread_spin_lock(&spin_lock);
num -= 1;//每次消费一个产品
//解锁
pthread_spin_unlock(&spin_lock);
}
}
int main()
{
printf("Start in main function.");
//初始化自旋锁
pthread_spin_init(&spin_lock, 0);
//定义两个线程
pthread_t thread1,thread2;
//一个执行生成者逻辑,一个执行消费者逻辑
pthread_create(&thread1, NULL, &producer, NULL);
pthread_create(&thread2, NULL, &consumer, NULL);
pthread_join(&thread1, NULL);
pthread_join(&thread2, NULL);
//打印临界资源的值
printf("Print in main function: num = %d\n", num);
return 0;
}
执行结果
在快速变化的技术中寻找不变,才是一个技术人的核心竞争力。知行合一,理论结合实践