内功心法-多线程的基本使用

一 ) 为什么使用多线程?

每个iOS应用程序都有个专门用来更新显示UI界面、处理用户的触摸事件的主线程,因此不能将其他太耗时的操作放在主线程中执行,不然会造成主线程堵塞(出现卡机现象),带来极坏的用户体验。一般的解决方案就是将那些耗时的操作放到另外一个线程中去执行,多线程编程是防止主线程堵塞,增加运行效率的最佳方法。

这里有两个概念是进程和线程。 进程就是负责程序运行的内存分配,而线程就是进程中得一个独立的执行那个路径。它是程序的执行路径,负责程序中代码的实际运行。这好比进程就是火车,线程就是火车的车厢,没有火车,车厢也跑不起来当然一个火车也不能只有一个车厢。

二)  ios 中常用的三种多线程是什么?

1  Thread: 这是相对轻量级别的,抽象级别相对来说比较低,但是需要管理线程的生命周期,同步以及加锁,这会导致一定的性能开销

2 Operations:  这个是基于OC实现的,以面向对象的方式封装了需要执行的操作,我们可以不必关心线程的管理和同步的问题。它可以开始,取消线程执行。他有两个默认的实现方法:NSInvocationOperation和NSBlockOperation。当然我们也可以自定NSOperations,只有实现里面的main方法即可

3  GCD:也是重点讲的一个,它是ios4才开始使用的,当然现在我们ios9都出来了,GCD的成熟度足可以让我们放心的使用,它是基于C实现的,性能上要相对来说好一些,而且可以用最简单的代码去实现复杂的线程问题。

三) NSThread的基本使用方法

1.动态初始化

NSThread threadOne = [[NSThread alloc]initWithTarget:self selector:@selector(run) object:nil];

//其中run是一个方法,我们通过方法来执行多线程中得处理

[_threadOne setName:@"one"]; //给线程初始化一个别名

[_threadOne start];//开始执行

2 静态初始化

[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil]; //通过这一句代码我们就可以创建出另一个线程出来并去执行

3 创建隐式线程:

[self performSelectorInBackground:@selector(run) withObject:nil];

注:这是NSObject的一个方法,他可以让一个耗时间的处理放入后台去执行,但是在swift中抛弃了,原因是苹果觉得这个方法是线程不安全的

4 获取当前的线程

NSThread *current = [NSThread currentThread];//返回的是目前的线程

5 返回主线程(刷新UI控件必须在主线程中执行)

[self performSelectorOnMainThread:@selector(main:) withObject:nil waitUntilDone:YES];

6 等待(暂停)当前的线程

[NSThread sleepForTimeInterval:3.0f] ;//3秒之后执行

或者

NSDate *date = [NSDate dateWithTimeInterval:2 sinceDate:[NSDate date]];

[NSThread sleepUntilDate:date]; 等待date完成后再去执行

注:前者是等待时间的完成,后者是等待数据的完成

四)  NSOperation使用方法:

NSOperation 实例封装了需要执行的操作和执行操作所需的数据,并且能够以并发或非并发的方式执行这个操作。NSOperation在ios4后也基于GCD实现,但是相对于GCD来说可控性更强,并且可以加入操作依赖。NSOperation提供了ready cancelled executing finished这几个状态变化,我们的开发也是必须处理自己关心的其中的状态。这些状态都是基于keypath的KVO通知决定,所以在你手动改变自己关心的状态时,请别忘了手动发送通知。这里面每个属性都是相互独立的,同时只可能有一个状态是YES。finished这个状态在操作完成后请及时设置为YES,因为NSOperationQueue所管理的队列中,只有isFinished为YES时才将其移除队列,这点在内存管理和避免死锁很关键。

1.NSInvocationOperation:

NSInvocationOperation *op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download:) object:nil];//红色多线程中处理的方法

[op start]; //开始

[op cancel];//取消

2  NSBlockOperation

NSBlockOperation *block = [NSBlockOperation    blockOperationWithBlock:^{

NSLog(@"%@",[NSThread currentThread]); //处理线程的block方法

}];

