iOS 多线程与GCD

来源:互联网 发布:网络歌曲2016 编辑:程序博客网 时间:2024/04/29 12:53

进程:系统中正在运行的一个程序,进程之间是相互独立的,每个进程都有属于自己的内存空间。比如手机中的 微信 应用和 印象笔记 应用,他们都是iOS系统中独立的进程,有着自己的内存空间。

线程:进程内部的任务执行路径,可简单理解为 一个程序它是按顺序从上往下执行的, 这个执行顺序我们可以把它看成是一条线,把这条线就叫做线程。进程若想执行任务,则必须得在线程下执行。也就是说进程至少有一个线程才能执行任务。

多线程的实现原理:虽然在同一时刻,CPU只能处理1条线程,但是CPU可以快速地在多条线程之间调度(切换),达到多线程并发的近似效果。

 

多线程的优点:

  1. 能适当提高程序的执行效率
  2. 能适当提高资源利用率(CPU、内存利用率)。

多线程的缺点:

  1. 创建线程是需要成本的:在栈空间的子线程512KB、主线程1MB,创建线程大约需要90毫秒的创建时间。
  2. 线程越多,CPU在调度线程上的开销就越大。
  1. 线程越多,程序设计就越复杂:因为要考虑到线程之间的通信,多线程的数据共享。

 

iOS主线程:刷新UI,响应UI事件

iOS子线程:异步执行,不影响主线程,处理耗时任务,如网络请求,加载图片,复杂运算

多线程安全:当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题。就好比好几个人在同时修改同一个表格,造成数据的错乱。

 

多线程资源抢夺的解决方案

要给数据添加互斥锁 。也就是说,当某线程访问一个数据之前就要给数据加锁,让其不被其他的线程所修改。就好比一个人修改表格的时候给表格设置了密码,那么其他人就无法访问文件了。当他修改文件之后,再讲密码撤销,第二个人就可以访问该文件了。

这里的线程都为子线程,如果给数据加了锁,就等于将这些异步的子线程变成同步的了,这也叫做线程同步技术。

  • atomic:原子属性,为setter方法加锁(默认就是atomic)
  • nonatomic:非原子属性,不会为setter方法加锁

多线程在iOS中的应用:GCD

GCD的优势:

  1. 自动利用更多的CPU内核(比如双核、四核)
  2. 自动管理线程的生命周期(创建线程、调度任务、销毁线程)
  3. 只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码。

 GCD的使用步骤

  1. 开发者定制将要执行的任务
  2. 将任务添加到队列中,GCD会自动将队列中的任务取出,放到对应的线程中执行。

GCD的队列可以分为2大类型:

  1. 串行队列:队列中的任务按顺序执行(不会同时执行)
  2. 并行队列:队列中的任务会并发执行,任务执行完毕,不一定出队列。只有前面的任务执行完了,才会出队列。

GCD的任务2大类型:

  1. 同步任务:优先级高,在线程中有执行顺序,不会开启新的线程。
  2. 异步任务:优先级低,在线程中执行没有顺序,看cpu闲不闲。在主队列中不会开启新的线程,其他队列会开启新的线程

队列:可理解为是管理任务的,它里面放着很多的任务,来管理这些任务的开启顺序,串行队列开启异步任务,是有顺序

任务同步异步:决定的是任务的执行顺序,并行队列里开启同步任务是有执行顺序的,只有异步才没有顺序

串行队列创建:

  1. 使用 dispatch_queue_create 函数创建串行队列:创建串行队列(队列类型传递NULL或者DISPATCH_QUEUE_SERIAL)
  2. dispatch_queue_t queue = dispatch_queue_create("serial_queue", NULL);
  3. 使用主队列(跟主线程相关联的队列)主队列是GCD自带的一种特殊的串行队列:放在主队列中的任务,都会放到主线程中执行。可以使用dispatch_get_main_queue()获得系统提供的主队列:

dispatch_queue_tqueue = dispatch_get_main_queue();

并发队列的创建:

  1. 使用 dispatch_queue_create 函数创建并发队列。

dispatch_queue_tqueue = dispatch_queue_create("concurrent.queue",DISPATCH_QUEUE_CONCURRENT);

  1. 使用 dispatch_get_global_queue 获得全局并发队列。GCD默认已经提供了全局的并发队列,供整个应用使用,可以无需手动创建。

dispatch_queue_tqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

多线程的应用

  1. 子线程与主线程的通信:有时需要在子线程处理一个耗时比较长的任务,并且此任务完成后,要在主线程执行另一个任务。

例如,从网络加载图片(在子线程),加载完成就更新UIView(在主线程)。

首先拿到全局并发队列(或自己开启一个子线程)来执行耗时的操作,然后在其完成block中拿到全局串行队列来执行UI刷新的任务。

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{

//加载图片

NSData*dataFromURL = [NSData dataWithContentsOfURL:imageURL];

UIImage*imageFromData = [UIImageimageWithData:dataFromURL];
dispatch_async(dispatch_get_main_queue(), ^{

//加载完成更新view

UIImageView*imageView = [[UIImageView alloc] initWithImage:imageFromData];         
      });     
  });

  1.  dispatch_once:用于在程序启动到终止,只执行一次的代码。此代码被执行后,相当于自身全部被加上了注释,不会再执行了。

为了实现这个需求,我们需要使用 dispatch_once 让代码在运行一次后即刻被“雪藏”。

//使用dispatch_once函数能保证某段代码在程序运行过程中只被执行1次

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    //只执行1次的代码,这里默认是线程安全的:不会有其他线程可以访问到这里
});

  1. dispatch_group:执行多个耗时的异步任务,但是只能等到这些任务都执行完毕后,才能在主线程执行某个任务。为了实现这个需求,我们需要让将这些异步执行的操作放在 dispatch_group_async 函数中执行,最后再调用 dispatch_group_notify 来执行最后执行的任务。

dispatch_group_tgroup = dispatch_group_create();
  dispatch_group_async(group,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
      // 执行1个耗时的异步操作
  });

dispatch_group_async(group,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
      // 执行1个耗时的异步操作
  });

dispatch_group_notify(group,dispatch_get_main_queue(), ^{
      //等前面的异步操作都执行完毕后,回到主线程...
  });

  1. dispatch_barrier:有时要执行几个不同的异步任务,但是我们还是要将其分成两组:当第一组异步任务都执行完成后才执行第二组的异步任务。

为了实现这个需求,我们需要使用dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);在两组任务之间形成“栅栏”,使其“下方”的异步任务在其“上方”的异步任务都完成之前是无法执行的。

dispatch_queue_tqueue = dispatch_queue_create("12312312", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue,^{
        NSLog(@"----任务1-----");
    });

dispatch_async(queue,^{
        NSLog(@"----任务2-----");
    });   

dispatch_barrier_async(queue, ^{
       NSLog(@"----任务栅栏-----");
    });

dispatch_async(queue,^{
        NSLog(@"----任务3-----");
    });

dispatch_async(queue,^{
        NSLog(@"----任务4-----");
    });

0 0