首页 > 其他分享 >iOS 多线程自己的理解

iOS 多线程自己的理解

时间:2023-07-21 16:05:07浏览次数:49  
标签:lock pthread iOS 互斥 理解 线程 自旋 多线程 spin


. 创建线程的平均开销:
内存堆栈: 主线程—— 1M , 子线程——512K

时间: 基本可以忽略不计

a. 不可改变的对象,通常是线程安全的

b. 主线程负责处理响应事件

线程安全的类和函数: NSArray, NSData, NSNumber.....

非线程安全: NSBundle, NSCoder, NSArchiver, NSMutableArray

只能用于主线程: NSAppleSript


注意使用NSlock和@synchronized指令时会使等待线程阻塞,阻塞后进入睡眠状态,使用时注意防止阻塞主线程,NSlock 可以使用tryLock防止阻塞当前线程。

自旋锁与互斥锁有点类似,只是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。

阻塞:阻塞调用是指调用结果返回之前,当前线程会被挂起(这里的挂起 为当前线程进入睡眠模式,等待调用结果完成 当前线程才能其他线程唤醒但是这种阻塞同步机制是比较耗费性能的,因为在阻塞和唤醒等状态转换中,是需要CPU指令进行帮忙实现,这要的调度是比较耗时的,因此这种策略是一种悲观策略,当然我们需要线程安全,又要高效,在一定情况下我们会采用非阻塞同步机制)。函数只有在得到结果之后才会返回。

非阻塞:非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。

 使用NSLock类
在Cocoa程序中NSLock中实现了一个简单的互斥锁。所有锁(包括NSLock)的接口实际上都是通过NSLocking协议定义的,它定义了lock和unlock方法。你使用这些方法来获取和释放该锁。

除了标准的锁行为,NSLock类还增加了tryLock和lockBeforeDate:方法。方法tryLock试图获取一个锁,但是如果锁不可用的时候,它不会阻塞线程。相反,它只是返回NO。而lockBeforeDate:方法试图获取一个锁,但是如果锁没有在规定的时间内被获得,它会让线程从阻塞状态变为非阻塞状态(或者返回NO)。

下面的例子显式了你可以是NSLock对象来协助更新一个可视化显式,它的数据结构被多个线程计算。如果线程没有立即获的锁,它只是简单的继续计算直到它可以获得锁再更新显式。

使用@synchronized指令
@synchronized指令是在Objective-C代码中创建一个互斥锁非常方便的方法。@synchronized指令做和其他互斥锁一样的工作(它防止不同的线程在同一时间获取同一个锁)。然而在这种情况下,你不需要直接创建一个互斥锁或锁对象。相反,你只需要简单的使用Objective-C对象作为锁的令牌,如下面例子所示:

- (void)myMethod:(id)anObj 
 { 
 @synchronized(anObj) 
 { 
 // Everything between the braces is protected by the @synchronized directive. 
 } 
 }


创建给@synchronized指令的对象是一个用来区别保护块的唯一标示符。如果你在两个不同的线程里面执行上述方法,每次在一个线程传递了一个不同的对象给anObj参数,那么每次都将会拥有它的锁,并持续处理,中间不被其他线程阻塞。然而,如果你传递的是同一个对象,那么多个线程中的一个线程会首先获得该锁,而其他线程将会被阻塞直到第一个线程完成它的临界区。

作为一种预防措施,@synchronized块隐式的添加一个异常处理例程来保护代码。该处理例程会在异常抛出的时候自动的释放互斥锁。这意味着为了使用@synchronized指令,你必须在你的代码中启用异常处理。了如果你不想让隐式的异常处理例程带来额外的开销,你应该考虑使用锁的类。

自旋锁和互斥锁区别 在iOS中 @synchronized和NSLock 和信号量均是使用 互斥锁来实现的

互斥锁,是一种信号量,常用来防止两个进程或线程在同一时刻访问相同的共享资源。可以保证以下三点:

原子性:把一个互斥量锁定为一个原子操作,这意味着操作系统(或pthread函数库)保证了如果一个线程锁定了一个互斥量,没有其他线程在同一时间可以成功锁定这个互斥量。

唯一性:如果一个线程锁定了一个互斥量,在它解除锁定之前,没有其他线程可以锁定这个互斥量。

