多线程开发
1.NSThread
2.NSOperation
3.GCD
三种方式是随着iOS的发展逐渐引入的,所以相比而言后者比前者更加简单易用,并且GCD也是目前苹果官方比较推荐的方式(它充分利用了多核处理器的运算性能)。
在iOS中每个进程启动后都会建立一个主线程(UI线程),这个线程是其他线程的父线程。由于在iOS中除了主线程,其他子线程是独立于Cocoa Touch的,所以只有主线程可以更新UI界面.
NSThread
// 方法1. 对象方法 (手动开启)
// (1)创建线程 ,把复杂任务放到子线程中执行
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(blockThread:) object:nil];
// (2) 手动开启
[thread start];
// 子线程
- (IBAction)blockThread:(id)sender {
@autoreleasepool {
// 凡是子线程执行的方法,都添加到 @autoreleasepool{} 自动释放池
}
}
// 方法2. 类方法 (无需手动开启)
// 方法2. 创建线程,把复杂任务放到子线程中执行
[NSThread detachNewThreadSelector:@selector(blockThread:) toTarget:self withObject:nil];
NSThread总结:
1.每个线程的实际执行顺序并不一定按顺序执行(虽然是按顺序启动);
2.每个线程执行时实际网络状况很可能不一致。当然网络问题无法改变,只能尽可能让网速更快,但是可以改变线程的优先级,让15个线程优先执行某个线程。线程优先级范围为0~1,值越大优先级越高,每个线程的优先级默认为0.5。
3.优先级高的执行,只是说,执行的概率变高,并不是最先执行。
4.使用NSThread在进行多线程开发过程中操作比较简单,但是要控制线程执行顺序并不容易,另外在这个过程中如果打印线程会发现循环几次就创建了几个线程,这在实际开发过程中是不得不考虑的问题,因为每个线程的创建也是相当占用系统开销的。
线程状态
线程状态分为isExecuting(正在执行)、isFinished(已经完成)、isCancellled(已经取消)三种。其中取消状态程序可以干预设置,只要调用线程的cancel方法即可。但是需要注意在主线程中仅仅能设置线程状态,并不能真正停止当前线程,如果要终止线程必须在线程中调用exist方法,这是一个静态方法,调用该方法可以退出当前线程。
在线程操作过程中可以让某个线程休眠等待,优先执行其他线程操作,而且在这个过程中还可以修改某个线程的状态或者终止某个指定线程。
-(NSData *)requestData:(int )index{
//对于多线程操作建议把线程操作放到@autoreleasepool中
@autoreleasepool{ //对一加载线程休眠2秒
if (index!=(ROW_COUNT*COLUMN_COUNT-1)) {
[NSThread sleepForTimeInterval:2.0];
}
NSURL *url=[NSURL URLWithString:_imageNames[index]];
NSData *data=[NSData dataWithContentsOfURL:url]; return data;
}
}
NSOperation
使用NSOperation和NSOperationQueue进行多线程开发类似于C#中的线程池,只要将一个NSOperation(实际开中需要使用其子类NSInvocationOperation、NSBlockOperation)放到NSOperationQueue这个队列中线程就会依次启动。NSOperationQueue负责管理、执行所有的NSOperation,在这个过程中可以更加容易的管理线程总数和控制线程之间的依赖关系。
NSOperation有两个常用子类用于创建线程操作:NSInvocationOperation和NSBlockOperation,两种方式本质没有区别,但是是后者使用Block形式进行代码组织,使用相对方便。
注意:操作本身只是封装了要执行的相关方法,并没有开辟线程,没有主线程之分,在哪个线程中都能执行。
// invocate 创建操作
// NSInvocationOperation *invo = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(blockThread:) object:nil];
// 开始任务
// [invo start];
// 1. 创建5个操作
NSInvocationOperation *invo1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(printString:) object:@"11"];
NSInvocationOperation *invo2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(printString:) object:@"22"];
NSInvocationOperation *invo3 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(printString:) object:@"33"];
NSInvocationOperation *invo4 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(printString:) object:@"44"];
NSInvocationOperation *invo5 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(printString:) object:@"55"];
// 2. NSBlockOperation 创建2个block操作
NSBlockOperation *block1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"666666");
}];
NSBlockOperation *block2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"777777");
}];
// 3. 创建 操作队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 4. 设置最大并发数
queue.maxConcurrentOperationCount = 1; // 设置为 1,顺序执行
// 最大并发数控制的是同一时间点,能够执行的任务数。如果为1,则同时执行1个,根据队列FIFO特点,一定是顺序执行。 如果不为1,则可以同时执行多个任务,同时执行任务,称为--并发执行。
// 5. 添加操作--将操作添加到队列
// [queue addOperation:invo];
[queue addOperation:invo1];
[queue addOperation:invo2];
[queue addOperation:invo3];
[queue addOperation:invo4];
[queue addOperation:invo5];
[queue addOperation:block1];
[queue addOperation:block2];
// 添加到队列中的任务会自动执行,队列内部会开辟子线程,任务放在子线程中执行。***********************************************
//方法2:直接使用操队列添加操作,eg:block2
[queue addOperationWithBlock:^{
NSLog(@"777777");
}];
}
- 使用NSBlockOperation方法,所有的操作不必单独定义方法,同时解决了只能传递一个参数的问题。
- 调用主线程队列的 addOperationWithBlock: 方法进行UI更新,不用再定义一个参数实体。
- 使用NSOperation进行多线程开发可以设置最大并发线程,有效的对线程进行了控制。
线程执行顺序
使用NSThread很难控制线程的执行顺序,但是使用NSOperation就容易多了,每个NSOperation可以设置依赖线程。假设操作A依赖于操作B,线程操作队列在启动线程时就会首先执行B操作,然后执行A。
// 加载5张图片,优先加载最后一张图的需求,只要设置前面的线程操作的依赖线程为最后一个操作即可。
-(void)loadImageWithMultiThread{
int count=ROW_COUNT*COLUMN_COUNT; //创建操作队列
NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
operationQueue.maxConcurrentOperationCount=5;
//设置最大并发线程数
NSBlockOperation *lastBlockOperation=[NSBlockOperation blockOperationWithBlock:^{
[self loadImage:[NSNumber numberWithInt:(count-1)]];
}];
//创建多个线程用于填充图片
for (int i=0; i<count-1; ++i) {
//方法1:创建操作块添加到队列
//创建多线程操作
NSBlockOperation *blockOperation=[NSBlockOperation blockOperationWithBlock:^{
[self loadImage:[NSNumber numberWithInt:i]];
}];
//设置依赖操作为最后一张图片加载操作
[blockOperation addDependency:lastBlockOperation];
[operationQueue addOperation:blockOperation];
}
//将最后一个图片的加载操作加入线程队列
[operationQueue addOperation:lastBlockOperation];
}
加载最后一张图片的操作最后被加入到操作队列,但是它却是被第一个执行的。操作依赖关系可以设置多个,例如A依赖于B、B依赖于C…但是千万不要设置为循环依赖关系(例如A依赖于B,B依赖于C,C又依赖于A),否则是不会被执行的。
GCD
- GCD(grand Center DisPath) 宏观中心分配(队列).
- 是苹果开发的一种支持并行操作的机制。它的主要部件是一个FIFO队列和一个线程池,前者用来添加任务,后者用来执行任务。
- GCD中的FIFO队列称为dispatch queue,它可以保证先进来的任务先得到执行(但不保证一定先执行结束)。
- GCD 是一个函数级的多线程,用C语言实现的。GCD 中可以分配多个队列,每个队列都具有一定的功能。比如:串行队列,并发队列,分组队列,只执行一次队列。
dispatch queue分为下面两种:
- Serial Dispatch Queue -- 线程池只提供一个线程用来执行任务,所以后一个任务必须等到前一个任务执行结束才能开始。
- Concurrent Dispatch Queue -- 线程池提供多个线程来执行任务,所以可以按序启动多个任务并发执行。
// 1. 创建一个串行队列
dispatch_queue_t serialQ = dispatch_queue_create("q1", DISPATCH_QUEUE_SERIAL);
// 参数1. 队列名称
// 参数2. 队列类型, 串行 还是 并行
// 2. 给队列添加任务 -(以异步方式添加任务)
dispatch_async(serialQ, ^{
NSLog(@"%@",[NSThread currentThread]);
NSLog(@"建立网络链接");
});
// 1. 创建一个并行队列
dispatch_queue_t concurrentQ = dispatch_queue_create("eg.gcd.ConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
// 创建并行队列的 全局队列
dispatch_queue_t globleQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
// 参数1. 队列的优先级, 有4种,默认default
// 参数2. 预留参数,通常给 0.
// 注意: 不要使用优先级使一个并行队列,变为一个串行队列。优先级高的执行,只是说,执行的概率变高,并不是最先执行。
dispatch_async(concurrentQ, ^{
// Code here
});
// 释放
dispatch_release(concurrentQ);
// 并行队列的特点:虽然也遵守FIFO,但是提交时,队列中的任务并不会等待,如果前面的任务没有执行完,不妨碍后面任务的执行。
// 并行队列中会开辟多个子线程。
而系统默认就有一个串行队列main_queue和并行队列global_queue:
dispatch_queue_t globalQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t mainQ = dispatch_get_main_queue();
通常,我们可以在global_queue中做一些long-running的任务,完成后在main_queue中更新UI,避免UI阻塞,无法响应用户操作.
提交到队列中的不同方式:
- (1) dispatch_once 这个函数,它可以保证整个应用程序生命周期中某段代码只被执行一次.
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// code to be executed once
});
- (2) dispatch_after 延时执行
// 延迟3s 后,改变背景颜色
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.view.backgroundColor = [UIColor redColor];
});
- (3) dispatch_apply 执行某个代码片段若干次。
dispatch_apply(10, globalQ, ^(size_t index) {
// 参数1. 重复提交的次数
// 参数2. 提交的队列
// 参数3. 当前提交的次数
});
- (4) Dispatch Group 机制监听一组任务是否完成
// 1.创建队列
dispatch_queue_t concurrentQ = dispatch_queue_create("q1", DISPATCH_QUEUE_CONCURRENT);
// 2. 创建一个组队列
// 组队列用途-- 把一系列的任务放到同一 组中,再提交到队列。
dispatch_group_t group = dispatch_group_create();
// 3.添加任务 到组中
dispatch_group_async(group, concurrentQ, ^{
NSLog(@"上传照片");
});
dispatch_group_async(group, concurrentQ, ^{
NSLog(@"获取授权");
});
dispatch_group_async(group, concurrentQ, ^{
NSLog(@"保存信息");
});
// 需求:上面的三个操作可以并发执行,最后提交信息,必须最后执行。
// 组通知提交方式, 通知提交方式的任务,等到组中,若有任务执行完毕后,才能执行。
dispatch_group_notify(group, concurrentQ, ^{
NSLog(@"最后提交信息");
});
- (5) dispatch_ barrier_ async 障碍提交
// 障碍提交方式一般用在,并行队列中,当障碍提交方式的任务执行时,后面的任务等待。
// 障碍提交方式:只是当前执行到此任务时,后面的任务等待。被障碍分割的上部分 和 下部分 执行顺序 不确定。 可能是上部分先执行,也可能是下部分先执行。
dispatch_async(concurrentQ, blk0);
dispatch_async(concurrentQ, blk1);
// 添加障碍,执行写入操作,写入没有执行完之前,不允许读取数据。
dispatch_barrier_async(concurrentQ, blk_barrier);
dispatch_async(concurrentQ, blk2);
- (6) dispatch_ async_ f 提交函数
// 声明一个函数
void string(void *s)
{
printf("%s\n",s);
}
- (IBAction)asyncf:(id)sender {
dispatch_async_f(dispatch_get_main_queue(), "aaaa", string);
// 参数1. 队列
// 参数2. 传递给函数的参数
// 参数3. 函数名
}
- (7) dispatch_sync 同步提交
// 同步提交方式 --提交的block,如果没有执行完成,那么后面的所有代码都不会执行。
// 也就是说,提交操作,在哪个线程中,就会阻塞哪个线程
// 注意:不管同步提交方式是提交到哪个线程,一定会阻塞当前线程,执行也一定是在当前线程中。
dispatch_queue_t conQ = dispatch_queue_create("q1", DISPATCH_QUEUE_CONCURRENT);
// 2. 同步提交 --
dispatch_sync(conQ, ^{
for (int i = 0; i < 65000; i++) {
NSLog(@"%@",[NSThread currentThread]);
NSLog(@"%d",i);
}
});
线程互斥:
- 多个线程同时访问同一个资源,产生的资源争夺问题.
static int ticket = 10;
- (IBAction)threadConflict:(id)sender {
dispatch_queue_t concurrentQ = dispatch_queue_create("q1", DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(10, concurrentQ, ^(size_t i) {
[self sellTicket];
});
}
-(void)sellTicket
{
// 添加同步锁,如果有一个线程正在访问,那么其他线程等待
NSLock *lock = [[NSLock alloc] init];
// 加锁
[lock lock];
NSLog(@"剩余票数%d",--ticket);
[lock unlock]; // 解锁
}