小码哥iOS学习笔记第二十天: 多线程的安全隐患

一、多线程的安全隐患

  • 资源共享
    • 1块资源 可能会被多个线程共享,也就是多个线程可能会访问同一块资源
    • 比如多个线程访问同一个对象、同一个变量、同一个文件
  • 当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题

二、多线程安全隐患示例01 – 存钱取钱

  • 模拟代码如下

  • 运行程序, 结果如下

  • 正常情况, 应该存5000, 取2500, 所以应该剩3500, 但是结果剩了2500
  • 再次运行模拟

  • 可以看到只剩了2000, 这就是多线程的安全隐患问题, 是数据错乱

三、多线程安全隐患示例02 – 卖票

  • 代码模拟如下

  • 运行程序, 模拟卖票

  • 一共卖出10张, 应该剩余0张, 但是结果却剩余3张, 说明数据出现了错乱

四、多线程安全隐患分析和解决方案

1、多线程安全隐患分析

2、多线程安全隐患的解决方案
  • 解决方案:使用线程同步技术(同步,就是协同步调,按预定的先后次序进行)
  • 常见的线程同步技术是:加锁

五、iOS中的线程同步方案

  • iOS中线程加锁有以下几种方案
OSSpinLock
os_unfair_lock
pthread_mutex
dispatch_semaphore
dispatch_queue(DISPATCH_QUEUE_SERIAL)
NSLock
NSRecursiveLock
NSCondition
NSConditionLock
@synchronized
复制代码

六、准备代码

  • 将上面的多线程安全隐患示例01 – 存钱取钱多线程安全隐患示例02 – 卖票代码封装到一个BaseDemo类中, 具体代码如下图

  • BaseDemo暴露出五个方法, 两个测试调用, 三个线程调用
  • 创建AddLockDemo继承自BaseDemo

  • ViewController中代码如下

七、OSSpinLock(自旋锁)

  • OSSpinLock叫做自旋锁,等待锁的线程会处于忙等(busy-wait)状态,一直占用着CPU资源
1、解决存钱取钱卖票的安全隐患
  • 存钱取钱卖票中加入OSSpinLock

  • 运行程序, 多次点击屏幕试验, 都可以发现结果正确

2、OSSpinLock目前已经不再安全,可能会出现优先级反转问题
  • 一个程序中可能会有多个线程, 但是只有一个CPU
  • CPU给线程分配资源, 让他们穿插的执行, 比如有三个线程thread1thread2thread3
  • CPU通过分配, 让thread1执行一段时间后, 接着让thread2执行一段时间, 然后再让thread3执行一段时间
  • 这样就给了我们有多个线程同时执行任务的错觉
  • 而线程是有优先级的
    • 如果优先级高, CPU会多分配资源, 就会有更多的时间执行
    • 如果优先级低, CPU会减少分配资源, 那么执行的就会慢
  • 那么就可能出现低优先级的线程先加锁,但是CPU更多的执行高优先级线程, 此时就会出现类似死锁的问题
假设通过OSSpinLock给两个线程`thread1`和`thread2`加锁
thread优先级高, thread2优先级低
如果thread2先加锁, 但是还没有解锁, 此时CPU切换到`thread1`
因为`thread1`的优先级高, 所以CPU会更多的给`thread1`分配资源, 这样每次`thread1`中遇到`OSSpinLock`都处于使用状态
此时`thread1`就会不停的检测`OSSpinLock`是否解锁, 就会长时间的占用CPU
这样就会出现类似于死锁的问题
复制代码

八、os_unfair_lock(互斥锁)

  • os_unfair_lock用于取代不安全的OSSpinLock, 从iOS10开始才支持
  • 从底层调用看, 等待os_unfair_lock锁的线程会处于休眠状态, 并非忙等
  • 需要导入头文件#import <os/lock.h>
// 初始化
os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
// 尝试加锁, 如果lcok已经被使用, 加锁失败返回false, 如果加锁成功, 返回true
os_unfair_lock_trylock(&lock);
// 加锁
os_unfair_lock_lock(&lock);
// 解锁
os_unfair_lock_unlock(&lock);
复制代码
解决存钱取钱卖票的安全隐患
  • 在存钱取钱和卖票中加入os_unfair_lock

  • 运行程序, 多次点击屏幕试验, 都可以发现结果正确

九、pthread_mutex

  • mutex叫做互斥锁,等待锁的线程会处于休眠状态
  • 需要导入头文件#import <pthread.h>
// 初始化属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
// 初始化锁
pthread_mutex_t pthread;
pthread_mutex_init(&pthread, &attr);
// 销毁属性
pthread_mutexattr_destroy(&attr);
// 销毁锁
pthread_mutex_destroy(&pthread);
复制代码
  • 属性类型的取值
