什么是线程?什么是进程?线程和进程的区别与联系?
- 线程的定义
- 线程是CPU调度(执行任务)的最小单位
- 其实质就是一段代码(一个任务)
- 进程的定义
- 系统中正在运行的一个应用程序
- 进程是CPU分配资源和调度的单位
- 两者的联系与区别
- 线程是进程的组成部分,一个进程可以开启多个子线程,但是每1个进程至少要有1个线程
- 1个进程的所有任务都是在线程中执行的
- 同1个进程内的线程共享进程的资源
多线程的概念和原理
- 概念
- 一个程序开启多条线程,每条线程可以并行(同时)执行不同的任务
- 原理
- 多个线程并发执行,其实质是CPU快速地在多条线程之间调度(切换)
- 当CPU调度线程的时间足够快,就会造成多线程并发执行的假象
多线程的优缺点
- 优点
- 能适当地提高程序的执行效率
- 能适当提高资源利用率(CPU和内存利用率)
- 缺点
- 创建多线程是有开销的,包括内存空间和创建时间上的开销
- 如果开启大量线程,会降低程序的性能
- 线程越多,CPU在调度线程上的开销就越大
什么是主线程?主线程的作用和使用注意有哪些?
- 定义
- iOS程序运行后,默认开启1条线程,称为“主线程”
- 作用
- 显示/刷新UI界面
- 处理UI事件(点击、滚动、拖拽事件等)
- 注意
- 别将耗时操作放在主线程,耗时操作会卡住主线程,影响UI的流畅度
实现多线程的方法一共有哪些?
项目中一般在哪用到多线程?
多线程一般用于耗时操作,如:
- 网络请求
- 上传,下载文件
- 读取数据库
- 文件操作
- 大循环
什么是线程安全问题?有什么解决方案?
- 定义
- 多个线程同时访问同一块资源(例如同一个对象、变量、文件)时,引发的数据错乱和数据安全问题,称为线程安全问题
- 解决方法
- 添加互斥锁
- 互斥锁的使用格式 :@synchronized( 锁对象 ){ //需要锁定的代码}
- 加锁的原理:使线程同步执行
- 使用互斥锁的注意事项:
- 锁对象必须是全局唯一的,一般用self
- 加锁必须满足多个线程共享同一块资源的前提条件
- 加锁需要耗费大量CPU资源
- 锁定一段代码只需要用一把锁
线程安全问题(以下代码的打印结果是?)
x = 1;
线程1: {x++;}
线程2: {x++;}
print x;
打印结果?
- 有可能是1
- 线程1和线程2还没走完,主线程已经结束了,结果 x = 1
- 有可能是2
- 线程1和线程2同时访问x, 结果 x = 2
- 有可能是3
- 线程1和线程2不同时访问x, 结果 x = 3
atomic和nonatomic的作用和区别?
- atomic是线程安全的,会自动为setter方法加锁,需要消耗大小资源
- nonatomic是非线程安全的,适合内存小的移动设备
FMDB是线程安全的么?
- FMDB如果使用FMDatabase类是线程不安全的
- 使用FMDatabaseQueue是线程安全的
线程通信的概念,如何实现?
- 概念
- 一个线程传递数据给另一个线程
- 一个线程中执行完特定任务后,转到另一个线程继续执行任务
- 实现方法
- performSelectorOnMainThread和performSelectorOnThread方法
- performSelectorInBackground(后台线程中执行)
- 使用GCD的dispatch方法
GCD内部怎么实现的,使用GCD有什么优势?
- 实现方式
- 通过定制任务和将任务添加到队列中来实现GCD的多线程功能
- GCD会自动将队列中的任务去出来,放到对应的线程中执行
- 任务的取出遵循FIFO原则(先进先出)
- 优势
- 会自动利用更多的CPU内核
- 会自动管理线程的生命周期
全局并发队列和使用create函数创建的并发队列有什么区别?
- 全局并发队列在整个程序中本身是默认存在的,并且对应有高优先级,默认优先级,低优先级,后台优先级一共四种并发队列,我们只是选择其中一个来用,而使用create函数创建的并发队列是实打实地从头开始去创建一个队列
- iOS6之前,使用create函数创建的队列都要进行一次Release,而全局并发队列不需要我们手动Release。但在iOS6之后,GCD已经纳入ARC内存操作中,不需要再进行Release
- 使用栅栏函数时,使用全局并发队列是无效的,只有使用create创建的并发队列才有效
GCD中如何控制多线程并发执行时的执行顺序
- 使用栅栏函数
dispatch_barrier_async
- 注意
- 给栅栏函数添加全局并发队列是无效的
- 栅栏函数无法控制栅栏前的并发队列的执行顺序
dispatch_queue_t queue = dispatch_queue_create("download", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"第1个线程执行了----%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"第2个线程执行了----%@",[NSThread currentThread]);
});
dispatch_barrier_async(queue, ^{
NSLog(@"++++++++++++++++++++++++");
});
dispatch_async(queue, ^{
NSLog(@"第5个线程执行了----%@",[NSThread currentThread]);
});
GCD除了开线程,还可以用在什么地方?
- 单例 (使用dispatch_once)
- 延迟执行 (使用dispatch_after,可以定义执行任务的线程)
- 定时器 (使用dispatch_source_set_event_handler)
- 快速迭代
GCD快速迭代的定义和作用?
- 定义:同时开启主线程和子线程并发完成循环操作,称为快速迭代
- 作用:大大提高循环操作的效率
- 代码示例:(从from文件夹中剪切其中的文件到to文件夹中)
- (void)moveFile{
NSString *fromPath = @"/Users/zkh/Desktop/from";
NSString *toPath = @"/Users/zkh/Desktop/to";
NSArray *subPaths = [[NSFileManager defaultManager] subpathsAtPath:fromPath];
NSInteger count = subPaths.count;
dispatch_apply(count, dispatch_get_global_queue(0, 0), ^(size_t index) {
NSString *fromFilePath = [fromPath stringByAppendingPathComponent:subPaths[index]];
NSString *toFilePath = [toPath stringByAppendingPathComponent:subPaths[index]];
[[NSFileManager defaultManager] moveItemAtPath:fromFilePath toPath:toFilePath error:nil];
NSLog(@"当前线程---%@", [NSThread currentThread]);
});
}
GCD中队列组的作用
- 使用队列组,除了可以开启新的线程,同时还能通过group监听队列中任务的执行情况
- 代码示例:
- (void)group1{
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@"执行任务1----%@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"执行任务2----%@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"执行任务3----%@", [NSThread currentThread]);
});
//dispatch_group_notify本事是异步执行的,不会阻塞主线程
dispatch_group_notify(group, queue, ^{
NSLog(@"----任务执行完毕-----");
});
NSLog(@"========end=========");
}
如何处理GCD造成的死锁问题
- 将队列改为非主队列
- 将调度方式改为异步调度
NSOperation和GCD的区别?
- 实现机制的区别
- 使用这两者执行任务都会由系统自动创建、销毁子线程
- GCD底层是基于C语言的,NSOperation则是对GCD的封装,是面向对象的
- NSOperation只有两种类型的队列
- 主队列和非主队列(非主队列既可以是并行的也可以是串行的,默认是并行的)
- GCD拥有四种类型的队列 (main, global, create[serial], create[concurrent] )
- 使用上的区别
- GCD的使用更轻量级,而NSOperation作为对象提供了更丰富的API
- 在NSOperationQueue中,可以随时取消要准备执行的任务,而GCD没法停止已经加入queue的block中的任务
- KVO能应用在NSOperation中,以监听一个Operation是否完成或取消,这样能比GCD更加有效地掌控我们执行的后台任务
- NSOperation通过继承,可以提高代码的复用度,这比GCD更有自由度和扩展性
- 同一个并行队列中的任务执行时,我们能够设置NSOperation的priority(优先级),使之按顺序执行,而在GCD中,要使block中的任务实现这个功能,需要大量复杂代码
自定义继承于NSOperation的类有什么好处?要注意什么?
- 好处
- 可提高代码的封闭性
- 可提高代码的复用性
- 注意点
- 必须在.m中重写main方法,main方法是操作任务的入口
什么是最大任务并发数?NSOperationQueue中如何操纵队列中的任务?
- 线程的最大任务并发数:异步执行时同一时间内可以同时执行的操作的最大数
- 当maxConcurrentOperationCount = 1,只能执行一个操作,队列为串行队列
- 当maxConcurrentOperationCount = 0,无法执行任何操作
- 当maxConcurrentOperationCount > 1,队列为并发队列
- 默认条件下maxConcurrentOperationCount = -1,代表最大并发数没有限制
- 代码示例:
- (IBAction)startOperation:(id)sender {
self.queue = [[NSOperationQueue alloc] init];
self.queue.maxConcurrentOperationCount = 1;
[self.queue addOperationWithBlock:^{
for (NSInteger i = 0; i < 2000; i++) {
NSLog(@"1----%ld----%@",i,[NSThread currentThread]);
}
}];
[self.queue addOperationWithBlock:^{
for (NSInteger i = 0; i < 2000; i++) {
NSLog(@"2----%ld----%@",i,[NSThread currentThread]);
}
}];
[self.queue addOperationWithBlock:^{
for (NSInteger i = 0; i < 2000; i++) {
NSLog(@"3----%ld----%@",i,[NSThread currentThread]);
}
}];
[self.queue addOperationWithBlock:^{
for (NSInteger i = 0; i < 2000; i++) {
NSLog(@"4----%ld----%@",i,[NSThread currentThread]);
}
}];
}
- (IBAction)suspendOperation:(id)sender {
//暂停队列中的任务,但是必须等队列中当前正在执行的任务完成后才能暂停
self.queue.suspended = YES;
}
- (IBAction)continueOperation:(id)sender {
//继续执行队列中的任务
self.queue.suspended = NO;
}
- (IBAction)cancelOperation:(id)sender {
//注意该方法内部会调用NSOperationQueue的cancel方法,取消后无法恢复(继续)!
[self.queue cancelAllOperations];
}
NSOperationQueue处理A,B,C三个线程,要求执行完A,B后才能执行C,怎么做?
- 添加依赖关系,AB都依赖C
- 代码示例:
- (void)operation{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *opA = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"A----%@",[NSThread currentThread]);
}];
NSBlockOperation *opB = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"B----%@",[NSThread currentThread]);
}];
NSBlockOperation *opC = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"C----%@",[NSThread currentThread]);
}];
//注意:不能互相依赖
[opC addDependency:opA];
[opC addDependency:opB];
[queue addOperation:opA];
[queue addOperation:opB];
[queue addOperation:opC];
NSLog(@"----end----");
}
有一个需求,需要将N个请求全部完成之后执行某个操作,该如何处理
- GCD中可以使用栅栏函数或者队列组
- NSOperation中可以添加依赖关系
设计一个多线程,分别下载2张图片后进行组合
- 可以使用GCD的栅栏函数或者队列组,可以使用NSOperation的依赖关系
- 代码示例:(这里以GCD中使用队列组为例)
- (void)combineImages{
/*
1.先开启子线程下载图片1和图片2
2.通过任务组在步骤1执行完后合成图片
3.在主线程中设置图片
*/
dispatch_queue_t queue = dispatch_queue_create("image", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://scimg.jb51.net/allimg/160918/103-16091Q12135548.jpg"]];
self.image1 = [UIImage imageWithData:data];
});
dispatch_group_async(group, queue, ^{
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://pic44.nipic.com/20140717/12432466_121957328000_2.jpg"]];
self.image2 = [UIImage imageWithData:data];
});
dispatch_group_notify(group, queue, ^{
UIGraphicsBeginImageContext(CGSizeMake(300, 300));
[self.image1 drawInRect:CGRectMake(0, 0, 150, 150)];
[self.image2 drawInRect:CGRectMake(150, 150, 150, 150)];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
});
});
}
使用多线程做多图下载并进行缓存的思路?
- 先检查内存中是否有图片缓存-->有就调用
- 没有就检查磁盘(沙盒)中是否有图片缓存-->有就调用
- 都没有就显示占位图片, 并检查是否正在下载
- 如果不是正在下载就创建子线程来下载图片
- 下载完后移除下载操作的缓存, 并把图片存入内存和沙盒中, 再刷新表格
- 如果出现内存警告, 应将内存中所有图片缓存清空, 并取消正在子线程的所有下载任务