3 NSOperationQueue

一个NSOperation对象可以通过调用start方法来执行任务,默认是同步执行的。也可以将NSOperation添加到一个NSOperationQueue(操作队列)中去执行,而且是异步执行的。一旦NSoperation添加到NSoperationQueue中,用户就无权对NSoperation管理,都有NSoperationQueue来执行。

3.1 添加一个队列

NSoperationQueue *myQueue = [[NSOperationQueue alloc]init];

NSInvocationOperation *op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download:) object:nil];

[myQueue addOperation:op];

3.2 主队列(任何刷新UI的方法都必须在主队列中执行)

[[NSOperationQueue mainQueue]addOperationWithBlock:^{

//可以执行刷新UI控件的方法

}];

3.3 队列直接可以设置依赖,比如队列1需要队列2执行完毕后才能执行(addDependency)

NSBlockOperation *block1 = [NSBlockOperation blockOperationWithBlock:^{

NSLog(@"%@",[NSThread currentThread]);

}];

NSBlockOperation *block2 = [NSBlockOperation blockOperationWithBlock:^{

NSLog(@"%@",[NSThread currentThread]);

}];

[block1 addDependency:block2];

[myQueue addOperation:block1];

[myQueue addOperation:block2];

3.4可以设置最大并发的操作数量

[myQueue setMaxConcurrentOperationCount:2];

3.5 一旦添加到队列,队列就拥有了这个Operation对象并且不能被  删除,唯一能做的事情是取消。

[myQueue cancelAllOperations];

3.6获取NSOperation

myQueue.operations 这是一个数组,里面存放这添加进入这个队列的所有任务

3.7 如果你想临时暂停Operations的执行,可以使用queue的setSuspended:方法暂停queue。

[myQueue setSuspended:YES];

四)GCD

1 GCD是Grand Central Dispatch的简称,它是基于C语言的。如果使用GCD,完全由系统管理线程,我们不需要编写线程代码。只需定义想要执行的任务,然后添加到适当的调度队列(dispatch queue)。GCD会负责创建线程和调度你的任务,系统直接提供线程管理。

2 GCD的操作思想是讲操作放在队列中去执行

1  操作是用block来实现的

2 队列是先进先出的,它是负责调度任务执行所在的线程

3 GCD分为串行和并行,有自定义,主队列和全局队列三种。一个同步函数只在完成了它预定的任务后才返回。一个异步函数,刚好相反,会立即返回,预定的任务会完成但不会等它完成。因此,一个异步函数不会阻塞当前线程去执行下一个函数。如果在主队列中执行同步的话,会造成死锁的发生

3 串行和并行

串行:一次只能执行一个任务, 当前任务完成才开始出列并启动下一个任务

并行:则尽可能多地启动任务并发执行

4GCD基本使用方法

1 自定义队列

dispatch_queue_t q = dispatch_queue_create("gcdDemo1", DISPATCH_QUEUE_SERIAL); DISPATCH_QUEUE_SERIAL:串行,可以传入nil 默认是串行,"gcdDemo1”是这个线程的别名,可以传nil

dispatch_queue_t q = dispatch_queue_create("gcdDemo2", DISPATCH_QUEUE_CONCURRENT);  DISPATCH_QUEUE_CONCURRENT:并行

dispatch_async(q, ^{    注:异步执行,可以开辟多个线程去执行,无需等待

NSLog(@"%@",[NSThread currentThread]);

});

dispatch_sync(q, ^{  注:同步执行,只开辟一个线程,需要等待上一个任务的完成才能执行

NSLog(@"%@",[NSThread currentThread]);

});

2 全局队列

dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

DISPATCH_QUEUE_PRIORITY_DEFAULT:默认的,全局队列是有优先级的

DISPATCH_QUEUE_PRIORITY_HIGH      最高(优先执行)

DISPATCH_QUEUE_PRIORITY_DEFAULT    默认

DISPATCH_QUEUE_PRIORITY_LOW        最低

DISPATCH_QUEUE_PRIORITY_BACKGROUND 后台

3 主队列

