0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

线程同步技术介绍

科技绿洲 来源:Linux开发架构之路 作者:Linux开发架构之路 2023-11-13 14:19 次阅读

正文:首先看一下问题引出,先看一些经典的问题.

多线程的隐患

首先我们利用多线程的话肯定是好处多多,因为我们可以同时去做一些事情,大大的提高了效率.像我们下载视频的时候就可以同时下载多个视频,这样是节省了很多时间,用户体验也会更好.但是用得时候也会存在一些安全隐患,比如同一块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源,这样会出现一些数据错乱和数据安全的问题.下面我们就看一些例子.

存钱取钱案例

比如我现在有1000元,同时有2个线程去处理,一个线程是取钱100元,一个线程是存钱100元,我制作了一张示意图如下:

图片

存钱取钱示意图

我们从上面的图应该很清楚,存100、取100的最终结果就是还是剩余1000元,但是看我们上面的示意图,最终结果要么是900要么是1100,这就与1000的结果对不上,所以很明显用多线程是会存在隐患,下面我们用代码演示一下上面的结果:

图片

卖票案例

这个和上面的稍微有点差别,因为上面的是2个操作,而卖票呢,它是1个操作.同样的比如我现在有1000张票,同时有2个线程去处理卖票,一个线程是一个线程卖票100张,另一个线程也是卖票100张,同时操作的话,也会出现异常,我制作了一张示意图如下:

图片

我们同样的也可以用代码演示一下效果:

图片

上面的两个,大家可以试试.接下来针对上面的问题,我们就引出了今天的主角,线程同步技术

线程同步技术

解决方案:使用线程同步技术 (同步,就是协同步调,按照预定的先后次序进行运行),我们先来看下线程同步技术有哪些方案:线程同步方案最常见的技术就是:加锁.大概方案如下(大致这么多方案,当然还有其他的)

OSSpinLock (自旋锁)

os_unfair_lock (互斥锁)

pthread_mutex (互斥锁、递归锁)(里面3种类型,目前只说2种对我们有用的)

dispatch_queue (DISPATCH_QUEUE_SERIAL)

NSLock

NSRecursiveLock

NSCondition

NSConditionLock

@synchronized

以上的这些都是可以做到线程同步方案,我会一个一个介绍,并且介绍它们的优缺点,性能怎么样,我们怎么去选择等等.我们就以上面的例子讲解.

OSSpinLock (自旋锁)

OSSpinLock 叫做 "自旋锁", 等待锁的状态会处于忙等( busy-wait )状态,一直占用着CPU内存.头文件导入,而且这个锁是过期锁,iOS10以后就过期了,但是我们还是来看看,因为面试中可能会遇到,用法如下:

初始化锁:OSSpinLock spinLock = OS_SPINLOCK_INIT;

加锁: OSSpinLockLock(&_spinLock);

解锁: OSSpinLockUnlock(&_spinLock);

我们先看卖票:

图片

我是加锁和解锁了,为什么上面的代码还有问题,有发现原因的吗?这是因为SpinLock是局部变量,所以我们进去都是初始化了一把新锁,这把锁并没有被使用过,是达不到加锁的目的.所以所有的线程都是用同一把锁才能达到加锁的目的.请看下面代码:

图片

确实是剩下85张,没有问题.原理是这样,每次执行saleTicket都会进入 //加锁: OSSpinLockLock(&_SpinLock) 这个代码,第一次进来是正常给_SpinLock加锁,第二次进来的时候,发现_SpinLock已经被人加了锁,它会在这边等待,等待这把锁被解锁,解锁完了以后然后它再去加锁,就这样依次进行,就保证了里面的那段代码同时只有一个线程在处理.这就解决了线程同步的问题.

接下来我们就看存钱和取钱的问题.存钱、取钱是2个操作,我们是用同一把锁还是2把锁?思考了这个问题我们就知道怎么做了,因为存钱和取钱是不能同时进行,所以我们就用同一把锁即可(2把锁是有问题的大家可以自己试试),请看下面的代码,我们验证一下:

图片

自旋锁"忙等"状态是怎么等呢?忙等就是一直忙碌,而且还在等待,类似这样while(锁还没有解开),就会一直执行,占用cpu,直到锁被放开.而且OSSpinLock是已经过期了,而且目前已经不再安全,可能会出现优先级反转的问题.

下面说一下线程的调度问题

图片

其实你看上面的图,如果随着时间的推移,操作系统把时间给thread1一点时间,再给thread2一点时间,再给thread3一点时间,而且这个时间周期非常短,就这样一直非常快的切换,这样下来给我们的感觉就是同时执行.这就是实现多线程的一个方案.也就是多线程的原理,我们也可以说这是时间片轮转调度算法.调用进程或者线程都是用这套算法

