计算机操作系统基础(十二)---线程同步之自旋锁

引言

本文为第十二篇,线程同步之自旋锁,在上一篇文章介绍了互斥量,通过互斥量解决线程同步的问题。本文是另一个解决线程同步的方法---自旋锁

自旋锁

自旋锁的工作原理跟互斥量的工作原理其实是一模一样的,它也是在访问临界资源之前加一个锁,完成之后再将锁给释放掉。但是互斥量和自旋锁还是有一点区别的

  • 自旋锁也是一种多线程同步的变量
  • 使用了自旋锁的线程会反复检查锁变量是否可用,如果不可用就会循环反复的检查
  • 因此自旋锁不会让出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;
}

执行结果

在快速变化的技术中寻找不变,才是一个技术人的核心竞争力。知行合一,理论结合实践

119 声望
32 粉丝
0 条评论
推荐阅读
Go编译原理系列10(逃逸分析)
在上一篇文章中分享了编译器的优化方法之一:函数内联,本文分享编译器的另一个优化方法:逃逸分析。逃逸分析是Go语言编译过程中比较重要的一个优化阶段,它主要用于标识变量应该被分配到栈上还是堆上

书旅阅读 485

One 一个简洁的博客、微博客系统
代码:[链接]文档:[链接]系统预览首页:微博列表:微博详细:文章列表:文章详细:归档:搜索,目前只能依据分类、标签搜索😀:管理后台:

Eyeswap45阅读 2.3k评论 1

怎样用 PHP 来实现枚举?
在数学和计算机科学理论中,一个集的枚举是列出某些有穷序列集的所有成员的程序,或者是一种特定类型对象的计数。这两种类型经常(但不总是)重叠。枚举是一个被命名的整型常数的集合,枚举在日常生活中很常见,...

唯一丶25阅读 6.4k评论 4

PHP转Go实践:xjson解析神器「开源工具集」
我和劲仔都是PHP转Go,身边越来越多做PHP的朋友也逐渐在用Go进行重构,重构过程中,会发现php的json解析操作(系列化与反序列化)是真的香,弱类型语言的各种隐式类型转换,很大程度的减低了程序的复杂度。

王中阳Go10阅读 1.9k评论 2

封面图
图片防盗链破解 解决图片防盗链问题 反向代理
当客户端(浏览器)向服务器请求内容的时候,会提交一个header,这个header中包含了如:浏览器信息、cookie等内容,那么有一个叫referer的东东,也包含在这里面。

TANKING7阅读 11.3k评论 5

Git操作不规范,战友提刀来相见!
年终奖都没了,还要扣我绩效,门都没有,哈哈。这波骚Git操作我也是第一次用,担心闪了腰,所以不仅做了备份,也做了笔记,分享给大家。问题描述小A和我在同时开发一个功能模块,他在优化之前的代码逻辑,我在开...

王中阳Go5阅读 2.3k评论 2

封面图
微信公众号开发:自动回复文本/图片/图文消息/关键词回复/上传素材/自定义菜单
对接流程1、申请微信公众号测试账号URL:[链接]2、登录,配置开发者服务器URL和Token开发者服务器配置代码:config.php {代码...} URL是config.php在你服务器的URLToken是上面代码自己设置的Token搞定之后,就能完...

TANKING2阅读 10.1k

119 声望
32 粉丝
宣传栏