Linux线程同步(5)——互斥锁or自旋锁?

news2024/11/30 8:37:38

自旋锁概述         

        自旋锁与互斥锁很相似,从本质上说也是一把锁,在访问共享资源之前对自旋锁进行上锁,在访问完成后释放自旋锁(解锁);事实上,从实现方式上来说,互斥锁是基于自旋锁来实现的,所以自旋锁相较于互斥锁更加底层。

        如果在获取自旋锁时,自旋锁处于未锁定状态,那么将立即获得锁(对自旋锁上锁);如果在获取自旋锁时,自旋锁已经处于锁定状态了,那么获取锁操作将会在原地“自旋”,直到该自旋锁的持有者释放了锁。 由此介绍可知,自旋锁与互斥锁相似,但是互斥锁在无法获取到锁时会让线程陷入阻塞等待状态;而自旋锁在无法获取到锁时,将会在原地“自旋”等待。“自旋”其实就是调用者一直在循环查看该自旋锁的持有者是否已经释放了锁,“自旋”一词因此得名。

        自旋锁的不足之处在于:自旋锁一直占用的 CPU,它在未获得锁的情况下,一直处于运行状态(自旋), 所以占着 CPU,如果不能在很短的时间内获取锁,这无疑会使 CPU 效率降低。

        试图对同一自旋锁加锁两次必然会导致死锁,而试图对同一互斥锁加锁两次不一定会导致死锁,原因在于互斥锁有不同的类型,当设置为 PTHREAD_MUTEX_ERRORCHECK 类型时,会进行错误检查,第二次加锁会返回错误,所以不会进入死锁状态。

        因此我们要谨慎使用自旋锁,自旋锁通常用于以下情况:需要保护的代码段执行时间很短,这样就会使得持有锁的线程会很快释放锁,而“自旋”等待的线程也只需等待很短的时间;在这种情况下就比较适合使用自旋锁,效率高! 综上所述,再来总结下自旋锁与互斥锁之间的区别:

  • 实现方式上的区别:互斥锁是基于自旋锁而实现的,所以自旋锁相较于互斥锁更加底层;
  • 开销上的区别:获取不到互斥锁会陷入阻塞状态(休眠),直到获取到锁时被唤醒;而获取不到自旋锁会在原地“自旋”,直到获取到锁;休眠与唤醒开销是很大的,所以互斥锁的开销要远高于自 旋锁、自旋锁的效率远高于互斥锁;但如果长时间的“自旋”等待,会使得 CPU 使用效率降低, 故自旋锁不适用于等待时间比较长的情况。
  • 使用场景的区别:自旋锁在用户态应用程序中使用的比较少,通常在内核代码中使用比较多;因为自旋锁可以在中断服务函数中使用,而互斥锁则不行,在执行中断服务函数时要求不能休眠、不能被抢占(内核中使用自旋锁会自动禁止抢占),一旦休眠意味着执行中断服务函数时主动交出了 CPU使用权,休眠结束时无法返回到中断服务函数中,这样就会导致死锁!

自旋锁初始化

        自旋锁使用 pthread_spinlock_t 数据类型表示,当定义自旋锁后,需要使用 pthread_spin_init ()函数对其进行初始化,当不再使用自旋锁时,调用 pthread_spin_destroy()函数将其销毁,其函数原型如下所示:

#include <pthread.h>

int pthread_spin_destroy(pthread_spinlock_t *lock);
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);

        参数 lock 指向了需要进行初始化或销毁的自旋锁对象,参数 pshared 表示自旋锁的进程共享属性,可以取值如下:

  • PTHREAD_PROCESS_SHARED:共享自旋锁。该自旋锁可在多个进程中的线程之间共享;
  • PTHREAD_PROCESS_PRIVATE:私有自旋锁。只有本进程内的线程才能够使用该自旋锁。

这两个函数在调用成功的情况下返回 0;失败将返回一个非 0 值的错误码。

自旋锁加锁和解锁

        可以使用 pthread_spin_lock()函数或 pthread_spin_trylock()函数对自旋锁进行加锁,前者在未获取到锁时 一直“自旋”;对于后者,如果未能获取到锁,就立刻返回错误,错误码为 EBUSY。不管以何种方式加锁, 自旋锁都可以使用 pthread_spin_unlock()函数对自旋锁进行解锁。其函数原型如下所示:

#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);

        参数 lock 指向自旋锁对象,调用成功返回 0,失败将返回一个非 0 值的错误码。

         如果自旋锁处于未锁定状态,调用 pthread_spin_lock()会将其锁定(上锁),如果其它线程已将自旋锁锁住了,那本次调用将会“自旋”等待;如果试图对同一自旋锁加锁两次必会导致死锁。

        使用示例

        对《线程同步(2)——初识互斥锁》代码进行修改,使用自旋锁替换互斥锁来实现线程同步,对共享资源的访问进行保护。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>