还有个就是线程的优先级问题,比如thread1的优先级比较高,那么操作系统就会给thread1多一点时间去执行.其他的线程就少一点时间去执行.这样的话,我们使用自旋锁就会存在一个优先级反转的问题.比如thread1优先级非常高,thread2优先级很低.首先是thread2先进去加锁,thread1再进来就会等thread2解锁,由于thread1的优先级非常高,CPU就把大量的时间给了thread1,此时可能导致thread2没有时间执行解锁,thread1就会一直执行等待,有点死锁的感觉.

这样大家想一想:如果优先级高的不是忙等,而是休眠,休息就不会占用CPU,那不就是解决了这个问题.

os_unfair_lock (互斥锁)

os_unfair_lock用于取代不安全OSSpinLock,是从iOS10开始支持.

从底层调用看,等待os_unfair_lock锁的线程会处于休眠状态,并非忙等(后面会证明一下)

它的用法和OSSpinLock非常像,需要倒入头文件,用法如下:

初始化锁:os_unfair_lock unfairLock = OS_UNFAIR_LOCK_INIT;

加锁: os_unfair_lock_lock(&_unfairLock);

解锁:os_unfair_lock_unlock(&_unfairLock);

下面我们就去看一下用法

图片

存钱和取钱也是一样的道理,我们可以自己试试.

pthread_mutex (互斥锁)

像这种pthread开头的一般都是跨平台的Windows、linux等等都是可以用的,mutex叫做"互斥锁",等待锁的线程会处于休眠状态

其实用法都是差不多,我们先来看下怎么用,这个稍微代码多一点点

//初始化属性
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
    //初始化锁
    pthread_mutex_init(&_mutex, &attr);(&attr也可以传NULL,这样的话,上面的都是默认的,上面可以都不用写)
    //加锁
    pthread_mutex_lock(&_mutex);
    //解锁
    pthread_mutex_unlock(&_mutex);
    //销毁相关资源
    pthread_mutexattr_destroy(&attr);
    pthread_mutex_destroy(&_mutex);

其中PTHREAD_MUTEX_NORMAL是锁的类型,后面会细说,先传默认PTHREAD_MUTEX_NORMAL

先看运行结果:

图片

没有问题,记得销毁哈,之前说的2个锁,没有提供销毁的方法,那我们就不写,如果提供了,我们还是写一下的好!

pthread_mutex (递归锁)

我们再看另一种情况,请看下面的代码:

图片

上面这些代码会出现什么情况?死锁,会出现相互等待的情况,只会输出第一个NSLog,遇到这种情况我们怎么解决才好呢?2把不同的锁即可解决问题,就是otherMutexTest里面一把锁,otherMutexTest2里面另一把锁就可以解决了,这个我就不截图了,我们可以自己试试

再看下面另一种情况:出现递归怎么办?如下图

图片

遇到上面的这种情况我们又怎么处理,如果就像截图那样的话,就会出现休眠等待.如果我们想执行下去,我们就是可以设置锁的类型来解决这个问题:一共3种类型如下

图片

我们只要把锁的类型换成递归锁,立刻就能解决这个问题,我们加一个递归停止条件,不然会一直运行

图片

还有一个注意的,这个允许重复加锁,一定是在同一个线程,如果是多个线程的话,就不行.递归锁:允许同一个线程对一把锁重复加锁.

自旋锁、互斥锁汇编分析

自旋锁:一直忙等,占用CPU内存,一直在执行代码;互斥锁:不等待,休眠.不执行代码.我们怎么去证明这个问题呢?我们可以从汇编实现上去证明这个问题,我们先看OSSpinLock自旋锁:

图片

首先我们如果用这个上面的来调试的话,是看不出来什么效果的,因为这里面都是一大段一大段汇编代码执行的,我们需要一句一句的执行汇编指令.就需要敲si,s是step的意思,代码一行一行的执行,如果只用s的话,就是一行oc代码执行,一行oc对应可能一大段汇编,所以我们还需要加i,i是instruction的意思是一行一行汇编指令执行,简称si. 还有个是nexti,它也是一行一行汇编指令执行,只是nexti它是遇到函数就会一下执行过去.因为我们要看函数实现,所以我们用si.

我们再看一下,我代码是怎么写的:

图片

我是创建了10个线程去执行卖票,而且在卖票中间sleep(100),这样是为了,第一条线程进去,我们不管,我们主要看第二条线程在这等待的时间,到底做了什么事.所以我们主要看第二条的汇编代码.sleep(100)是为了时间长点,方便我们能看出做什么事.如果时间太短,直接第二条线程就不等待,那我们就看不到效果,请看下面的结果

图片

从上面的结果看,进入OSSpinLockLock函数,它会一直在81aef那里一直循环执行,这是外循环,我们所说的自旋锁就是这样,一直循环执行,占用CPU内存.一旦有人放开这把锁就会条件循环结束,不会再执行循环.

接下来我们看看互斥锁pthread_mutex

