1、主线程(UI线程)
——顾名思义就是:与UI有关,处理UI事件的线程。
1.1、主线程的主要作用:
显示\刷新UI界面、处理UI事件(比如点击事件,滚动事件,拖拽事件)
使用注意:不能将比较耗时的操作放到主线程中,会有卡的感觉。耗时操作要放到子线程(后台线程,非主线程)操作
单条线程是串行,多条线程同时(来回快速切换)工作是并行
耗时操作放在子线程-后台线程执行
2、开辟线程:
2.1、线程分类
1、 pthread--POSIX threads 跨平台(就会与OS有关) 纯C语言 但是线程的生命周期由程序员自己管理 几乎不用
(POSIX表示可移植操作系统接口(Portable Operating System Interface ,缩写为 POSIX)
2、 NSThread OC的 可直接操作线程对象 但是线程的生命周期由程序员自己管理 偶尔使用
3、GCD 替代NSThread等线程技术 充分利用设备的核 C语言 线程的生命周期自动管理 经常使用
4、NSOperation 基于GCD 比GCD多了一些更简单实用的功能 更加面向对象 OC语言 线程的生命周期自动管理 经常使用
一、pthread生命周期程序员管理
// 1、pthread
pthread_t pthreadID;
pthread_create(&pthreadID, NULL, run, NULL);
void *run(void *org){
NSThread *t = [NSThread currentThread];
for (int i = 0; i< 10000; i++) {
NSLog(@"子线程-- %@",t);
}
return NULL;
}
二、NSThread生命周期程序员管理
创建NSThread线程的三种方法:
1 NSThread
1- NSThread *t = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
2- [t start];
start之后,就执行self的run方法,之后的object可以是run中传入的方法
有优先级的设置和获得、有获得主线程/当前线程的方法,设置/获得线程的名称
2 创建线程自动启动 这个没有返回值 detach派遣分离
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
3 隐式创建子线程(InBackground在后台执行的线程就是子线程)自动启动线程
performSelectorInBackground是NSObject的对象方法,谁都可以调用
[self performSelectorInBackground:@selector(run) withObject:nil];
pthread与NSThread需要程序员自己管理线程的生命周期,以下介绍生命周期的常识:
<pre>新建new —(start)— 就绪runnable(在调度池中) —— 运行running
—— 阻塞blocked(sleep或者等待同步锁,在内存中,不在调度池中,就绪的状态都不是)
—— 死亡deaded(死亡只是状态 可是线程对象还在内存中,不在调度池中)release之后线程对象才会从内存中消失。</pre>
只有就绪和运行状态时候,线程才会在调度池中。
注意:一旦线程停止(死亡)了,就不能再次开启任务。
2.2、线程安全隐患:
资源共享
比如访问同一块内存,对象 变量 文件读写等等 比如银行的存钱与取钱 还有火车站卖票问题。
方法一:加互斥锁
多条线程只能有一条线程去访问同一块资源。
// 任何对象都可以为锁对象,锁只能有一把,每次线程想访问资源的时候,都会问下锁对象是否可以访问资源,所以要用公用的锁比较好,锁对象多个的话就不晓得问哪一把锁了,问出的结果就会特别乱,导致错误
@synchronized (self) {
self.leftTicketCount -- ;
NSLog(@"卖票之后剩余%d张-%@", self.leftTicketCount, [NSThread currentThread].name);
}
线程的优先级问题:
self.thread01.threadPriority = 0.5; // 0.0~1.0 值越大,优先级越高,被调度的频率越高
[self.thread01 setThreadPriority:0.5];
[NSThread setThreadPriority:0.5];
[NSThread threadPriority]; // readonly属性
互斥锁的优缺点---一般加锁之后性能会比较差:
优点:能有效防止因多线程抢夺资源造成的数据安全问题
缺点:需要消耗大量CPU资源(其他线程的等待以及每时每刻记录着锁的状态)
注意:只有存在多线程才会加锁
线程同步:多条线程按顺序的执行任务--串行执行。互斥锁就是运用了线程同步技术
方法二:非原子属性
原子和非原子属性:
atomic:OC中属性默认是原子属性,也就是默认在setter方法中加了锁,性能会比较差,线程安全,需要消耗大量的资源
nonatomic:非原子属性,非线程安全,适合内存小的移动设备
(最优)iOS开发建议:
所有属性都声明为nonatomic
尽量避免多线程抢夺同一块资源
尽量将加锁、资源抢夺的业务逻辑交给服务器处理,减小移动客户端的压力
2.3、线程间通讯:
线程之间数据传递 以及 线程之间的切换(比如从子线程跳到主线程)
\- (void) performSelectorOnMainThread:(SEL)aSelector......; // 跳转到主线程
\- (void) performSelector:(SEL)aSelector onThread:(NSThread *)thr...... // 跳转到特定的线程上
例子:图片的下载
/// waitUntilDone YES 等待主线程完成后再子线程继续执行以后代码一般设置为NO
/// withObject可以传入参数
[self performSelectorOnMainThread:<#(nonnull SEL)#> withObject:<#(nullable id)#> waitUntilDone:<#(BOOL)#>]; // “self” 调用后面的“方法”
三、GCD
两步骤:
- 1定制任务
- 2将任务添加到队列中
GCD会自动将队列中的任务取出,放到对应的线程中执行(主队列 对应 主线程,其他队列 对应 子线程)
-
两个方法可以将任务添加到队列
(是否具备开启新线程的能力分了两种方法):dispatch_sync(queue,block) // 同步-----不具备开启线程的能力
dispatch_async(queue, block) // 异步——具备开线程的能力
-
队列的分类(决定了任务的执行方式):
并发队列---多个任务并行执行,说明会开多条线程执行任务,只能是async函数。
串行队列---多个任务一个挨着一个的执行
MRC(MannulReference Counting)
并发队列:
GCD已经提供给我们全局的并发队列,供整个应用使用,不需要手动创建
dispatch_get_global_queue(优先级枚举,0);
优先级枚举值:
#define DISPATCH_QUEUE_PRIORITY_HIGH 2 高的
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 默认的 ---- 常用
#define DISPATCH_QUEUE_PRIORITY_LOW (-2) 低的
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN 最低的
// 异步执行--- 有开线程的能力
// 创建队列(全局队列默认就是并发队列)
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 添加任务到队列
dispatch_async(queue, ^{
NSLog(@"下载图片1---%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"下载图片2---%@",[NSThread currentThread]);
});
#结果:开两条线程
串行队列:
-
1 手动创建 参数:队列名称(出现在调试界面)与传入参数NULL
dispatch_queue_t queue = dispatch_queue_create("串行队列create", NULL);
// 添加任务到队列dispatch_async(queue, ^{ NSLog(@"下载图片1---%@",[NSThread currentThread]); }); dispatch_async(queue, ^{ NSLog(@"下载图片2---%@",[NSThread currentThread]); }); # 结果:开一条线程
-
2 主队列 (与主线程相关的队列)
dispatch_queue_t queue = dispatch_get_main_queue();
同步执行sync:在当前线程执行
并行队列(全局队列): 不开线程
串行队列: 不开线程
主队列(也是串行队列):放在主线程时候 相互等待卡死状态
例外:下载图片------不开线程,会把任务放到主队列的最后,出现了永远不能执行而是互相等待的状态~~~~比如:在B块代码在A行之后,把下载图片sync方式C代码块添加到A行上,之后下载图片的C代码块在内存的位置是放到了B代码块的后面。由于是在主线程顺序执行代码的规则,在程序代码先后顺序上想执行B必须先执行完C代码(B等C),在主队列中先执行完B才能执行C(C等B),所以,互相等待中...
异步执行async:开线程
并行队列(全局队列): 能开多条线程,由任务个数决定
串行队列: 只开启一条线程
主队列(也是串行队列):block内任务可以缓一缓
总结:只要是同步是指,等待对应的任务执行完毕后,才继续向下执行
只要是异步是指,不是串行执行,可以缓一缓再执行任务
任务就是放在队列中的,一个block是一个任务!!!!!
GCD其他函数:
1、延时函数:dispatch_after(dispatch_time(xx,xx), 队列, block); // 其中队列可以是主队列 也可以是全局队列!!!!!!!!之前不晓得~
类似:[self performSelectior:@selector(run) withObject:nil afterDelay:2.0];
2、程序运行过程中,只执行一次:
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 代码
});
队列组:
需求:首先,分别异步执行2个耗时的操作
其次,等2个异步操作都执行完毕后,再回到主线程执行操作
===========================代码==========================
// 创建队列组
dispatch_group_t group = dispatch_group_create();
// 下载图片1和2
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
__block UIImage *image01 = nil;
dispatch_group_async(group, queue, ^{
// 下载第一张图片
NSURL *url = [NSURL URLWithString:@"http://preview.quanjing.com/fod_liv002/fo-11171537.jpg"];
NSData *imageData = [NSData dataWithContentsOfURL:url];
NSLog(@"data01-----------%@",imageData);
image01 = [UIImage imageWithData:imageData];
});
__block UIImage *image02 = nil;
dispatch_group_async(group, queue, ^{
// 下载第二张图片
NSURL *url = [NSURL URLWithString:@"http://scimg.jb51.net/allimg/160716/105-160G61F250436.jpg"];
NSData *imageData = [NSData dataWithContentsOfURL:url];
image02 = [UIImage imageWithData:imageData];
});
// 完成后的通知
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 显示图片
self.imageView01.image = image01;
self.imageView02.image = image02;
// 合并图片 开启图片上下文
UIGraphicsBeginImageContextWithOptions(CGSizeMake(200, 100), NO, 0.0);
[image01 drawInRect:CGRectMake(0, 0, 100, 100)];
[image02 drawInRect:CGRectMake(100, 0, 100, 100)];
self.imageViewBig.image = UIGraphicsGetImageFromCurrentImageContext();
// 关闭图片上下文
UIGraphicsEndImageContext();
});
=======================end=======================================
四、NSOperation:
类似于GCD GCD中任务类似NSOperation
队列类似NSOperationQueue
一个operation对象对应一个任务。
NSOperatinon是个抽象类
代码实现1:----没有创建operation队列
invocation是“回调”的意思。
NSInvocationOperation *invocation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperationSomething) object:nil];
[invocation start]; // 没有添加到队列的话,就是主线程
NSBlockOperation *blockOperation = [NSBlockOperation
blockOperationWithBlock:^{
NSLog(@"NSBlockOperation---%@", [NSThread currentThread]);
}];
[blockOperation start]; // 没有添加到队列的话 就是主线程
- 默认情况下,调用了start方法后,并不会开一条新线程去执行操作,而是在当前线程(并不一定总是主线程)同步执行操作。
只有将操作NSInvocationOperation放到队列NSOperationQueue中,才会开启线程异步执行操作。
但是对于NSBlockOperation只要操作数大于2就会开启新线程,如果还是1就在当前线程执行:
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"--blockOperation1 %@--", [NSThread currentThread]);
}];
// 添加更多操作
[blockOperation addExecutionBlock:^{
NSLog(@"--blockExecutionOperation2 %@--", [NSThread currentThread]);
}];
[blockOperation start]; //如果任务个数大于1个,比如n(n>1)个,就会开新线程n-1条,不会有GCD中的异步同步函数
-
需求:不管操作数是一个还是两个都要开启新线程,那么就要放到队列中,并且自动执行操作,自动开启线程。
NSOperationQueue *queue = [[NSOperationQueue alloc] init]; 调用[queue addOperation:xxx];
或者:
[queue addOperationWithBlock:^{
NSLog(@"匿名操作--%@--",[NSThread currentThread]);
}];
生成主队列 [NSOperationQueue mainQueue];
最大并发数:
(queue的属性)同时执行的任务数。2~3为宜。如果是2,线程num = 2,num = 3,num = 4...由整的操作数决定。在打印的统一时间上的执行操作数一般都是设置的最大并发数。 maxConcurrentOperationCount属性。
队列的取消:
调用cancelAllOperations取消队列的所有操作 或者,操作调用cancel方法取消单个操作。
队列的暂停:
setSuspended:(BOOL)b; // YES暂停队列 NO恢复队列
比如:icon异步下载并且显示,当tableView上下滚动的时候,显得有些卡顿(主线程与多个子线程来回
切换的原因),所以,当滚动的时候,下载操作暂停,tableView停止滚动的时候,下载操作再恢复就可以了。
操作依赖----面试题
操作的对象方法:[operationA addDependency:operationB];
operationA 依赖于 operationB;
这句代码要写在添加队列之前。否则没有效果,添加的时候代码执行自动取出操作开始执行。
注意:可以在不同queue之间产生操作依赖,就是不能相互依赖。
操作监听:operation.completionBlock = ^{
// 写上操作完成后的所做的事情。所在线程是操作的当前线程。也可以写在操作代码的后面,也算是操作监听。操作完成之后在执行此段代码。
};
例子:自定义operation
在tableViewcell上显示下载图片,并且滚动的时候用户体验好一些:
1 写个类继承自NSOperation
2 定义-(void)main函数
3 下载操作写在main函数内,而且添加自动释放池,且需要判断是否操作被取消,如果被取消了就不再继续执行以下代码
4 传image时候,使用代理在主线程展示图片到视图上
最后保存到沙盒的时候,使用压缩图片
NSData *data = UIImagePNGRepresentation(image);
[data writeToFile:@“” atomically:YES];
网友评论