static pthread_spinlock_t spin;
static int g_count = 0;

static void *new_thread_start(void *arg){
    int loops = *((int *)arg);
    int l_count, j;
    for (j = 0; j < loops; j++) {
        pthread_spin_lock(&spin);//自旋锁上锁
        l_count = g_count;
        l_count++;
        g_count = l_count;
        pthread_spin_unlock(&spin);//自旋锁解锁
    }
    return (void *)0;
}
static int loops;
int main(int argc, char *argv[]){
    pthread_t tid1, tid2;
    int ret;
    /* 获取用户传递的参数 */
    if (2 > argc)
        loops = 10000000; //没有传递参数默认为 1000 万次
    else
        loops = atoi(argv[1]);
 
    /* 初始化自旋锁(私有) */
    pthread_spin_init(&spin,PTHREAD_PROCESS_PRIVATE);
    
    /* 创建 2 个新线程 */
    ret = pthread_create(&tid1, NULL, new_thread_start, &loops);
    if (ret) {
        fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
        exit(-1);
    }
    ret = pthread_create(&tid2, NULL, new_thread_start, &loops);
    if (ret) {
        fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
        exit(-1);
    }
    /* 等待线程结束 */
    ret = pthread_join(tid1, NULL);
    if (ret) {
        fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
        exit(-1);
    }
    ret = pthread_join(tid2, NULL);
    if (ret) {
        fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
        exit(-1);
    }
    /* 打印结果 */
    printf("g_count = %d\n", g_count);

    /* 销毁自旋锁 */
    pthread_spin_destroy(&spin);
    exit(0);
}

 运行结果:

        将互斥锁替换为自旋锁之后,测试结果打印也是没有问题的,并且通过对比可以发现,替换为自旋锁之后,程序运行所耗费的时间明显变短了,说明自旋锁确实比互斥锁效率要高,但是一定要注意自旋锁所适用的场景。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/523433.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

shell脚本之“sort“、“uniq“、“tr“、“cut“、“split“、“paste“以及“eval“命令详解

文章目录 sort命令uniq命令tr命令cut命令split命令paste命令eval命令总结 sort命令 以行为单位对文件内容进行排序&#xff0c;也可以根据不同的数据类型来排序. 比较原则&#xff1a;从首字符向后&#xff0c;依次按ASCII码值进行比较&#xff0c;最后将他们按升序输出. 语法…

Chrome和edge报STATUS_STACK_BUFFER_OVERRUN错误的处理办法

Chrome和edge突然就报STATUS_STACK_BUFFER_OVERRUN错误&#xff0c;原因未知。 解决方案&#xff1a; Chrome 卸载本地的chrome访问https://www.chromedownloads.net/chrome64win/&#xff08;windows64&#xff09;https://www.chromedownloads.net/chrome32win/&#xff0…

母亲节到了,写一个简单的C++代码给老妈送上一个爱心祝福

&#x1f34e; 博客主页&#xff1a;&#x1f319;披星戴月的贾维斯 &#x1f34e; 欢迎关注&#xff1a;&#x1f44d;点赞&#x1f343;收藏&#x1f525;留言 &#x1f347;系列专栏&#xff1a;&#x1f319; C/C专栏 &#x1f319;请不要相信胜利就像山坡上的蒲公英一样唾…

快速上手Arthas

目录 基本概述 安装方式 基础指令 jvm相关指令 class/classloader相关指令 monitor/watch/trace相关指令 其他 基本概述 jconsole等工具都必须在服务端项目进程中配置相关的监控参数&#xff0c;然后工具通过远程连接到项目进程&#xff0c;获取相关的数据。这样就会带…

快速查询的秘籍——B+树索引

页和记录的关系示意图 InnoDB根据主键查找数据的过程是什么&#xff1f; 没有索引的查找是什么&#xff1f;索引查找和通过主键查找有什么关系&#xff1f; 索引是解决什么问题的&#xff1f; 索引是解决定位数据页的&#xff0c;而不是定位一个页中的数据的&#xff0c;定位…

MATLAB绘制动画(一)质点动画

vx 100*cos(1/3*pi); vy 100*sin(1/3*pi); t 0:0.005:18; x vx*t; y vy*-9.8*t.^2/2; comet(x,y) 这里只是截取了最后的画面&#xff0c;正常运行时&#xff0c;可以看到从最高点向下落的动作。 想要了解这段代码&#xff0c;我们要知道comet函数的意义 这个函数可以沿着…

ChatGPT 发布重磅更新,插件系统即将上线!

公众号关注 “GitHubDaily” 设为 “星标”&#xff0c;每天带你逛 GitHub&#xff01; 昨天凌晨&#xff0c;ChatGPT 为诸多 Plus 会员陆续开放了插件系统内测权限&#xff0c;申请比较早的用户&#xff0c;现在应该都能体验上最新的插件系统了。 为了让风暴来得更为猛烈&…