查找的方法和上面的一样,我就截图最关键的图即可,请看下面:

图片

执行到最后,直接是callsys,调用系统的方法,是不是类似我之前说的runloop里面的休眠的方法,而且我们知道休眠是任何事情都不会做,不占用CPU内存,所以我们最后看到,我的模拟器立刻又弹出来了,说明确实是睡眠,不占用任何CPU内存.

os_unfair_lock_lock我们可以用上面的方法尝试,它的结果也是互斥锁.

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 数据
    +关注

    关注

    8

    文章

    6512

    浏览量

    87601
  • 操作系统
    +关注

    关注

    37

    文章

    6288

    浏览量

    121886
  • 代码
    +关注

    关注

    30

    文章

    4556

    浏览量

    66784
  • 多线程同步
    +关注

    关注

    0

    文章

    2

    浏览量

    5207
收藏 人收藏

    评论

    相关推荐

    一文详解Linux线程同步

    我们在工作中会经常遇到线程同步,那么到底什么是线程同步呢,线程同步的本质是什么,
    发表于 08-25 11:49 493次阅读

    线程编程之四 线程同步

    介绍最常用的四种:临界区(CCriticalSection) 事件(CEvent) 互斥量(CMutex) 信号量(CSemaphore)  通过这些类,我们可以比较容易地做到线程同步。A
    发表于 10-22 11:43

    Linux多线程线程同步

    。同一进程内的线程共享进程的地址空间。通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。调度和切换:线程
    发表于 12-08 14:14

    IOT-OS之RT-Thread--- 线程同步线程间通信

    rt_thread,下面要介绍线程间的同步与通信,线程同步对象rt_sem / rt_mutex / rt_event和
    发表于 07-02 06:15

    QNX环境下多线程编程

    介绍了QNX 实时操作系统和多线程编程技术,包括线程同步的方法、多线程程序的分析步骤、
    发表于 08-12 17:37 30次下载

    基于多线程技术的多串口通信

    介绍了一个多串口通信模块,该模块采用VC++6.0并结合多线程技术编写,用来处理从远程终端站上传来数据。同时良好的线程同步解决方法也保证了模
    发表于 02-21 15:52 35次下载

    基于多线程技术的多串口通信

    介绍了一个多串口通信模块,该模块采用VC++6.0并结合多线程技术编写,用来处理从远程终端站上传来数据。同时良好的线程同步解决方法也保证了模
    发表于 07-22 17:38 37次下载

    Linux多线程同步方法

    线程对共享相同内存操作时,就会出现多个线程对同一资源的使用,为此,需要对这些线程进行同步,以确保它们在访问共享内存的时候不会访问到无效的数值。
    发表于 08-08 14:17 1961次阅读

    Win32多线程同步技术浅析

    简要介绍了在Win32环境下多线程访问共享资源时的同步机制,讨论了主要的4种同步对象(临界区、互斥元、事件、信号量),并描述了它们的优缺点,给出了使用Win32 API函数操控这4种对
    发表于 11-14 10:55 31次下载
    Win32多<b class='flag-5'>线程</b><b class='flag-5'>同步</b><b class='flag-5'>技术</b>浅析

    java多线程同步方法

    二、为什么要线程同步 因为当我们有多个线程要同时访问一个变量或对象时,如果这些线程中既有读又有写操作时,就会导致变量值或对象的状态出现混乱,从而导致程序异常。举个例子,如果一个银行账户
    发表于 09-27 13:19 0次下载

    三种Linux中的常用多线程同步方式浅析

    嵌入式linux中文站给大家介绍三种Linux中的常用多线程同步方式:互斥量,条件变量,信号量。
    发表于 05-02 14:49 2897次阅读
    三种Linux中的常用多<b class='flag-5'>线程</b><b class='flag-5'>同步</b>方式浅析

    使用C#实现Interlocked线程同步的程序免费下载

    本文档的主要内容详细介绍的是使用C#实现Interlocked线程同步的程序免费下载。
    发表于 07-21 08:00 1次下载
    使用C#实现Interlocked<b class='flag-5'>线程</b><b class='flag-5'>同步</b>的程序免费下载

    RT-Thread文档_线程同步

    RT-Thread文档_线程同步
    发表于 02-22 18:29 1次下载
    RT-Thread文档_<b class='flag-5'>线程</b>间<b class='flag-5'>同步</b>

    线程同步的几种方法

    线程同步是指在多个线程并发执行的情况下,为了保证线程执行的正确性和一致性,需要采用特定的方法来协调线程之间的执行顺序和共享资源的访问。下面
    的头像 发表于 11-17 14:16 525次阅读

    线程如何保证数据的同步

    。本文将详细介绍线程数据同步的概念、问题、以及常见的解决方案。 一、多线程数据同步概念 在多线程
    的头像 发表于 11-17 14:22 353次阅读