本文并非最终版本,如果想要关注更新或更正的内容请关注文集,联系方式详见文末,如有疏忽和遗漏,欢迎指正。
本文相关目录:
==================== 所属文集:4.0 多线程 ====================
4.1 多线程基础->1.0 进程 & 线程
······················ 2.0 多线程简介
4.2 pthread
4.3 NSThread->1.0 创建线程
····················· 2.0 线程属性
····················· 3.0 线程状态/线程生命周期
····················· 4.0 多线程安全隐患
····················· 5.0 线程间通讯和常用方法
4.4 GCD->1.0 GCD简介和使用
·············· 2.0 线程间的通信
·············· 3.0 其他用法
·············· 4.0 GCD 的定时器事件
4.5 NSOperation->1.0 NSOperation简介
························ 2.0 NSOperationQueue
························ 3.0 线程间通信
························ 4.0 自定义NSOperation
4.6 RunLoop - 运行循环
===================== 所属文集:4.0 多线程 =====================
4.1 资源共享/抢夺
多线程的安全隐患:
(1) 起因 :
资源共享概念 : 1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源
主要是因为多条线程,对`同一资源同时操作`,导致的问题
(2) 举例 :
比如多个线程访问同一个对象、同一个变量、同一个文件
(3) 结果 :
当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题 (资源强夺)
(4) 解决方案 :
互斥锁 / 同步锁
(5) 实例1 :卖票案例
多线程开发的复杂度相对较高,在开发时可以按照以下套路编写代码:
- 首先确保单个线程执行正确
- 添加线程
代码示例 :
#import "ViewController.h"
@interface ViewController ()
@property(nonatomic, strong) NSThread *thread01; // 售票员01
@property(nonatomic, strong) NSThread *thread02; // 售票员02
@property(nonatomic, strong) NSThread *thread03; // 售票员03
@property(nonatomic, assign) NSInteger ticketCount; //票的总数
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.ticketCount = 10;
//创建线程
self.thread01 = [[NSThread alloc] initWithTarget:self
selector:@selector(saleTicket)
object:nil];
self.thread01.name = @"售票员01";
self.thread02 = [[NSThread alloc] initWithTarget:self
selector:@selector(saleTicket)
object:nil];
self.thread02.name = @"售票员02";
self.thread03 = [[NSThread alloc] initWithTarget:self
selector:@selector(saleTicket)
object:nil];
self.thread03.name = @"售票员03";
}
// 开启线程
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self.thread01 start];
[self.thread02 start];
[self.thread03 start];
}
// 卖票
- (void)saleTicket {
while (1) {
@synchronized(self) { //互斥锁(控制器做锁对象)
// 先取出总数
NSInteger count = self.ticketCount;
// 判断还有没有余票
if (count > 0) {
self.ticketCount = count - 1;
NSLog(@"%@卖了一张票,还剩下%zd张", [NSThread currentThread].name,
self.ticketCount);
} else {
NSLog(@"票已经卖完了");
break;
}
}
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
打印结果:
线程安全[3386:472835]售票员02卖了一张票,还剩下9张
线程安全[3386:472836]售票员03卖了一张票,还剩下8张
线程安全[3386:472834]售票员01卖了一张票,还剩下7张
线程安全[3386:472835]售票员02卖了一张票,还剩下6张
线程安全[3386:472836]售票员03卖了一张票,还剩下5张
线程安全[3386:472834]售票员01卖了一张票,还剩下4张
线程安全[3386:472835]售票员02卖了一张票,还剩下3张
线程安全[3386:472836]售票员03卖了一张票,还剩下2张
线程安全[3386:472834]售票员01卖了一张票,还剩下1张
线程安全[3386:472835]售票员02卖了一张票,还剩下0张
线程安全[3386:472836]票已经卖完了
线程安全[3386:472834]票已经卖完了
线程安全[3386:472835]票已经卖完了
4.2 安全隐患解决 – 互斥锁 / 同步锁
互斥锁使用技术 : 线程同步
概念:多条线程按顺序地执行任务
引申 : 互斥锁,就是使用了线程同步技术
互斥锁使用格式 :
//锁对象为能够加锁的任意 NSObject 对象
//锁对象一定要保证所有的线程都能够访问
//如果代码中只有一个地方需要加锁,大多都使用self,这样可以避免单独再创建一个锁对象
@synchronized(锁对象) {
//需要锁定的代码
}
注意:
- 锁定1份代码只用1把锁,用多把锁是无效的
- 保证锁内的代码,同一时间,只有一条线程能够执行!
- 互斥锁的锁定范围,应该尽量小,锁定范围越大,效率越差!
- 速记 : [[NSUserDefaults standardUserDefaults] synchronize];
互斥锁的使用前提:
多条线程抢夺同一块资源
互斥锁的优缺点 :
优点:能有效防止因多线程抢夺资源造成的数据安全问题 , 能保证数据的准确性
缺点:需要消耗大量的CPU资源 , 可能会导致执行速度变慢
4.3 原子属性/非原子属性
原子和非原子属性:
(默认) atomic | 原子属性 | 为setter方法加锁 | 线程安全,需要消耗大量的资源 |
---|---|---|---|
(推荐) nonatomic | 非原子属性 | 不会为setter方法加锁 | 非线程安全,适合内存小的移动设备 |
atomic加锁原理:
@property (assign, atomic) int age;
- (void)setAge:(int)age
{
@synchronized(self) {
_age = age;
}
}
iOS开发的建议:
- 所有属性都声明为nonatomic
- 尽量避免多线程抢夺同一块资源
- 尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减小移动客户端的压力
本文源码 Demo 详见 Github
https://github.com/shorfng/iOS_4.0_multithreading.git
作者:蓝田(Loto)
出处: 简书
如果你觉得本篇文章对你有所帮助,请点击文章末尾下方“喜欢”
如有疑问,请通过以下方式交流:
① 评论区回复
② 微信(加好友请注明“简书+称呼”)
③发送邮件
至 shorfng@126.com
本文版权归作者和本网站共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
网友评论