非繁忙等待:如果一个线程已经锁定了一个互斥量,第二个线程又试图去锁定这个互斥量,则第二个线程将被挂起(不占用任何cpu资源),直到第一个线程解除对这个互斥量的锁定为止,第二个线程则被唤醒并继续执行,同时锁定这个互斥量。

其它方案的讨论:

a、NSCondition和NSConditionLock实际使用的性能表现并任何优势,然而条件锁的意义在于对信号量做了面向对象封装;

b、NSLock和NSRecursiveLock在性能表现上与mutex算比较接近,用法上也并无二致,因此,常规情况,NSRecursiveLock和mutex之间的选择,春哥以为更多是习惯和偏好的问题;

c、@synchronized似乎是这些方案当中性能表现最不佳的,那是不是应该完全抛弃呢?春哥倒不这么认为,@synchronized最大的特点在于“快捷”,同步语法仅仅需要一个对象(id指针)作为互斥量,而且还不限于实例对象,类对象也能够支持,这就使得类方法中做同步变得简单不少,block用法也使得代码更紧凑,内存管理更稳健,非常适合一些低频而又不得不同步的逻辑,比如单例初始化、启动加载等等。


得益于不进内核不挂起的方式,OSSpinLock有着优异的性能表现,然而在高并发执行(冲突概率大,竞争激烈)的时候,又或者代码片段比较耗时(比如涉及内核执行文件io、socket、thread等),就容易引发CPU占有率暴涨的风险,因此更适用于一些简短低耗时的代码片段;


iOS 多线程自己的理解_自旋锁

iOS 多线程自己的理解_互斥量_02

上图为OSSpinLock等待取锁时的耗时测试用例代码,下图为测试结果,图中可以看到,等待取锁时,如果异步线程比较耗时,CPU占有率会有一个飙升 (测试代码)  



综合上述分析与讨论,总结有以下几点原则:



1、总的来看,推荐pthread_mutex作为实际项目的首选方案;



2、对于耗时较大又易冲突的读操作,可以使用读写锁代替pthread_mutex;



3、如果确认仅有set/get的访问操作,可以选用原子操作属性;



4、对于性能要求苛刻,可以考虑使用OSSpinLock,需要确保加锁片段的耗时足够小;



5、条件锁基本上使用面向对象的NSCondition和NSConditionLock即可;



6、@synchronized则适用于低频场景如初始化或者紧急修复使用;


从以上三点,我们看出可以用互斥量来保证对变量(关键的代码段)的排他性访问。



Pthreads提供了多种锁机制:
(1) Mutex(互斥量):pthread_mutex_***
(2) Spin lock(自旋锁):pthread_spin_***
(3) Condition Variable(条件变量):pthread_con_***
(4) Read/Write lock(读写锁):pthread_rwlock_***

Pthreads提供的Mutex锁操作相关的API主要有:
pthread_mutex_lock (pthread_mutex_t *mutex);
pthread_mutex_trylock (pthread_mutex_t *mutex);
pthread_mutex_unlock (pthread_mutex_t *mutex);

Pthreads提供的与Spin Lock锁操作相关的API主要有:
pthread_spin_lock (pthread_spinlock_t *lock);
pthread_spin_trylock (pthread_spinlock_t *lock);
pthread_spin_unlock (pthread_spinlock_t *lock);

从 实现原理上来讲,Mutex属于sleep-waiting类型的锁。例如在一个双核的机器上有两个线程(线程A和线程B),它们分别运行在Core0和 Core1上。假设线程A想要通过pthread_mutex_lock操作去得到一个临界区的锁,而此时这个锁正被线程B所持有,那么线程A就会被阻塞 (blocking),Core0 会在此时进行上下文切换(Context Switch)将线程A置于等待队列中,此时Core0就可以运行其他的任务(例如另一个线程C)而不必进行忙等待。而Spin lock则不然,它属于busy-waiting类型的锁,如果线程A是使用pthread_spin_lock操作去请求锁,那么线程A就会一直在 Core0上进行忙等待并不停的进行锁请求,直到得到这个锁为止。

所以,自旋锁一般用用多核的服务器。

