多线程
---恢复内容开始---
1.基本概念
1.1 进程
进程是指在系统中正在运行的一个应用程序.每个进程之间是独立的,每个进程之间均运行在其专受保护的内存空间内.
1.2 线程
基本概念:一个线程要想执行任务,必须得有线程(每个进程中至少有一个线程). 线程是进程基本执行单元,一个进程的所有任务都在线程中执行.
线程的串行:一个线程中任务的执行是串行的,要想在一个线程中执行多个任务,那么只能一个一个的按顺序执行这些任务.也就是说,在同一时间里,一个线程只能执行一个任务.
1.3 多线程
基本概念:即一个进程中可以开启多条线程,每条线程可以并行(同时)执行不同的任务.
线程的并行:并行也就是同时执行.比如同时开启3条线程分别下载3个文件(分别是文件A, 文件B, 文件C). 进程 ==> 车间 线程 ==> 车间工人
多线程并发执行的原理:在同一时间里,CPU只能处理一条线程,只有一条线程在执行. 多线程并发(同时)执行,其实是CPU快速的在多条线程之间调度(切换),如果CPU调度的时间足够快,就造成了线程并发执行的假象.
多线程的优缺点:
a优点 1>能适当提高程序的执行效率
2>能适当提高资源的利用率(CPU, 内存利用率)
b缺点 1>开启线程需要占用一定的内存空间(默认情况下,主线程占用1M,子线程占用512KB,每创建线程需耗时90毫秒)如果开启大量的线程,会占用大量的内存空间,降低程序的性能.
2>线程越多,CPU在调度线程上的开销就越大
3>程序设计更加复杂:比如线程之间的通信, 多线程的数据共享
1.4 主线程
基本概念:一个iOS程序运行后,默认会开启一条线程,称之为"主线程" 或 "UI线程"
主要作用: 1>显示\刷新UI界面
2>处理UI事件 (比如点击事件, 滚动事件, 拖拽事件等)
使用注意: 1>别将比较耗时的操作放到主线程中
2>耗时操作会卡住主线程,严重影响UI的流畅度,给用户一种"卡"的不好体验.
3>和UI相关的刷新操作必须放到主线程中进行处理.
2.实现方案
2.1 NSThread的基本使用
创建方式 a> alloc + init
注意:需要手动开启线程.
特点:如果正在执行系统分离出来的线程(子线程)时, 系统内部会retaion当前线程.
只有线程中的方法执行完毕,系统才会将其释放 release.
第一个参数: 目标对象
第二个参数: 选择器,线程启动要调用哪个方法
第三个参数: 前面方法要接收的参数(最多只能接收一个参数,没有则传nil)
NSTread *thread = [[NSTread alloc] initWithTarget:self selector:@selector(run) object:nil];
//启动线程
[thread start];
b> 分离出一条子线程
不用手动调动start方法,系统会自动启动.
没有返回值,不能对线程进行更多的设置.
优点:简单快捷 缺点:无法对多线程进行更详细的设置
应用场景:需要快速简便的执行线程
第一个参数: 线程启动调用的方法
第二个参数: 目标对象
第三个参数: 传递给调用方法的参数
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
c> 后台线程
//隐式创建并启动线程
//系统会自动创建一个子线程,并且在子线程中自动执行self的@selector
[self performSelectorInBackground:@selector(run) withObject:nil];
2.2 设置线程的属性
//设置线程的名称
thread.name = @"线程A";
//设置线程的优先级,注意线程优先级的取值范围为0.0~1.0之间, 1.0表示线程的优先级最高, 如果不设置,那么默认为0.5
thread.threadProiority = 1.0;
2.3 线程的状态
创建出来 --> 新建状态
调用start --> 准备就绪
被CPU调用 --> 运行
sleep --> 阻塞
执行完毕,或者被强制关闭 --> 死亡
注意:如果强制关闭线程,关闭之后的其他操作都无法执行
//阻塞线程
[NSThread sleepForTimeInterval:2.0];
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]];
//退出当前线程
[NSThread exit];
2.4 线程安全
应用场景: 多个线程访问同一块资源会发生数据安全问题
注意点: 1>只要加锁就会消耗性能
2>加锁必须传递一个对象.作为锁
3>如果想要正真的锁住代码,那么多个线程必须使用同一把锁才行
4>加锁的时候尽量缩小范围,因为范围越大性能就越低
加锁: @synchronized
线程同步的意思是:多条线程在同一条线上执行(按顺序执行)
互斥锁,就是利用了线程同步技术
安全隐患实例: a>存钱取钱
b>卖票(代码如下)
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, strong) NSThread *thread1; /** 售票员1*/
@property (nonatomic, strong) NSThread *thread2; /** 售票员2*/
@property (nonatomic, assign) NSInteger totalTicket; /** 剩余票数*/
@property (nonatomic, strong) NSObject *obj; /** 锁*/
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//初始化剩余票数
self.totalTicket = 100;
//创建2个售票员
self.thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
self.thread1.name = @"售票员1";
self.thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
self.thread2.name = @"售票员2";
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
//2个售票员同时开始售票
[self.thread1 start];
[self.thread2 start];
}
- (void)saleTicket{
while (1)
//在开发中,如果要加锁,一般情况使用self
@synchronized(self)
{//取出剩余的票数
NSInteger count = self.totalTicket;
//判断还有没有余票
if (count > 0){
[NSThread sleepForTimeInterval:0.01];
//卖票
self.totalTicket = count - 1;
NSLog(@"%@, %ld", [NSThread currentThread].name, self.totalTicket);
}else
{//提示用户卖完啦
NSLog(@"对不起, 卖完啦");
[NSThread exit];
}
}
}
@end
2.2原子和非原子属性
atomic: 原子属性为setter方法加锁(默认就是atomic)
nonatomic: 非原子属性,不会为setter方法加锁
注意点: atomic系统给我们自动添加的锁不是互斥锁,是自旋锁
自旋锁和互斥锁的对比:
共同点:1>都能够保证多线程在同一时候,只能有一个线程操作锁定的代码
不同点:1>如果是互斥锁,假如现在被锁住了,那么后面来的线程就会进入"休眠"状态直到解锁之后,又会唤醒线程继续执行
2>如果是自旋锁,假如现在被锁住了,那么后面来的线程不会进入休眠状态,会一直等待,直到解锁之后立即执行
3>自旋锁更适合做一些较短的操作
2.3 NSThread线程通信
基本概念:在一个进程中,线程往往不是孤立存在的,多个线程之间需要经常进行通信
体现: 1> 一个线程传递数据给另一个线程
2> 在一个线程中执行完特定任务后,转到另一个线程继续执行任务
线程间通信常用方法:1> - (void)performSelectorOnMainThread:(SEL) aSelector WithObject:(id)arg waitUntilDone:(BOOL)wait;
2> - (void)performSelector(SEL)aSelector onThread:(NSThread*)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
waitUnitlDone的含义:1>如果传入的是YES: 那么会等到主线程中i方法执行完毕,才会继续执行下面其他行的代码
2>如果传入的是NO: 那么不用等到主线程中的方法执行完毕,就可以继续执行下面其他行的代码
注意:虽然有时候可以在子线程中操作UI,但是开发中千万不要这样干.如果是在子线程中操作UI,有时候行,有时候不行. 更新UI一定要在主线程中更新
[self performSelectorInBackground:@selector(download2:) withObject:url];
[self performSelectorOnMainThread:@selector(showImage:) withObject:image waitUntilDone:YES];
[self performSelectorOnMainThread:@selector(showImage:) withObject:image waitUntilDone:NO];
[self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES];
[self performSelector:@selector(showImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];
3.GCD
3.1 任务的基本概念:执行什么操作
队列的基本概念:用来存放任务
同步函数dispatch_sync 不具备开启新线程的能力
异步函数dispatch_sync 具备开启线程的能力
队列的类型: a>并发队列
1>可以让多个任务并发(同时)执行
2>也可以自己创建
dispatch_queue_t queue = dispatch_queue_create(<#const char *label#>, <#dispatch_queue_attr_t attr#>)
3>全局并发队列: dispatch_get_global_queue(0 , 0);
b>串行队列
1>让任务一个接着一个的执行
GCD的各种组合:1>
同步 + 并行 = 不会开启新的线程
注意: 能不能开启新的线程, 和并行/串行没有关系, 只要函数是同步还是异步
*/
- (void)syncConcurrent
{
// 1.创建队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 2.添加任务
dispatch_sync(queue, ^{
NSLog(@"1 - %@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"2 - %@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"3 - %@", [NSThread currentThread]);
});
NSLog(@"%s", __func__);
}
2>
同步 + 串行 = 不会创建新的线程
注意: 如果是同步函数, 只要代码执行到了同步函数的那一行, 就会立即执行任务, 只有任务执行完毕才会继续往后执行
3>
/*
异步 + 并行 = 会开启新的线程
*/
- (void)asynConcurrent
{
/*
第一个参数: 队列
第二个参数: 任务
*/
// 1.创建队列
/*
第一个参数: 队列的名称
第二个参数: 队列的类型
*/
// dispatch_queue_t queue = dispatch_queue_create("com.520it.lnj", DISPATCH_QUEUE_CONCURRENT);
// 其实系统内部已经给我们提供了一个现成的并发队列
/*
第一个参数: iOS8以前是线程的优先级/ iOS8以后代表服务质量
iOS8以前
* - DISPATCH_QUEUE_PRIORITY_HIGH: 2
* - DISPATCH_QUEUE_PRIORITY_DEFAULT: 0
* - DISPATCH_QUEUE_PRIORITY_LOW: -2
* - DISPATCH_QUEUE_PRIORITY_BACKGROUND: -32768
iOS8开始, 取值都是十六进制
* - QOS_CLASS_USER_INTERACTIVE 用户交互(用户迫切的想执行任务, 不要在这种服务质量下做耗时的操作)
* - QOS_CLASS_USER_INITIATED 用户需要
* - QOS_CLASS_DEFAULT 默认(重置队列)
* - QOS_CLASS_UTILITY 实用工具(耗时的操作放在这里)
* - QOS_CLASS_BACKGROUND
* - QOS_CLASS_UNSPECIFIED 没有设置任何优先级
第二个参数: 系统保留的参数, 永远传0
*/
// 1.获取全局的并发队列
dispatch_queue_t queue = dispatch_get_global_queue(0 , 0);
// 2.添加任务到队列
// 文档说明是FIFO原则, 先进先出
// 打印结果不正确的原因: 线程的执行速度可能不一样, 有得快一些, 有的慢一些
dispatch_async(queue, ^{
NSLog(@"1 - %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2 - %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"3 - %@", [NSThread currentThread]);
});
NSLog(@"%s", __func__);
}
4>
/*
异步 + 串行 = 会创建新的线程, 但是只会创建一个新的线程, 所有的任务都在这一个新的线程中执行
异步任务, 会先执行完所有的代码, 再在子线程中执行任务
*/
- (void)asyncSerial
{
// 1.创建队列
dispatch_queue_t queue = dispatch_queue_create("com.520it.lnj", DISPATCH_QUEUE_SERIAL);
// 2.添加任务
dispatch_async(queue, ^{
NSLog(@"1 - %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2 - %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"3 - %@", [NSThread currentThread]);
});
NSLog(@"%s", __func__);
}
---恢复内容结束---

浙公网安备 33010602011771号