dispatch_queue_t q = dispatch_get_main_queue();

每一个应用程序都有一个主线程  在ios中所有的ui刷新都再主线程中执行!这是因为苹果为了提高性能,大部分库都是线程不安全的,如果在子线程刷新控件会造成一些问题,所有所有的UI控件的刷新都由主线程上刷新

4 延迟执行

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);

dispatch_after(time, dispatch_get_main_queue(), ^{

NSLog(@"我是3秒才执行的!");

});

5 dispatchGroup组队列

dispatchGroup作用:当 dispatch_group_async函数将多个任务关联到一个Dispatch Group和相应的queue中,group会并发地同时执行这些任务。而且Dispatch Group可以用来阻塞一个线程, 直到group关联的所有的任务完成执行。有时候你必须等待任务完成的结果,然后才能继续后面的处理。

dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group, q, ^{

NSLog(@"1 == %@",[NSThread currentThread]);

});

dispatch_group_async(group, q, ^{

NSLog(@"2 == %@",[NSThread currentThread]);

});

dispatch_group_async(group, q, ^{

NSLog(@"3 == %@",[NSThread currentThread]);

});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{//不管前面的线程谁先执行,最后都会执行notify方法。

NSLog(@"4 == %@",[NSThread currentThread]);

});

6 dispathc_apply

dispathc_apply是dispatch_sync 和dispatch_group的关联API.它以指定的次数将指定的Block加入到指定的队列中。并等待队列中操作全部完成.

dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_apply(10, globalQueue, ^(size_t index) {

NSLog(@"%zu",index);

dispatch_source_merge_data(socurce, 1);

});

7 dispatch_source_t 信号源

dispatch source是一个监视某些类型事件的对象。当这些事件发生时,它自动将一个block放入一个dispatch queue的执行例程中。书中定义。我的理解就是多线程中得KVO,它检测用户事件,它是由dispatch_source_merge_data函数来向自己发送信号,然后通过dispatch_source_set_event_handler这个函数去执行。

这是我写了一个进度条的例子

执行部分:  __weak __typeof(self)weakSelf = self;

dispatch_source_t  socurce = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());

dispatch_source_set_event_handler(socurce, ^{

[weakSelf.progressIndicator setProgress:dispatch_source_get_data(socurce)  animated:YES];

});

dispatch_resume(socurce);

监听部分:    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_apply(10, globalQueue, ^(size_t index) {

NSLog(@"%zu",index);

dispatch_source_merge_data(socurce, 1);

});

/*

dispatch_source_create(dispatch_source_type_t type,

uintptr_t handle,

unsigned long mask,

dispatch_queue_t queue);

第1个参数:要监听的事件类型

第2个参数:可以理解为句柄、索引或id,假如要监听进程,需要传入进程的ID

第3个参数:根据参数2,可以理解为描述,提供更详细的描述,让它知道具体要监听什么

第4个参数:当事件发生时,将block添加至哪个队列来执行

**/

8 dispatch_semaphore信号量

当我们在处理一系列线程的时候,当数量达到一定量,在以前我们可能会选择使用NSOperationQueue来处理并发控制,在GCD中我们需要通过dispatch_semaphore来控制它的并发数量。

dispatch_group_t group = dispatch_group_create();

dispatch_semaphore_t semaphore = dispatch_semaphore_create(5);

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

for (int i = 0; i < 10; i++)

{

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);//如果技术器的数值大于等于1的时候进行-1操作

dispatch_group_async(group, queue, ^{

NSLog(@"----%d",i);

sleep(2);

dispatch_semaphore_signal(semaphore);//计数器+1

});

}

简单的介绍一下这一段代码,创建了一个初使值为5的semaphore,每一次for循环都会创建一个新的线程,线程结束的时候会发送一个信号,线程创建之前会信号等待,所以当同时创建了5个线程之后,for循环就会阻塞,等待有线程结束之后会增加一个信号才继续执行,如此就形成了对并发的控制,如上就是一个并发数为5的一个线程队列。

你可能感兴趣的:(内功心法-多线程的基本使用)