自旋锁(Spin lock)
自旋锁与互斥锁有点类似,只是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是 否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。其作用是为了解决某项资源的互斥使用。因为自旋锁不会引起调用者睡眠,所以自旋锁的效率远 高于互斥锁。虽然它的效率比互斥锁高,但是它也有些不足之处:
1、自旋锁一直占用CPU,他在未获得锁的情况下,一直运行--自旋,所以占用着CPU,如果不能在很短的时 间内获得锁,这无疑会使CPU效率降低。
2、在用自旋锁时有可能造成死锁,当递归调用时有可能造成死锁,调用有些其他函数也可能造成死锁,如 copy_to_user()、copy_from_user()、kmalloc()等。
因此我们要慎重使用自旋锁,自旋锁只有在内核可抢占式或SMP的情况下才真正需要,在单CPU且不可抢占式的内核下,自旋锁的操作为空操作。自旋锁适用于锁使用者保持锁时间比较短的情况下。
自旋锁的用法如下:
首先定义:spinlock_t x;
然后初始化:spin_lock_init(spinlock_t *x); //自旋锁在真正使用前必须先初始化
在2.6.11内核中将定义和初始化合并为一个宏:DEFINE_SPINLOCK(x)

获得自旋锁:spin_lock(x); //只有在获得锁的情况下才返回,否则一直“自旋”
spin_trylock(x); //如立即获得锁则返回真,否则立即返回假
释放锁:spin_unlock(x);

结合以上有以下代码段:

spinlock_t lock; //定义一个自旋锁
spin_lock_init(&lock);
spin_lock(&lock);
....... //临界区
spin_unlock(&lock); //释放锁

还有一些其他用法:
spin_is_locked(x)
//  该宏用于判断自旋锁x是否已经被某执行单元保持(即被锁),如果是, 返回真,否则返回假。
spin_unlock_wait(x)
//  该宏用于等待自旋锁x变得没有被任何执行单元保持,如果没有任何执行单元保持该自旋锁,该宏立即返回,否
//将循环 在那里,直到该自旋锁被保持者释放。

spin_lock_irqsave(lock, flags)
//  该宏获得自旋锁的同时把标志寄存器的值保存到变量flags中并失效本地中//断。相当于:spin_lock()+local_irq_save()
spin_unlock_irqrestore(lock, flags)
//  该宏释放自旋锁lock的同时,也恢复标志寄存器的值为变量flags保存的//值。它与spin_lock_irqsave配对使用。
//相当于:spin_unlock()+local_irq_restore()

spin_lock_irq(lock)
  //该宏类似于spin_lock_irqsave,只是该宏不保存标志寄存器的值。相当 //于:spin_lock()+local_irq_disable()
spin_unlock_irq(lock)
//该宏释放自旋锁lock的同时,也使能本地中断。它与spin_lock_irq配对应用。相当于: spin_unlock()+local_irq+enable()

spin_lock_bh(lock)
//  该宏在得到自旋锁的同时失效本地软中断。相当于: //spin_lock()+local_bh_disable()
spin_unlock_bh(lock)
//该宏释放自旋锁lock的同时,也使能本地的软中断。它与spin_lock_bh配对//使用。相当于:spin_unlock()+local_bh_enable()

spin_trylock_irqsave(lock, flags)
//该宏如果获得自旋锁lock,它也将保存标志寄存器的值到变量flags中,并且失//效本地中断,如果没有获得锁,它什么也不做。因此如果能够立即 获得锁,它等//同于spin_lock_irqsave,如果不能获得锁,它等同于spin_trylock。如果该宏//获得自旋锁lock,那需要 使用spin_unlock_irqrestore来释放。

spin_trylock_irq(lock)
//该宏类似于spin_trylock_irqsave,只是该宏不保存标志寄存器。如果该宏获得自旋锁lock,需要使用spin_unlock_irq来释放。
spin_trylock_bh(lock)
//  该宏如果获得了自旋锁,它也将失效本地软中断。如果得不到锁,它什么//也不做。因此,如果得到了锁,它等同于spin_lock_bh,如果得 不到锁,它等同//于spin_trylock。如果该宏得到了自旋锁,需要使用spin_unlock_bh来释放。
spin_can_lock(lock)
//  该宏用于判断自旋锁lock是否能够被锁,它实际是spin_is_locked取反。//如果lock没有被锁,它返回真,否则,返回 假。该宏在2.6.11中第一次被定义,在//先前的内核中并没有该宏。



