一、进程
- 进程是指在系统中正在运行的一个应用。一般说来,一个进程就是一个运行的应用。
- 每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内。
二、线程
- 一个进程想要执行任务,就必须得有线程(每一个进程至少要有1条线程)
- 一个进程(程序)的所有的任务都在线程中执行(可以认为线程就是进程中的执行路径)
三、线程的串行
- 一个线程中的所有任务都是串行的
- 如果要在一个线程中执行多个任务,则必须一个一个地按顺序执行。
- 也就是说,在同一时间内,一个线程只能执行一个任务
四、多线程
- 一个进程中可以开启多条线程,每条线程可以并行(同时)执行不同的任务。
五、多线程的原理
- CPU在同一时间只能处理一条线程,换句话说,在同一时间,只有一条线程在工作(执行)。
- 多线程的并发(同时)执行,其实是CPU快度地在多线程之间调度(切换)。
- 如果CPU的调度线程的速度足够快,就会造成多线程并发(同时)执行的假象。
六、多线程的优缺点
1.优点:
- 能够适当的提高程序的执行效率
- 能够适当的提高资源的利用率(CPU、内存利用率等)
2.缺点:
- 创建线程是有开销的,iOS下主要成本包括:内核数据结构(大约1KB)、栈空间(子线程512KB、主线程1MB,也可以使用-setStackSize:设置,单必须是4k的倍数,而且最小是16k),创建线程大约需要90毫秒的创建时间。
- 如果开启大量的线程,会降低程序的性能。
- 线程越多,CPU就会在调度线程上的开销越大。
- 线程越多,程序的设计就会更加的复杂:比如线程之间的通信、多线程的数据共享。
七、主线程
- 一个程序运行后,默认会开启一跳线程,此线程称为“主线程”或者“UI线程”。
- 主线程的作用:1.显示、刷新界面。2.处理UI事件(比如点击事件、滚动事件、拖拽事件等)。
- 主线程的使用注意:1.别将比较耗时的操作放在主线程,如果有耗时操作,将其放在子线程(后台线程)中去。
八、多线程的实现
- NSThread(OC语言)
- GCD(C语言)
- NSOperation(OC语言)
九、NSThread
1.创建、启动线程(object是run方法的参数)
NSThread *thrad = [[NSThread alloc]initWithTarget:self selector:@selector(run:) object:@"HP"]; [thrad start];
线程一启动(start),就会在线程thread中调用self的run方法。
2.主线相关用法:
- +(NSThread *)mainThread; // 获得主线程
- + (BOOL)isMainThread; // 是否为主线程
- - (BOOL)isMainThread; // 是否为主线程
3.其他用法:
- 获得当前线程:[NSThread currentThread];
- 设置线程的名字: - (void)setName:(NSString *)name;
- 获取线程的名字: - (NSString *)name;
4.其他创建线程方式:
- 创建线程后自动启动线程(此方式是没有返回值的):[NSThread detachNewThreadSelector:@Selector(run) toTarget:self WithObject:nil];
- 隐式创建线程(此方式是没有返回值的):[self performSelectorInBackground:@selector(run) withObject:nil];
- 此两种方式创建线程的优点:简单快捷;缺点:由于没有返回值,所以无法对线程进行更详细的设置。
5.线程的状态
- 线程创建后会进入可调度线程池中等待CPU调度。
- 线程的启动:- (void)start; 当线程的任务执行完毕就会自动进入死亡(指创建的线程对象为局部变量时)。
- 阻塞(暂停)线程:(1)+(void)sleepUntilDate:(NSDate *)date; (2) +(void)sleepForTimeInterval:(NSTimeInterval)ti;
- 强制停止线程: +(void)exit;
- 注意:一旦线程停止(死亡)了,就不能再次开启,只能重新创建。
6.线程的安全隐患
- 资源共享
- 一块资源可能会被多个线程共享。
- 比如多个线程访问同一个对象、同一个变量、同一个文件。
7.线程安全隐患的解决---互斥锁
- 互斥锁使用格式
- @synchronized(锁对象){ // 需要锁定的代码 }
- 注意:锁定一份代码只能用一把锁,用多把锁是无效的的。
- 互斥锁的优缺点:
- 优点:能够有效的防止多线程抢夺资源造成的安全问题。
- 缺点:需要消耗大量的CPU资源(因为互斥锁锁定一段代码,别的线程需要等待解锁进入)。
- 互斥锁的使用前提:多条线程抢夺同一块资源。
- 相关专业术语:线程同步。
- 线程同步的意思:多条线程在同一条线上执行(按顺序地执行任务)。
- 互斥锁就是使用了线程同步技术。
8.原子和非原子-----atomic和nonatmoic
- atomic:线程安全(系统默认会在setter方法加锁),需要消耗大量资源。
- nonatomic:非线程安全,适合内存小的移动设备。
9.线程间的通信
- 线程间通信常用的方法:
-
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
-
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait
- NSPort、NSMessagePort、NSMachPort(了解)
-
十、Grand Central Dispatch(GCD,大中枢调度)
1.GCD介绍
- 纯C语言编写。
- GCD的优势:
- GCD是苹果公司为多核的并行运算提出的解决方案。
- GCD会自动利用CPU的多核(比如双核、四核)。
- GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)。
- 程序员只需要告诉GCD要执行什么任务,不需要编写任何线程管理代码。
2.任务和队列
- GCD中的2个概念:
- 任务:执行什么操作。
- 队列:用来存放任务。
- GCD的使用步骤(2步):
- 定制任务:确定想做的事情。
- 将任务添加到队列中:
- GCD会自动将队列中的任务取出来放到对应的线程中执行。
- 任务的取出遵循队列的FIFO的原子:先进先出,后进后出。
- 执行任务:GCD中有两个用来执行任务的常用函数。
- 同步的方式执行:dispatch_sync(<#dispatch_queue_t queue#>, <#^(void)block#>),其中,queue:队列,block:任务。
- 异步的方式执行:dispatch_async(<#dispatch_queue_t queue#>, <#^(void)block#>)。
- 同步和异步的区别:
- 同步:只能在当前线程中执行任务,不具备开启新线程的能力。
- 异步:可以在新的线程中执行任务,具备开新线程的能力(如果遇到主队列,则不会开新线程)。
- 队列类型:GCD的队列可以分为2大类型。
- 并发队列(Concurrent Dispatch Queue):
- 允许多个任务并发(同时)执行(自动开启多个线程,同时执行任务)。
- 并发功能只有在异步(dispatch_async)函数下才有效。
- 串行队列(Serial Dispatch Queue):
- 让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)。
- 并发队列(Concurrent Dispatch Queue):
- 容易混淆的术语:同步、异步、并行、串行
- 同步和异步的主要区别:能不能开启新的线程。
- 同步:只能在当前线程中执行任务,不具备开启新线程的能力。
- 异步:可以在新的线程中执行任务,具备开启新线程的能力。
- 并发和串行的主要区别:任务的执行方式。
- 并发:允许多个任务并发(同时)执行。
- 串行:一个任务执行完毕后,再执行下个任务。
- 同步和异步的主要区别:能不能开启新的线程。
- 并发队列:
- 使用dispatch_queue_create函数创建队列:dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
- const char *label :设置队列的名字。
- dispatch_queue_attr_t attr : 设置队列的类型(Concurrent Dispatch Queue、Serial Dispatch Queue)。
- 使用GCD默认提供的全局并发队列。
- 使用dispatch_get_global_queue(<#long identifier#>, <#unsigned long flags#>)函数获得全局的并发队列。identifier:表示优先级;flags:传0,为将来备用,苹果推荐传0。
- 使用dispatch_queue_create函数创建队列:dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
- 串行队列:
- 使用dispatch_queue_create函数创建队列:设置队列的类型(Concurrent Dispatch Queue、Serial Dispatch Queue)为NULL,即可得到串行队列。
- 使用主队列(跟主线程相关的队列):
- 主队列是GCD自带的一种特殊的串行队列。
- 放在主队列的任务,都会放到主线程中执行(所以异步执行也不是一定开新线程)。
- 使用dispatch_get_main_queue()获得主队列。
- 各种队列执行效果
- 注意:
- 使用sync函数往当前串行队列中添加任务,将会卡主当前的串行队列。
3.线程间的通信
- 从子线程回归主线程
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // 执行耗时异步操作... dispatch_async(dispatch_get_main_queue(), ^{ // 回归主线程,刷新UI界面... }); });
4.常用函数
- barrier在一个并发队列中创建一个同步点。
// 当在并发队列中,在barrier之前任务执行结束后才会执行barrier的函数,barrier执行结束后才会执行barrier后面的任务。 // 这里指定的并发队列需要通过dispatch_queue_create函数创建的。如果你传的是一个串行队列或者全局并发队列,这个函数等同dispatch_async函数。 dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
- 延迟函数
-
performSelector延迟函数
// aSelector:延迟执行的方法; anArgument:执行方法的参数;delay: 延迟执行的时间 - (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
-
dispatch_after延迟函数
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)2 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ });
- NSTimer定时器延迟
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(delayRun) userInfo:nil repeats:NO];
-
- 一次性代码
- dispatch_once:保证某段代码在整个程序运行过程中只被执行一次。
static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ // 执行一次性代码(默认是线程安全的) });
- dispatch_once:保证某段代码在整个程序运行过程中只被执行一次。
- 快速迭代
- Dispatch_apply:能够进行快速迭代遍历
// size_t iterations:迭代的次数; dispatch_queue_t queue:队列(并发); dispatch_apply(10, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^(size_t index) { // 执行10次,index顺序不确定 });
- Dispatch_apply:能够进行快速迭代遍历
-
队列组
- 首次执行队列组中任务,等队列组中所有任务执行完后,执行dispatch_group_notify函数中的任务。
dispatch_group_t group = dispatch_group_create(); dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_group_async(group, queue, ^{ // 执行耗时异步操作 NSLog(@"----------1操作----------"); }); dispatch_group_async(group, queue, ^{ // 执行耗时异步操作 NSLog(@"----------2操作----------"); }); dispatch_group_async(group, queue, ^{ // 执行耗时异步操作 NSLog(@"----------3操作----------"); }); dispatch_group_async(group, queue, ^{ // 执行耗时异步操作 NSLog(@"----------4操作----------"); }); dispatch_group_async(group, queue, ^{ // 执行耗时异步操作 NSLog(@"----------5操作----------"); }); // 此函数也是异步操作,并且也是新开线程 dispatch_group_notify(group, queue, ^{ // 此组(group)里面所有的异步任务都操作完后,执行此函数任务 NSLog(@"group组里面所有的任务都已经做完%@", [NSThread currentThread]); });
- 首次执行队列组中任务,等队列组中所有任务执行完后,执行dispatch_group_notify函数中的任务。
十一、NSOperation
1.具体步骤:
- 先将需要执行的操作封装到一个NSOperation对象中(NSOperation就是存放任务的)
- 然后将NSOperation对象添加到NSOperationQueue中(NSOperationQueue为队列)
- 系统会自动将NSOperationQueue中的NSOperation取出来
- 将取出的NSOperation封装的操作(任务)放到一条新线程中执行
2.NSOperation的使用:NSOperation是一个抽象类,并不具备封装操作的能力,必须使用它的子类来操作,方式如下:
- NSInvocationOperation
// 1.创建 NSInvocationOperation 对象 NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(run) object:nil]; // 2.调用 start 方法开始执行操作 [operation start]; // 注意:默认情况下,调用start方法后不会去开启一条新线程,而是在当前线程同步执行操作,只有将NSOperation放到一个NSOperationQueue中,才会异步执行操作。
- NSBlockOperation
// 创建 NSBlockOperation 对象 NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"下载1------%@", [NSThread currentThread]); }]; // 添加更多的操作 [operation addExecutionBlock:^{ NSLog(@"下载2------%@", [NSThread currentThread]); }]; [operation addExecutionBlock:^{ NSLog(@"下载3------%@", [NSThread currentThread]); }]; [operation addExecutionBlock:^{ NSLog(@"下载4------%@", [NSThread currentThread]); }]; // 开始执行 [operation start]; // 注意:只要NSBlockOperation封装的任务操作 > 1,就会异步执行操作。
- 自定义子类继承NSOperation,实现内部相应的方法
3.NSOperationQueue
- 队列的类型
/* NSOperationQueue 队列类型 1. 主队列:[NSOperationQueue mainQueue]; 2. 其他队列:[[NSOperationQueue alloc]init]; */
- NSOperation可以调用start方法执行任务,但默认是同步执行的。
- 如果将NSOperation添加到NSOperationQueue中,系统会自动异步执行NSOperation中的操作。
- 添加操作到NSOperationQueue中
NSOperationQueue *queue = [[NSOperationQueue alloc]init]; // 直接利用block执行耗时操作 [queue addOperationWithBlock:^{ // 直接在此处添加任务操作,相当于创建了operation NSLog(@"开新线程执行耗时操作..."); }]; // 创建 NSInvocationOperation 对象 NSInvocationOperation *opration1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(downPicture1) object:nil]; NSInvocationOperation *opration2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(downPicture2) object:nil]; // 创建 NSBlockOperation 对象 NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"下载图片3------------%@", [NSThread currentThread]); }]; [operation3 addExecutionBlock:^{ NSLog(@"下载图片3-1------------%@", [NSThread currentThread]); }]; [operation3 addExecutionBlock:^{ NSLog(@"下载图片3-2------------%@", [NSThread currentThread]); }]; NSBlockOperation *operation4 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"下载图片4------------%@", [NSThread currentThread]); }]; [operation3 addExecutionBlock:^{ NSLog(@"下载图片4-1------------%@", [NSThread currentThread]); }]; [operation3 addExecutionBlock:^{ NSLog(@"下载图片4-2------------%@", [NSThread currentThread]); }]; // 自定义Opetation,继承NSOperation类 HPCustomOperation *operaton5 = [[HPCustomOperation alloc]init]; // 加入队列中的操作是并发执行 [queue addOperations:@[opration1, opration2, operation3, operation4, operaton5] waitUntilFinished:NO]; NSLog(@"--------------------------------------"); } - (void)downPicture1 { NSLog(@"下载图片1------------%@", [NSThread currentThread]); } - (void)downPicture2 { NSLog(@"下载图片2------------%@", [NSThread currentThread]); }
- 最大并发队列(实际是控制队列是串行还是并行)
// 并发执行,最多同时执行三条线程 queue.maxConcurrentOperationCount = 3; // 串行队列 queue.maxConcurrentOperationCount = 1; // 无并发性,队列中操作不会执行 queue.maxConcurrentOperationCount = 0; // 总结:> 1 时即可为并发队列;= 1 时为串行队列;不设置,默认为并发队列,最大并发执行数由系统默认。
- NSOperationQueue 的挂起和取消
NSOperationQueue *queue = [[NSOperationQueue alloc]init]; // 暂时挂起 queue.suspended = YES; // 线程取消 [queue cancelAllOperations]; // 注意:1. 线程队列取消,会停止接下来的任务,对于已经执行未完成的任务是不会立刻停止的。 2. 如果是自定义Operation,则实际是调用自定义Operation内部的cancelled方法。 3. 如果是自定义Operation,队列取消时,建议在每次执行耗时操作都来判断下,该任务所在队列是否经行了取消操作。
-
NSOperation 的依赖和监听
// 依赖函数 - (void)addDependency:(NSOperation *)op; // 移除依赖 - (void)removeDependency:(NSOperation *)op; // 监听函数(监听某个任务操作完成后做什么。。。) @property (nullable, copy) void (^completionBlock)(void) NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"执行任务1"); }]; NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"执行任务2"); }]; NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"执行任务3"); }]; NSBlockOperation *operation4 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"执行任务4"); }]; NSBlockOperation *operation5 = [NSBlockOperation blockOperationWithBlock:^{ for (int i = 0; i < 1000; i++) { NSLog(@"执行任务5"); } }]; NSBlockOperation *operation6 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"执行任务6"); }]; // 依赖: operation3依赖于operation1、2、5 任务,必须等operation1、2、5三个任务做完了才会执行operation3 [operation3 addDependency:operation1]; [operation3 addDependency:operation2]; [operation3 addDependency:operation5]; // 监听: 监听operation3 任务操作执行完,才会执行此block中操作 [operation3 setCompletionBlock:^{ NSLog(@"operation3已经执行完了"); }]; [queue addOperations:@[operation1, operation2, operation3, operation4, operation5, operation6] waitUntilFinished:NO];
- NSOperation 线程间的通信
// 线程间的通信:子线程做耗时操作,结束后回归主线程,做刷新UI界面操作。 NSOperationQueue *queue = [[NSOperationQueue alloc]init]; [queue addOperationWithBlock:^{ // 子线程执行耗时操作 NSURL *url = [NSURL URLWithString:@"http://www.zhlzw.com/sj/UploadFiles_9645/201208/20120819210352682.jpg"]; NSData *data = [NSData dataWithContentsOfURL:url]; UIImage *image = [UIImage imageWithData:data]; // 回归主线程,刷新UI界面 [[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.img.image = image; }]; }];