#define PTHREAD_MUTEX_NORMAL		0
#define PTHREAD_MUTEX_ERRORCHECK	1
#define PTHREAD_MUTEX_RECURSIVE		2
#define PTHREAD_MUTEX_DEFAULT		PTHREAD_MUTEX_NORMAL
复制代码
1、解决存钱取钱卖票的安全隐患
  • 导入头文件, 创建锁, 加锁解锁

  • 运行程度, 多次点击屏幕试验, 都可以发现结果正确

2、递归锁
  • 定义PthreadTest类继承自NSObject, 其中recursive是一个递归方法

  • ViewController中代码如下, 点击屏幕后调用PthreadTestrecursive方法

  • 点击屏幕, 可以看到发生了死锁, 这是因为recursive中调用recursive, 此时还没有解锁, 再次进行加锁, 所以发生了死锁

  • 设置pthread初始化时的属性类型为PTHREAD_MUTEX_RECURSIVE, 这样pthread就是一把递归锁

  • 递归锁允许同一线程内, 对同一把锁进行重复加锁, 所以可以看到递归方法调用成功

3、条件
  • PthreadTest中代码如下

  • ViewController中代码如下

  • 当点击屏幕时, 会在array中移除最后一个元素添加一个新元素, 代码中可以看到, 使用不同线程调用__remove__add两个方法

  • 现在的需求是, 只有在array不为空的情况下, 才能执行删除操作, 如果直接运行, 那么可能会先调用__remove在调用__add, 那么就与需求相违背

  • 所以, 我们可以使用条件对两个方法进行优化

  • 创建cond

  • array.count == 0时, 是程序进入休眠, 只有当array中添加了新数据后在发起信号, 将休眠的线程唤醒

  • 运行程序, 点击屏幕, 可以看到程序先进入__remove方法, 但是却在__add中添加新元素之后再移除元素

十、NSLock、NSRecursiveLock、NSCondition、NSConditionLock

  • NSLockNSRecursiveLockNSConditionNSConditionLock是基于pthread封装的OC对象
1、NSLock
  • AddLockDemo中代码如下, 直接使用NSLock进行加锁

  • ViewController中点击屏幕时调用方法

  • 运行程序, 点击屏幕, 可以看到结果正确

  • 查看GNUStep中关于NSLock的底层代码, 可以看到NSLock是基础pthread封装的normal

2、NSRecursiveLock
  • PthreadTest中代码如下, 使用NSRecursiveLock递归函数加锁解锁

  • ViewController中, 当点击屏幕时调用recursive方法

  • 运行程序, 点击屏幕, 可以看到递归锁的结果

  • 查看GNUStep中关于NSRecursiveLock的底层代码

3、NSCondition
  • PthreadTest中代码如下, 使用NSCondition加锁解锁

  • ViewController中, 当点击屏幕时调用pthreadTest方法

  • 可以看到, 先调用了__remove方法, 但是却在__add中给array添加了新元素之后, 才删除一个元素

  • 查看GNUStep中关于NSCondition的底层代码

4、NSConditionLock
  • NSConditionLock是对NSCondition的进一步封装
@interface NSConditionLock : NSObject <NSLocking> {
@private
    void *_priv;
}

// 初始化, 同时设置 condition
- (instancetype)initWithCondition:(NSInteger)condition;

// condition值
@property (readonly) NSInteger condition;

// 只有NSConditionLock实例中的condition值与传入的condition值相等时, 才能加锁
- (void)lockWhenCondition:(NSInteger)condition;
// 尝试加锁
- (BOOL)tryLock;
// 尝试加锁, 只有NSConditionLock实例中的condition值与传入的condition值相等时, 才能加锁
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
// 解锁, 同时设置NSConditionLock实例中的condition值
- (void)unlockWithCondition:(NSInteger)condition;
// 加锁, 如果锁已经使用, 那么一直等到limit为止, 如果过时, 不会加锁
- (BOOL)lockBeforeDate:(NSDate *)limit;
// 加锁, 只有NSConditionLock实例中的condition值与传入的condition值相等时, 才能加锁, 时间限制到limit, 超时加锁失败
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;
// 锁的name
@property (nullable, copy) NSString *name;

@end
复制代码
  • 可以使用NSConditionLock设置线程的执行顺序

  • 运行程序, 可以看到打印顺序

十一、同步队列解决多线程隐患

  • 使用同步队列, 代码如下图

  • ViewController代码如下

  • 点击屏幕, 可以看到结果正确

