1. 进程、线程、多线程
进程 : 可以简单的理解, 一个应用程序就是一个进程;
线程 : 可以理解为在app中往后运行的通道, 一个进程可以有多个线程;
多线程: 并不是所有的框架都支持多线程, 必须要有多核的cpu支持才行, 单核cpu即使开了多线程运行速度也不会有变化, 开的线程数有几种说法, 其一: 线程数为手机核数的2到3倍, 比如一个双核手机, 开线程数为4到6条; 其二: 根据网络状态, 如果不是wifi状态, 一般开3到4条, 如果是wifi状态,可以开启5到6条;
多线程的目的: 将耗时的操作放在后台, 而不影响主线程与用户的交互;
多线程示意图:
多线程优缺点:
优点:
1). 能适当提高程序的执行效率,
2). 能适当提高资源的利用率
3). 线程上的任务执行完成后, 线程会自动销毁
缺点:
1). 开启线程需要占用一定的内存空间 ( 默认情况下,每一个线程都占用512Kb ) ( 以前主线程占用1MB空间,现在主线程和子线程都只占用512KB )
2). 如果开启大量的线程,会占用大量的内存空间,CPU会在N个线程之间切换,消耗大量的CPU资源,每个线程被调度的次数会降低,线程的执行效率降低;
3). 程序设计更加复杂,比如线程间的通信、多线程的数据共享
2 . 串行并行,同步异步
串行简单理解就是一个人负责多件事情, 并行是多个人一起负责多个事件;
同步是多个事件按顺序往下执行, 异步是多个事件在多个通道同时执行;
3 . 异步操作和多线程的联系和区别
异步大多数是多线程, 但也不一定是, block方法回调和dispatch的定时函数也是异步操作, 但不一定是多线程, 视当前的代码环境而定;
4. 多编程的技术方案
方案 | 简介 | 语言 | 线程生命周期 | 使用频率 |
---|---|---|---|---|
pthread |
|
C | 程序员管理 | 几乎不用 |
NSThread |
|
OC | 程序员管理 | 偶尔使用 |
GCD |
|
C | 自动管理 | 经常使用 |
NSOperation |
|
OC | 自动管理 | 经常使用 |
代码演练:
一、pthread
C
语言中 void *
与 OC
中的 id
类似[NSThread currentThread]
能够在任何多线程技术中,查询当前代码执行所在线程
number == 1
,表示主线程number != 1
,表示其他线程
- pthread 是 POSIX 多线程开发框架,由于是跨平台的 C 语言框架,在苹果的头文件中并没有详细的注释
- 要查阅 pthread 有关资料,可以访问 http://baike.baidu.com
pthread
#import <pthread.h>
touchesBegan
创建线程
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { /* 参数: 1. pthread_t *restrict: 需要 pthread_t(线程标示符) 类型的指针, C语言中的 _t/Ref,都是结构体 2. const pthread_attr_t *restrict: 线程属性 3. void *(*)(void *): 线程调用的函数 void * (*) (void *) 返回值 函数地址 参数类型 4. void *restrict: 第三个参数的参数 返回值: 0: 表示成功 非0表示失败,一个数字对应一个失败原因 */ // 1> 定义一个 C 字符串 // char *cName = "zhangsan"; // 2> OC 的字符串 NSString *name = @"lisi"; pthread_t threadId = NULL; int result = pthread_create(&threadId, NULL, demo, (__bridge void *)(name)); if (result == 0) { NSLog(@"成功"); } else { NSLog(@"失败"); } }
pthread
线程调用函数
/// 线程调用函数 void *demo(void * param) { NSLog(@"线程 = %@", [NSThread currentThread]); // 1.打印C语言字符串 // NSLog(@"param = %s", param); // 2.打印OC字符串 // NSString *name = (__bridge NSString *)(param); NSLog(@"param = %@", name); return NULL; }
*
C
语言中的 void *
和 OC
中的 id
是类似的OC
中,如果是 ARC
开发,编译器会在编译时,根据代码结构,自动添加 retain/release/autoreleaseARC
只负责管理 OC
部分的内存管理,而不负责 C
语言 代码的内存管理C
语言框架出现 retain/create/copy/new 等字样的函数,大多都需要 release,否则会出现内存泄漏C
和 OC
之间传递数据,需要使用 __bridge
进行桥接,桥接的目的就是为了告诉编译器如何管理内存
Xcode
的辅助功能添加二、NSThread
NSThread的3中创建线程方式
NSThread创建线程
创建线程第一种方式:
使用 NSThread
类的 alloc/init
创建线程
#pragma mark - 创建线程 /// 使用 alloc / init 创建线程 - (void)thread1 { NSLog(@"thread1 begin %@", [NSThread currentThread]); // 创建 NSThread 对象 NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(longOperation:) object:@"hello"]; // 启动线程 [thread start]; NSLog(@"thread1 end %@", [NSThread currentThread]); }
定义 子线程 执行的方法
#pragma mark - 线程执行方法 - (void)longOperation:(id)param { NSLog(@"longOperation begin %@", [NSThread currentThread]); NSLog(@"longOperation end %@", [NSThread currentThread]); }
touchesBegan:withEvent:
调用 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { [self thread1]; }
OC
中,任何一个线程执行代码都是从上向下顺序执行的[thread start]
; 执行后,会在另外一个线程执行 longOperation:
方法NSThread
的 类方法
detachNewThreadSelector
创建线程 /// 使用 NSThread 类方法 - (void)thread2 { [NSThread detachNewThreadSelector:@selector(longOperation:) toTarget:self withObject:@"hello2"]; }
detachNewThreadSelector
类方法 不需要启动
,会自动创建线程并执行 @selector
方法NSObject
分类方法
performSelectorInBackground
创建线程 /// 使用 NSObject 分类方法 - (void)thread3 { [self performSelectorInBackground:@selector(longOperation:) withObject:@"hello3"]; }
performSelectorInBackground
是 NSObject
的分类方法示意图:
GCD的方法很多,用法也很多,这里只列举一些常用的方法。常用的方法包括:
<span style="font-family:Open Sans, Clear Sans, Helvetica Neue, Helvetica, Arial, sans-serif;color:#333333;">/* *使用GCD 的多线程 *优点:有很多串行并线队列多线程,block实现线程方法,高级,好用,方法多。 *缺点:在很多不需要高级控制线程的场景可以不用使用GCD */ -(void)GCDFunction{ NSLog(@"GCDFunction start"); //获取一个队列 dispatch_queue_t defaultQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //dispatch_async:异步方式执行方法(最常用) // dispatch_async(defaultQueue, ^{ // [self function1]; // }); //dispatch_sync:同步方式使用场景,比较少用,一般与异步方式进行调用 // dispatch_async(defaultQueue, ^{ // NSMutableArray *array = [self GCD_sync_Function]; // dispatch_async(dispatch_get_main_queue(), ^{ // //利用获取的arry在主线程中更新UI // // }); // }); //dispatch_once:一次性执行,常常用户单例模式.这种单例模式更安全 // static dispatch_once_t onceToken; // dispatch_once(&onceToken, ^{ // // code to be executed once // NSLog(@"dispatch_once"); // }); //dispatch_after 延迟异步执行 // dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC); // dispatch_after(popTime, defaultQueue, ^{ // NSLog(@"dispatch_after"); // }); //dispatch_group_async 组线程可以实现线程之间的串联和并联操作 // dispatch_group_t group = dispatch_group_create(); // NSDate *now = [NSDate date]; // //做第一件事 2秒 // dispatch_group_async(group, defaultQueue, ^{ // [NSThread sleepForTimeInterval:2]; // NSLog(@"work 1 done"); // }); // //做第二件事 5秒 // dispatch_group_async(group, defaultQueue, ^{ // [NSThread sleepForTimeInterval:5]; // NSLog(@"work 2 done"); // }); // // //两件事都完成后会进入方法进行通知 // dispatch_group_notify(group, defaultQueue, ^{ // NSLog(@"dispatch_group_notify"); // NSLog(@"%f",[[NSDate date]timeIntervalSinceDate:now]);//总共用时5秒,因为2个线程同时进行 // }); //dispatch_barrier_async :作用是在并行队列中,等待前面的队列执行完成后在继续往下执行 // dispatch_queue_t concurrentQueue = dispatch_queue_create("my.concurrent.queue", DISPATCH_QUEUE_CONCURRENT); // dispatch_async(concurrentQueue, ^{ // [NSThread sleepForTimeInterval:2]; // NSLog(@"work 1 done"); // }); // dispatch_async(concurrentQueue, ^{ // [NSThread sleepForTimeInterval:2]; // NSLog(@"work 2 done"); // }); // //等待前面的线程完成后执行 // dispatch_barrier_async(concurrentQueue, ^{ // NSLog(@"dispatch_barrier_async"); // }); // // dispatch_async(concurrentQueue, ^{ // [NSThread sleepForTimeInterval:3]; // NSLog(@"work 3 done"); // }); //dispatch_semaphore 信号量的使用,串行异步操作 // dispatch_semaphore_create 创建一个semaphore // dispatch_semaphore_signal 发送一个信号 // dispatch_semaphore_wait 等待信号 /*应用场景1:马路有2股道,3辆车通过 ,每辆车通过需要2秒 *条件分解: 马路有2股道 <=> dispatch_semaphore_create(2) //创建两个信号 三楼车通过 <=> dispatch_async(defaultQueue, ^{ } 执行三次 车通过需要2秒 <=> [NSThread sleepForTimeInterval:2];//线程暂停两秒 */ dispatch_semaphore_t semaphore = dispatch_semaphore_create(2); dispatch_async(defaultQueue, ^{ dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); [NSThread sleepForTimeInterval:2]; NSLog(@"carA pass the road"); dispatch_semaphore_signal(semaphore); }); dispatch_async(defaultQueue, ^{ dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); [NSThread sleepForTimeInterval:2]; NSLog(@"carB pass the road"); dispatch_semaphore_signal(semaphore); }); dispatch_async(defaultQueue, ^{ dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); [NSThread sleepForTimeInterval:2]; NSLog(@"carC pass the road"); dispatch_semaphore_signal(semaphore); }); //应用场景2 :原子性保护,保证同时只有一个线程进入操作 // dispatch_semaphore_t semaphore = dispatch_semaphore_create(1); // for(int i=0 ;i< 10000 ;i++){ // dispatch_async(defaultQueue, ^{ // dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); // NSLog(@"i:%d",i); // dispatch_semaphore_signal(semaphore); // }); // } NSLog(@"GCDFunction end"); }</span>
NSOperation需要在NSOperationQueue中使用,通过queue可以实现先进先出的队列任务,可以添加或取消任务,NSOperation有2个重要的子类,分别是:NSInvocationOperation,NSBlockOperation,分别表示调用一个方法或调用一个block的任务。 NSOperation是比GCD更高层次的api,相同的线程操作如果能用NSOperation操作就尽量用,不能实现的线程操作才使用GCD.相比GCD,NSOperation还有个好处,就是任务可以被取消,而GCD不可以。
<span style="font-family:Open Sans, Clear Sans, Helvetica Neue, Helvetica, Arial, sans-serif;font-size:14px;color:#333333;">-(void)NSOperationFunction{ NSOperationQueue *queue = [[NSOperationQueue alloc]init]; //设置队列最大同时进行的任务数量,1为串行队列 [queue setMaxConcurrentOperationCount:1]; //添加一个block任务 [queue addOperationWithBlock:^{ sleep(2); NSLog(@"block task 1"); }]; [queue addOperationWithBlock:^{ sleep(2); NSLog(@"block task 2"); }]; //显示添加一个block任务 NSBlockOperation *block1 = [NSBlockOperation blockOperationWithBlock:^{ sleep(2); NSLog(@"block task 3"); }]; //设置任务优先级 //说明:优先级高的任务,调用的几率会更大,但不表示一定先调用 [block1 setQueuePriority:NSOperationQueuePriorityHigh]; [queue addOperation:block1]; NSBlockOperation *block2 = [NSBlockOperation blockOperationWithBlock:^{ sleep(2); NSLog(@"block task 4,任务3依赖4"); }]; [queue addOperation:block2]; //任务3依赖4 [block1 addDependency:block2]; //设置任务完成的回调 [block2 setCompletionBlock:^{ NSLog(@"block task 4 comlpete"); }]; //设置block1完成后才会继续往下走 [block1 waitUntilFinished]; NSLog(@"block task 3 is waitUntilFinished!"); //初始化一个子任务 NSInvocationOperation *oper1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(function1) object:nil]; [queue addOperation:oper1]; [queue waitUntilAllOperationsAreFinished]; NSLog(@"queue comlpeted"); // 取消全部操作 // [queue cancelAllOperations]; // 暂停操作/恢复操作/是否暂定状态 // [queue setSuspended:YES];[queue setSuspended:NO];[queue isSuspended]; //操作优先级 // [queue waitUntilAllOperationsAreFinished];</span>
2016-02-04 15:11:54.283 ThreadAndAsynchronization[28948:3783683] block task 1 2016-02-04 15:11:56.358 ThreadAndAsynchronization[28948:3783684] block task 2 2016-02-04 15:11:58.430 ThreadAndAsynchronization[28948:3783683] block task 4,任务3依赖4 2016-02-04 15:11:58.430 ThreadAndAsynchronization[28948:3783694] block task 4 comlpete 2016-02-04 15:12:00.504 ThreadAndAsynchronization[28948:3783683] block task 3 2016-02-04 15:12:00.504 ThreadAndAsynchronization[28948:3783527] block task 4 is waitUntilFinished! 2016-02-04 15:12:02.573 ThreadAndAsynchronization[28948:3783694] function1 done 2016-02-04 15:12:02.573 ThreadAndAsynchronization[28948:3783527] queue comlpeted
有2个值得注意的地方,第一个是mainQueue,第二个是maxConcurrentOperationCount。
mainQueue是通过 NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
获取到,它代表主队列,也就是UI队列,所以用到mainQueue队列的时候一般用于更新ui界面,且特别注意在这个队列中执行的方法,要考虑到会不会阻塞进程。
maxConcurrentOperationCount:最多有多少个队列可以同时执行,默认是5,当设置为1是,队列是一个串行队列,设置>1时,队列是一个并行队列。但是在主队列上设置同时执行的任务是没有效果的!如果没有设置最大并发数,那么并发的个数是由系统内存和CPU决定的,可能内存多久开多一点,内存少就开少一点。
[queue cancelAllOperations];
// [queue setSuspended:YES]; // [queue setSuspended:NO]; // [queue isSuspended];
//block1依赖block2 [block1 addDependency:block2];
//设置任务完成的回调 [block2 setCompletionBlock:^{ NSLog(@"block task 4 comlpete"); }];