多线程与网络线程的几种创建方法
线程的创建方法
pthread
- 创建 pthread_create
- 只要create一次就会创建一个新的线程
-
系统会自动在子线程中调用传入的函数
{ // 将耗时的操作放在子线程中 /* 第一个参数:pthread_t *restrict 线程的代号 第二个参数:const pthread_attr_t *restrict 线程的属性 第三个参数:void *(*)(void *) 指向函数的指针,将来线程需要执行的方法 第四个参数:void *restrict 给第三个参数的指向函数 传递的参数 */ pthread_t threadID; // 只要create一次就会创建一个新的线程 pthread_create(&threadID, NULL, &demo, @"xiao"); } void *demo(void * index) { for (int i = 0; i < 100; ++i) { NSLog(@"%i------%@", i, [NSThread currentThread]); } return NULL; // 比较特殊,要有返回值 }
NSThread
注意点:死了不能再活过来(即要重新调用)
-
1.几种创建方式
-
1)alloc-initWithTarget方法
- 注意:需要手动调用start方法启动线程
- 特点:系统内部会retain当前线程
- 生命周期:只有线程中的方法执行完毕,系统才会将其释放
-
代码:
// 1.创建一个新的线程 NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:nil]; // 2.启动线程 [thread start];
-
2)detachNewThreadSelector:方法
- 注意:不需要手动调用start方法启动线程
- 缺点:没有返回值,不能对线程进行更多的设置
- 应用场景:需要快速简便的执行线程
-
代码:
// 分离出一个新的子线程 [NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:nil];
-
3)performSelectorInBackground:方法
- 注意:不需要手动调用start方法启动线程,当前控制器调用(self)
- 缺点:没有返回值,不能对线程进行更多的设置
- 应用场景:需要快速简便的执行线程
-
代码:
// 用后台线程调用run:函数 [self performSelectorInBackground:@selector(run:) withObject:nil];
-
4)相关属性
-
-
2.线程状态
- 新建状态(New):创建出来
- 就绪状态(Runnable):调用start
- 运行状态(Running):被CUP调用
-
阻塞状态(Blocked):调用了sleep方法/等待同步锁
// 指定时间(阻塞线程,阻塞5秒的时间) [NSThread sleepForTimeInterval:5.0f]; // 指定日期(阻塞线程,阻塞到现在开始的2秒钟之后) [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]]; // 指定日期(睡眠醒不来) [NSThread sleepUntilDate:[NSDate distantFuture]]; [NSThread exit];
-
死亡状态(Dead):线程任务执行完毕/异常/强制退出(exit)
-
3.互斥锁(安全隐患)
- 应用场景:多线程存在资源抢夺(多个线程同时访问某个文件/变量等)
- 注意点:
- 只要枷锁就会消耗性能
- 如果想真正的锁住代码, 那么多个线程必须使用同一把锁才行
- 加锁的时候尽量缩小范围, 因为范围越大性能就越低
-
技巧:如何快速记住加锁的单词
-
[NSUserDefaults standardUserDefaults] synchronize
快速记忆的方法// self是锁对象:唯一性 @synchronized:(self) { // 加锁的内容 }
-
-
专业术语:线程同步(多条线程在同一条线上执行)
-
4.原子和非原子
- atomic:原子属性,为setter方法加锁,线程安全,性能低
- nonatomic:非原子属性,不会为setter方法加锁,线程不安全,性能高
-
5.线程间通信
- 体现:
- 1.一个线程传递数据给另一个线程
- 2.在一个线程中执行完特定任务后,让另一个线程执行接下来的任务
- 体现:
-
附:下载图片
-
1.获得下载图片的url
NSURL *url = [NSURL URLWIthString:@"图片下载地址"];
-
2.下载图片的二进制数据到本地(花费时间最长)
NSData *imageData = [NSData dataWithContentsOfURL:url];
-
3.把二进制数据转换成image
UIImage *image = [UIImage imageWithData:imageData];
-
4.回到主线程刷新UI(设置图片)很多个方法
[self performSelector:@selector(showImage:) withObject...];
-
5.在showImage中设置图片
self.imageView.image = image;
-
GCD
- 1.简介:(Grand Central Dispatch)牛逼的中枢调度器
- 2.特点:
- 为多核的并行运算提出的方案
- 自动利用更多的cup内核
- 自动管理线程的生命周期
- 缺少
- 3.任务和队列:执行什么操作,用来存放任务
-
4.同步和异步(能不能开线程)---封装任务,添加任务到队列中
- 同步: 只能在当前线程中执行任务,不具备开启新线程的能力,要求立刻执行
- 异步: 可以在新的线程中执行任务,具备开启新线程的能力
-
对应的两个函数
同步函数:dispatch_sync 异步函数:dispatch_async
-
5.队列类型(任务的执行方式)---保持任务,安排|调度任务
- 并发队列: 允许多个任务并发(同时)执行
- 串行队列: 一个任务执行完毕后,再执行另一个任务
-
6.GCD的基本使用
- 01 异步函数+并发队列:开启多条线程,并发执行任务
- 02 异步函数+串行队列:开启一条线程,串行执行任务
- 03 同步函数+并发队列:不开线程,串行执行任务
- 04 同步函数+串行队列:不开线程,串行执行任务
- 05 异步函数+主队列:不开线程,在主线程中串行执行任务
- 06 同步函数+主队列:不开线程,串行执行任务(注意死锁发生)
- 主队列特点:如果发现主线程正在执行代码,那么就暂停调度队列里面的任务,即是死锁现象,需要设置一个子控制器来执行
-
07 注意同步函数和异步函数在执行顺序上面的差异
- 异步函数:不需要当前代码执行完毕,就可以执行后面的代码
- 同步函数:要等到当前代码执行完毕,才可以继续往下执行(一直等待)
-
7.GCD的常用通信代码模板
-
1.创建队列: 保存任务,安排|调度任务
// 1.创建队列: 保持任务,安排|调度任务 /* 第一个参数:const char *label 字符串 第二个参数:dispatch_queue_attr_t attr 指示出是并发还是串行 DISPATCH_QUEUE_CONCURRENT 并发 DISPATCH_QUEUE_SERIAL 串行 */ dispatch_queue_t queue = dispatch_queue_create("xiao", DISPATCH_QUEUE_CONCURRENT); dispatch_queue_t queue = dispatch_queue_create("xiao", DISPATCH_QUEUE_SERIALS); /* 第一个参数:long identifier 队列的优先级, DISPATCH_QUEUE_PRIORITY_DEFAULT == 0 第二个参数:unsigned long flags 此参数暂时无用,设置为0 */ dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
-
2.创建同步/异步函数: 封装任务,添加任务到队列中
// 2.异步函数: 封装任务,添加任务到队列中 dispatch_async(queue, ^{ // 执行代码 }); // 2.同步函数: 封装任务,添加任务到队列中 dispatch_sync(queue, ^{ // 执行代码 });
-
3.下载图片小事例
// 1.创建队列(全局并发队列) dispatch_queue_t queue = dispatch_get_global_queue(0, 0); // 2.下载图片(耗时)-->放在子线程中(异步函数) dispatch_async(queue, ^{ // 创建url NSURL *url = [NSURL URLWithString:@"http://www.qqjia.com/z/06/tu8000_5.jpg"]; // 通过url将图片转换成二进制NSData NSData *data = [NSData dataWithContentsOfURL:url]; // 将NSData转换成图片 UIImage *image = [UIImage imageWithData:data]; // 更新UI --> 在主线程中 // 如果是通过异步函数调用, 那么会先执行完所有的代码, 再更新UI // 如果是同步函数调用, 那么会先更新UI, 再执行其它代码 dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"%@", [NSThread currentThread].name); self.imageView.image = image; NSLog(@"先更新UI"); }); NSLog(@"先执行"); });
-
-
8.常用函数
-
1.GCD的延迟执行
// DISPATCH_TIME_NOW:从什么时候开始计时(现在) // 2.0位置:间隔时间(延迟时间) // dispatch_get_main_queue():队列,决定block在哪个线程中调用,当是主队列时就是主线程调用 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ // 代码 });
-
2.GCD的栅栏函数(
barrier
)// 栅栏函数:可以控制队列中任务的执行顺序,前面的任务执行完毕后执行后面的任务 // 注意:这边只能使用并发队列(但是不能用全局并发队列) dispatch_barrier_async(queue, ^{ NSLog(@"---------"); };
-
3.一次性代码函数
// 保证整个程序运行中过程中执行一次,不能放在懒加载中,会为空 static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSLog(@"----once----"); };
-
4.快速迭代函数
// 第一个参数:size_t iterations 遍历的次数 // 第二个参数:dispatch_queue_t queue 队列,决定block在哪个线程调用,并发队列 // 第三个参数:^(size_t) (需要在size_t后面加上一个参数名)索引 dispatch_apply(size_t iterations, dispatch_queue_t queue, ^(size_t) { // 代码 });
-
5.队列组
// 创建队列组 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, ^{ // 代码 } // 第一种:(拦截通知)当所有任务都执行完毕后,来到该方法 dispatch_group_notify(group, queue, ^{ // 代码 } // 第二种:(拦截通知)一直等待,等待所有的任务都执行完毕后继续往下执行 | 阻塞 dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
-
小案例:下载两张图片,合成图片
代码
-
-
-
9.使用create函数创建的并发队列和全局并发队列的区别
- 1.全局并发队列在整个应用程序中本身是默认存在的,并且对应有高优先级、默认优先级、低优先级和后台优先级一共四个并发队列,我们只是选择其中的一个直接拿来用。而Crearte函数是实打实的从头开始去创建一个队列。
- 2.在iOS6.0之前,在GCD中凡是使用了带Crearte和retain的函数在最后都需要做一次release操作。而主队列和全局并发队列不需要我们手动release。当然了,在iOS6.0之后GCD已经被纳入到了ARC的内存管理范畴中,即便是使用retain或者create函数创建的对象也不再需要开发人员手动释放,我们像对待普通OC对象一样对待GCD就OK。
- 3.在使用栅栏函数的时候,苹果官方明确规定栅栏函数只有在和使用create函数自己的创建的并发队列一起使用的时候才有效(没有给出具体原因)
NSOperation
- 1.概念:
- NSOperation是对GCD的包装
- 两个核心概念[队列+操作]
-
2.NSOperation基本使用:
- NSOperation本身是抽象类,只能使用它的子类
- 三个子类:NSBlockOperation/NSInvocationOperation/自定义继承NSOperation的类
- NSOperation和NSOperationQueue配合使用实现多线程编程
-
相关代码:
-
1) NSBlockOperation
//1.封装操作 NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ // 主线程执行 NSLog(@"download1---%@",[NSThread currentThread]); }]; // 追加任务 // 追加的任务在子线程中并发执行 [op1 addExecutionBlock:^{ NSLog(@"download4---%@",[NSThread currentThread]); }]; //2.开始执行 [op1 start];
-
2) NSInvocationOpeartion
//1.封装操作 /* 第一个参数:目标对象 self 第二个参数:调用方法 第三个参数:调用方法需要传递的参数 */ NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download1) object:nil]; //2.启动操作 [op1 start];
-
3) 自定义继承NSOperation的类
// 自定义一个继承NSOperation的类(重写main函数) - (void)main { NSLog(@"1---%@", [NSThread currentThread]); } // 封装操作 LJSubOperation *subOperation1 = [[LJSubOperation alloc] init]; LJSubOperation *subOperation2 = [[LJSubOperation alloc] init]; // 开始执行 [subOperation1 start]; [subOperation2 start];
-
-
3.NSOperationQueue基本使用
- 两种队列
- 主队列 通过mainQueue获得,凡是放到主队列中的任务都将在主线程执行
- 非主队列 直接alloc init出来的队列。非主队列同时具备了并发和串行的功能,通过设置最大并发数属性来控制任务是并发执行还是串行执行
-
相关代码:
// 1.创建队列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 2.将操作添加到队列中(会自动调用start方法) [queue addOperation:bOpertion1]; //简便方法:该方法内部会自动将block块里面的任务封装为一个NSBlockOperation对象,然后添加到队列 [queue addOperationWithBlock:^{ // 代码 }];
- 两种队列
-
4.NSOperation的其它用法
-
4.1 设置最大并发数(控制任务并发和串行)
// 注意点:该属性需要在任务添加到队列中之前进行设置 // 该属性控制队列串行还是并发执行 // 如果该属性设置1,则为串行,大于1则就是并发的 // 系统默认的值位-1,如果该属性设置为0,则不会执行任何任务 queue.maxConcurrentOperationCount = 2;
-
4.2 暂停.恢复以及取消
// 暂停,只是不执行当前任务下面的操作,但是当前的还是会执行 self.queue.suspended = YES; // 恢复 self.queue.suspended = NO; // 取消队列中的所有操作,不可以恢复 [self.queue cancelAllOperations]; // 注意:苹果官方建议,每当执行完一次耗时操作之后,就查看一下当前队列是否为取消状态,如果是,那么就直接退出 // 好处是可以提高程序的性能 if (self.isCancelled) { return; }
-
-
5.操作依赖和操作监听
// op1和op2都是NSOperation操作,能保证操作1依赖于操作2,执行2再执行1 // 如果互相都依赖对方,那么两个都不依赖 [op1 addDependency:op2];
-
6.小案例
- 6.1 下载图片
看附件
- 6.2 图片下载综合案例
看附件 // 1.数据展示 // 2.内存存储(存储图片)NSMutableDictionary *iamges // 3.沙盒存储(Libriary/caches) NSString *caches = [NSSearchPathForDirectoriesInDimains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; // 4.问题1:数据混乱(重复下载)-->将操作加到操作缓存中 // 问题2:复用问题:判断之前设置占位图片self.imageView.image = nil; // 问题3:数据容错处理 // 发生内存警告:1.清空内存缓存 2.关闭所有的队列操作 3.清空所有的下载操作字典
-
7.附录
- addEd..内部调用的是start方法,start内部调用的是main