美文网首页
内功心法-多线程的基本使用

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

作者: 土豆萝卜 | 来源:发表于2016-03-21 14:38 被阅读0次

    一 ) 为什么使用多线程?

    每个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的一个线程队列。

    相关文章

      网友评论

          本文标题:内功心法-多线程的基本使用

          本文链接:https://www.haomeiwen.com/subject/sppklttx.html