十二、dispatch_semaphore_t

  • 可以使用dispatch_semaphore_t设置信号量为1, 来控制同意之间只有一条线程能执行, 实际代码如下

  • 运行程序, 点击屏幕, 可以看到打印结果正确

十三、@synchronized

  • @synchronized是对mutex递归锁的封装
  • 源码查看:objc4中的objc-sync.mm文件
  • @synchronized(obj)内部会生成obj对应的递归锁,然后进行加锁、解锁操作
1、解决多线程的安全隐患
  • 使用@synchronized进行加锁

  • 执行代码, 点击屏幕, 效果如下

2、@synchronized底层原理
  • 找到objc_sync_enterobjc_sync_exit两个函数, 分别用于加锁和解锁

  • 查看SyncData

  • 通过所点进去, 找到recursive_mutex_tt

  • 查看recursive_mutex_tt, 可以看到底层是通过os_unfair_recursive_lock封装的锁

  • 接着查看通过对象获取锁的代码

  • 找到LIST_FOR_OBJ, 点击查看

  • 可以看到, 通过传入的对象, 会获取唯一标识所谓锁

十四、iOS线程同步方案性能比较

性能从高到低排序
os_unfair_lock
OSSpinLock
dispatch_semaphore
pthread_mutex
dispatch_queue(DISPATCH_QUEUE_SERIAL)
NSLock
NSCondition
pthread_mutex(recursive)
NSRecursiveLock
NSConditionLock
@synchronized
复制代码

十五、自旋锁、互斥锁比较

  • 什么情况使用自旋锁比较划算?
    • 预计线程等待锁的时间很短
    • 加锁的代码(临界区)经常被调用,但竞争情况很少发生
    • CPU资源不紧张
    • 多核处理器
  • 什么情况使用互斥锁比较划算?
    • 预计线程等待锁的时间较长
    • 单核处理器
    • 临界区有IO操作
    • 临界区代码复杂或者循环量大
    • 临界区竞争非常激烈

转载于:https://juejin.im/post/5c8e478051882545ec4f6e24

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
东南亚位于我国倡导推进的“一带一路”海陆交汇地带,作为当今全球发展最为迅速的地区之一,近年来区域内生产总值实现了显著且稳定的增长。根据东盟主要经济体公布的最新数据,印度尼西亚2023年国内生产总值(GDP)增长5.05%;越南2023年经济增长5.05%;马来西亚2023年经济增速为3.7%;泰国2023年经济增长1.9%;新加坡2023年经济增长1.1%;柬埔寨2023年经济增速预计为5.6%。 东盟国家在“一带一路”沿线国家中的总体GDP经济规模、贸易总额与国外直接投资均为最大,因此有着举足轻重的地位和作用。当前,东盟与中国已互相成为双方最大的交易伙伴。中国-东盟贸易总额已从2013年的443亿元增长至 2023年合计超逾6.4万亿元,占中国外贸总值的15.4%。在过去20余年中,东盟国家不断在全球多变的格局里面临挑战并寻求机遇。2023东盟国家主要经济体受到国内消费、国外投资、货币政策、旅游业复苏、和大宗商品出口价企稳等方面的提振,经济显现出稳步增长态势和强韧性的潜能。 本调研报告旨在深度挖掘东南亚市场的增长潜力与发展机会,分析东南亚市场竞争态势、销售模式、客户偏好、整体市场营商环境,为国内企业出海开展业务提供客观参考意见。 本文核心内容: 市场空间:全球行业市场空间、东南亚市场发展空间。 竞争态势:全球份额,东南亚市场企业份额。 销售模式:东南亚市场销售模式、本地代理商 客户情况:东南亚本地客户及偏好分析 营商环境:东南亚营商环境分析 本文纳入的企业包括国外及印尼本土企业,以及相关上下游企业等,部分名单 QYResearch是全球知名的大型咨询公司,行业涵盖各高科技行业产业链细分市场,横跨如半导体产业链(半导体设备及零部件、半导体材料、集成电路、制造、封测、分立器件、传感器、光电器件)、光伏产业链(设备、硅料/硅片、电池片、组件、辅料支架、逆变器、电站终端)、新能源汽车产业链(动力电池及材料、电驱电控、汽车半导体/电子、整车、充电桩)、通信产业链(通信系统设备、终端设备、电子元器件、射频前端、光模块、4G/5G/6G、宽带、IoT、数字经济、AI)、先进材料产业链(金属材料、高分子材料、陶瓷材料、纳米材料等)、机械制造产业链(数控机床、工程机械、电气机械、3C自动化、工业机器人、激光、工控、无人机)、食品药品、医疗器械、农业等。邮箱:market@qyresearch.com

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值