标签:lock,pthread,iOS,互斥,理解,线程,自旋,多线程,spin
From: https://blog.51cto.com/u_16124099/6801685

相关文章

  • bellman-ford算法理解
    bellman-ford算法理解从本题谈起再回归到最短路。本题为限制边数的最短路,是这个算法优势领域的题目。为什么它能解决?最外层每循坏一次,就是各点向外走一条边,内层对边的遍历是对所有边进行松弛操作,每次进行该操作时,需要用到备份数组,目的是防止连锁反应,保证每次每个点到起点的距离......
  • 计算凸多边形的重叠面积(原理解析)
    版权声明:遵循CC4.0BY-SA版权协议,转载请附上原文出处链接和本声明。参考文章:https://blog.csdn.net/xuyin1204/article/details/107768030本文主要是参考了CSDN博主xuyin1204关于计算两个多边形的重叠面积的文章,并做了原理的相关分析。代码放在文末首先将两个多边形分解为......
  • 如何理解小程序插件?微信及支付宝官方详解
    一、小程序插件功能介绍1、如何理解插件插件,英文名可称作“Plug-in、Plugin、add-in、addin、add-on、addon或extension”,是一个依附于主程序的辅助程序,透过和主程序的互动,用来代替主程序需要增加一些所需的特定功能。更通俗的来讲,就类似机器的零件,可以“插入”的形式添加到程......
  • vue2 使用axios
    如何在Vue2中使用Axios简介在Vue2中使用Axios是一种常见的方法来处理网络请求。Axios是一个基于Promise的HTTP客户端,可以用于浏览器和Node.js。它提供了一种简单和直观的方法来发送HTTP请求,并处理响应。这篇文章将指导你如何在Vue2中使用Axios来进行网络请求。步骤下面是使用A......
  • 实验五 Java多线程程序设计实验总结
    Java多线程程序设计实验总结引言多线程是计算机科学中重要的概念,它允许同时执行多个任务,从而提高程序的效率和性能。在Java中,多线程被广泛应用于各种场景,例如并发编程、网络编程等。本文将通过实验五的实践经验,介绍Java多线程程序设计的基本原理和常用技巧,并提供代码示例以加深......
  • 踩坑记录,axios post方法请求参数出现在地址栏的问题
    某天使用axios做post请求接口突然不好使了,总是调不通,并且参数都是出现在访问地址后,如图: 找了半天,原来是调用api的时候,参数使用错误:由于post 请求接收params参数和data参数,这里是cv上面get请求的方法,只修改method为post,下面的params忘记改成data了!,导致axios拿到params后直接......
  • L4168BIOS
    L4168BIOS简介及代码示例一、L4168BIOS简介L4168BIOS是一款用于嵌入式系统的基本输入输出系统(BIOS),它提供了对硬件设备的底层控制和管理功能。L4168BIOS通常嵌入在主板上的存储器中,当计算机启动时,会首先加载并执行L4168BIOS。L4168BIOS的主要功能包括:初始化硬件设备、提供中断服......
  • 轻松理解Java中的public、private、static和final
    一、概念1、public和private两个都是访问权限修饰符,用于控制外界对类内部成员的访问。public:表明对象成员是完全共有的,外界可以随意访问。用public修饰的数据成员、成员函数是对所有用户开放的,所有用户都可以直接进行调用。private:表明对象成员是完全私有的,不容许外界的任何......
  • 混合波束成形| 通过天线空间方向图理解波束成形的物理意义
    波束成形的物理意义如图,是在各种教材中经常看到的天线方向图。上图表示的是当前天线经过波束成形后在空间中指向30度方向(一般考虑的方向是0-180度)。这里解释一下天线方向:不失一般性的,一个MISO系统的接收信号(简洁起见,省略噪声)可以表示为:y......
  • 由浅入深:Stable-Diffusion 原理解析01 —— 基本概念的介绍
    由浅入深:Stable-Diffusion原理解析01——基本概念的介绍由于实习工作需要,最近一段时间的学习,自己也对Stable-Diffusion有了一些基础的理解,在学习和阅读论文的过程中,发现信息比较碎片化,于是决定产出一个SD原理的系列解析。本系列相比于本人之前的代码阅读系列没那么“硬核......