SQL在线刷题

牛客网学习SQL在线编程&#xff0c;牛客网在线编程&#xff0c;一共82道 用于实践的网站&#xff0c;在线运行SQL 目前43道&#xff0c;刷不动了&#xff0c;剩下的之后找机会搞 只记录有疑问的题目 简单 SQL196 查倒数第三 查找入职员工时间排名倒数第三的员工所有信息 …

js堆和栈

目录 关键句提取&#xff1a; 一、认识堆和栈 1、内存操作场景 2、数据结构场景 二、堆和栈的优缺点 1.栈(stack) 2.堆(heap) 3.总结&#xff1a; 三、堆和栈的溢出 四、 传值和传址 五、为什么会有栈内存和堆内存之分&#xff1f; 垃圾回收 标记清理 引用…

品牌控价的好处有哪些、品牌控价方法有哪些

今天和大家聊聊【品牌控价】&#xff0c;他们常会说到自己的产品有多好&#xff0c;经销商们体验完也说产品效果非常不错&#xff0c;价格在业内也是有很大优势&#xff0c;但是客户购买量和预期效果确有很大差距&#xff0c;难道我产品性价比这个高&#xff0c;还不能打动顾客…

一种不需要注册没有魔法使用ChatGPT的方法

关于我&#xff1a;关注AIGC、读书、成长和自媒体。加我微信&#xff1a;keeepdance&#xff0c;备注&#xff1a;chatgpt。进ChatGPT交流群。 如果你还没有使用过ChatGPT&#xff0c;那你来对了地方。文章结尾&#xff0c;我将提供一种能不需要梯子、不需要注册&#xff0c;无…

【数据结构.C】顺序表和单链表的增删查改

宝子&#xff0c;你不点个赞吗&#xff1f;不评个论吗&#xff1f;不收个藏吗&#xff1f; 最后的最后&#xff0c;关注我&#xff0c;关注我&#xff0c;关注我&#xff0c;你会看到更多有趣的博客哦&#xff01;&#xff01;&#xff01; 喵喵喵&#xff0c;你对我真的很重要…

干货! CVPR:基于VDB的高效神经辐射渲染场

点击蓝字 关注我们 AI TIME欢迎每一位AI爱好者的加入&#xff01; 作者介绍 严 涵 上海交通大学2019级人工智能专业本科生&#xff0c;研究兴趣主要是与NeRF相关的三维重建算法。 报告题目 基于VDB高效神经辐射渲染场 内容简介 01 NeRF NeRF的提出起初主要是为了解决新视⻆生成…

shell脚本常用的命令

管理文件内容的使用 一、sort命令二、uniq命令三、tr命令四、cut命令五、split命令六、paste命令七、eval命令 一、sort命令 sort命令是以行为单位对文件内容进行排序&#xff0c;也可以根据不同的数据类型来排序&#xff0c;比较原则是从首字符向后&#xff0c;依次按ASCII码…

尚硅谷Vue配套资源

1.gitee地址&#xff1a; https://gitee.com/coderPatrickStar/Vue/tree/master/%E5%B0%9A%E7%A1%85%E8%B0%B7Vue%E9%85%8D%E5%A5%97%E8%B5%84%E6%BA%90 2.

Linux常用命令——id命令

在线Linux命令查询工具 id 显示用户的ID以及所属群组的ID 补充说明 id命令可以显示真实有效的用户ID(UID)和组ID(GID)。UID 是对一个用户的单一身份标识。组ID&#xff08;GID&#xff09;则对应多个UID。id命令已经默认预装在大多数Linux系统中。要使用它&#xff0c;只需…

编译一个开源软件遇到的问题总结

一、开源软件Supra 需要编译一个开源的软件Supra&#xff0c;它需要的前置条件是&#xff1a; 1、cmake ≥ 3.4 2、gcc ≥ 4.8 or min. Visual Studio 2015 (Compiler needs to be supported by CUDA! For that, see the CUDA installation instructions.) 3、QT ≥ 5.5 4、TB…

infuluxdb时序数据库介绍

时序数据库&#xff08;influxdb&#xff09; InfluxDB是一个开源的、高性能的时序型数据库&#xff0c;在时序型数据库DB-Engines Ranking上排名第一。 下载地址:https://dl.influxdata.com/influxdb/releases/influxdb2-2.3.0-windows-amd64.zip 启动&#xff1a; CMD到解压…

游戏服务器被攻击怎么办

游戏服务器的安全是每个游戏开发者和管理员必须关注的问题。然而&#xff0c;尽管有各种防御措施&#xff0c;游戏服务器仍然可能受到攻击。小编将为您介绍游戏服务器被攻击的原因&#xff0c;并提供一些解决方案。 游戏服务器被攻击的原因 1. DDoS 攻击 DDoS 攻击是最常见的攻…

【SSA-LSTM】基于SSA-LSTM预测研究(Python代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…