IOS多线程--GCD

ios多线程--GCD(Grand Central Dispatch)

1.GCD的好处

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

2.同步执行和异步执行

同步执行(sync---synchronize)

  1. 同步添加任务到指定的队列中在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后在继续执行
  2. 只能在当前线程中执行任务,不具备开启新线程的能力

异步执行(async---asynchronous)

  1. 异步添加任务到指定的队列当中去,它不会做任何的等待,可以直接执行任务
  2. 可以在新的线程中执行任务,具备开启新线程的能力

举例子

同步执行:在一个安检口,人们排队过安检,前一个人安检完了,下一个人才安检

异步执行:在机场里有许多的安检口,人们可以同时在不同的安检口过安检

注意:异步执行(async)具有开启新线程的能力,但是它不一定开启新线程

3.串行队列和并发队列

串行队列:只开启一个线程,一个任务一个任务的执行

并发队列:可以开启多个线程,并且同时执行任务

注意:并发队列的并发功能只有在异步(dispatch_async)方法下才生效

4.如何使用GCD

1.创建一个队列

dispatch_queue_creat

方法来创建队列,传入两个参数

  1. 参数一表示队列的唯一标识符,可为nil
  2. 参数二是用来识别是串行队列还是并发队列
DISPATCH_QUEUE_SERIAL  //(串行队列)
DISPATCH_QUEUE_CONCURRENT //(并发队列)

主队列

对于串行队列,GCD默认提供了主队列(Main Dispatch Queue)

  1. 所有放在主队列的任务,都会被放在主线程中执行
  2. 可以使用dispathc_get_main_queue()方法获得主队列
  3. 注意:主队列其实不特殊,主队列的本质就是一个普通的串行队列,只是因为默认情况下,当前代码是放在主队列当中的,然后主队列中的代码,有的会放到主线程中去执行,所以才造成了主队列特殊的情况
dispatch_queue t queue = dispatch_get_main_queue();

全局并发队列

对于并发队列,GCD默认提供了全局并发队列(Global Dispatch Queue)

可以使用dispatch_get_global_queue方法来获取全局并发队列。需要传入两个参数,第一个参数为优先级,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT来表示,第二个参数暂时没用,用0即可

diapatch_queue_t queue = dispatch_get_gloabl_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);

2.创建任务

GCD提供了同步执行任务的创建方法dispatch_sync异步执行任务的创建方法dispatch_async

//同步执行任务的创建方法
dispatch_sync(queue,^{
  //这里放同步执行代码
});

//异步执行任务的创建方法
dispatch_async(queue,^{
  //这里放异步执行代码
});

5.主线程

1.主线程

区别 并发队列 串行队列 主队列
同步(sync) 没有开启新线程,串行执行任务 没有开启新线程,串行执行任务 死锁
异步(async) 有开启新线程,并发执行任务 有开启新线程(1条),串行执行任务 没有开启新线程,串行执行任务

主线程中调用主队列 + 同步执行会导致死锁问题,因为主队列中追加的同步任务和主线程本身的任务两者间互相等待,阻塞了主队列,最终导致死锁。

注意:在子线程调用主队列 + 同步执行,不会阻塞主队列,也不会造成死锁问题。最终结果是:不会开启新线程,串行执行任务。

6.六种组合方式

  1. 同步执行 + 并发队列
  2. 异步执行 + 并发队列
  3. 同步执行 + 串行队列
  4. 异步执行 + 串行队列
  5. 同步执行 + 主队列
  6. 异步执行 + 主队列

1.同步执行 + 并发队列

在当前任务中执行任务,不会开启新线程,执行完一个任务,再执行下一个任务。

  1. 所有任务都是在当前线程(主线程)中执行的,没有开启新的线程(同步执行不具备开启新线程的能力)
  2. 任务按顺序执行

注意:虽然是并发队列可以开启多个线程,并同时执行多个任务。但是因为是同步执行,本身不能创建新线程,只有当前一个线程,所以也就不能存在并发。而当前线程只有等待当前队列中正在执行的任务执行完毕后,才能继续执行下面的操作。所以任务只能一个个被执行,不能同时执行。通俗的讲,并行队列能开多线程,同步执行限制了它,它没开成。

2.异步执行 + 并发队列

开启多个线程,任务交替(同时)执行

  1. 除了当前的线程(主线程),系统还会开启别的子线程,并且任务是交替进行的

异步执行具备开启新线程的能力,且并发队列可开启多个线程,同时执行多个任务

3.同步执行 + 串行队列

不会开启新的线程,在当前线程执行任务。任务是串行的,执行完一个任务再执行下一个任务

  1. 所有任务都是在当前线程(主线程)中执行的,并没有开启新的线程(同步执行不具备开启新线程的能力)
  2. 任务按顺序执行(串行队列每次只有一个任务被执行)

4.异步执行 + 串行队列

会开启新的线程,但是因为是串行队列,所以执行完一个任务,再执行下一个任务

  1. 开启一条新的线程(异步执行具备开启新线程的能力,串行队列只开启一个线程)
  2. 异步执行不做任何等待,直接执行任务
  3. 任务是按照顺序执行,串行队列每次只有一个任务被执行,任务一个接一个按顺序执行

5.同步执行 + 主队列

GCD默认提供的主队列就是串行队列,它是绑定在主线程的队列

  1. 默认情况下,平常所写的代码是直接放进主队列中的
  2. 所有放在主队列中的任务,都会放到主线程中执行
  3. 可使用dispatch_get_main_queue()获得主队列

同步执行 + 主队列在不同线程中调用结果也是不一样的,在主线程中调用会发生死锁问题,而在其他线程中调用则不会

在主线程中调用 同步执行 + 主队列

/**
 * 同步执行 + 主队列
 * 特点(主线程调用):互等卡主不执行。
 * 特点(其他线程调用):不会开启新线程,执行完一个任务,再执行下一个任务。
 */
- (void)syncMain {
    
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"syncMain---begin");
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    dispatch_sync(queue, ^{
        // 追加任务 1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    dispatch_sync(queue, ^{
        // 追加任务 2
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    dispatch_sync(queue, ^{
        // 追加任务 3
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    NSLog(@"syncMain---end");
}

输出:syncMain---begin

造成死锁的原因:我们在主线程中执行syncMain方法,相当于把syncMain任务放到了主线程的队列中,而同步执行会等待当前队列中任务执行完毕,才会继续执行。所以当我们把任务1追加进主队列中,任务1就在等待主线程执行完syncMain任务,而syncMain任务需要等待任务1执行完毕,才能接着执行。所以syncMain和任务1都在等对方执行完毕,所以就死锁了

解释什么是死锁

面试时,老板对面试者说,你今天把死锁说明白了,我就录取你,面试者说,你让我进入公司了,我就把死锁和你说明白,就产生了死锁

在其他线程中使用同步执行 + 主队列 不会造成死锁

  1. 所有任务都是在主线程中执行的,但主线程并不是当前使用的线程,并且没有开启新的线程(所有放在主队列中的任务,都会放在主线程中执行)
  2. 任务是按顺序执行的(主队列是串行队列)

6.异步执行 + 主队列

只在主线程中执行任务,执行完一个任务,再执行下一个任务

  1. 所有任务都是在当前线程(主线程)中执行的,并没有开启新的线程(虽然异步执行具备开启线程的能力,但是主队列,所有任务都在主队列中)
  2. 任务 直接执行
  3. 任务是按顺序执行的(因为主队列是串行队列,每次只有一个任务被执行,任务一个接一个按顺序执行

7.GCD线程间的通信

我们在主线程中进行UI刷新,把一些耗时的操作放在其他线程,例如图片的下载,文件的上传等等。当我们在其他线程完成耗时操作,需要回到主线程程时,就完成了线程间的通信。

/**
 * 线程间通信
 */
- (void)communication {
    // 获取全局并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    // 获取主队列
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    
    dispatch_async(queue, ^{
        // 异步追加任务 1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        
        // 回到主线程
        dispatch_async(mainQueue, ^{
            // 追加在主线程中执行的任务
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
        });
    });
}

8.GCD的其他方法

1.GCD的栅栏方法:dispatch_barrier_async

有时需要异步执行两组操作,而且是第一组执行完再执行第二组,就可以使用栅栏方法,各个组里的执行顺序不一定

2.GCD的延时执行方法:dispatch_after

当有的任务需要在几秒后再执行时,就可以使用dispatch_after方法来实现

注意:dispatch_after并不是在指定时间之后再开始执行处理,而是在指定时间后,将任务追加到主队列中

9.一些例子

1)死锁的情况

- (void) testDemo {
  dispatch_queue_t queue = dispatch_queue_creat("mgd",DISPATCH_QUEUE_SERIAL);
   NSLog(@"1");
  dispatch_async(queue, ^{
    NSLog(@"2");
    dispatch_sync(queue, ^{
      NSLog(@"3");
    });
    NSLog(@"4");
  });
  NSLog(@"5");
}

输出顺序:1 5 2 死锁

解释:串行队列,打印1,异步函数,执行5,在队列中加入任务2,再加入一个任务块,再加入任务4,但是加入的任务块是同步的,会产生堵塞,堵塞任务4,执行任务块时,会往队列里再加入一个任务3,所以我们在执行任务块时,会先执行任务3,但是在队列中,任务3比任务4后面进入,根据队列的先进先出原则,会导致,任务3要等任务4执行完再执行,而任务4要等任务块执行完再执行,而任务块的任务是执行任务3,产生死锁

2)并发队列+异步执行

- (void)testDemo {
  dispatch_queue_t queue = dispatch_queue_creat("mgd",DISPATCH_QUEUE_CONCURRENT);
  NSLog(@"1");
  dispatch_async(queue, ^{
    NSLog(@"2");
    dispatch_async(queue, ^{
      NSLog(@"3");
    });
    NSLog(@"4");
  });
  NSLog(@"5");
}

输出顺序:1 5 2 4 3

解释:并发队列,程序由上到下执行时,先打印1,经过异步函数,它会延迟执行,但不会堵塞,所以程序不会等待这个异步函数执行,所以打印5,然后再执行大的异步函数代码块,打印2,和上面一样,不会等待,所以打印4,最后打印3。

- (void) testDemo {
  dispatch_queue_t queue = dispatch_queue_creat("mgd",DISPATCH_QUEUE_CONCURRENT);
  
  dispatch_async(queue, ^{
    NSLog(@"1");
  });
  
  dispatch_async(queue, ^{
    NSLog(@"2");
  });
  
  dispatch_sync(queue, ^{
    NSLog(@"3");
  });
  //*************
  NSLog(@"0");
  
  dispatch_async(queue, ^{
    NSLog(@"7");
  });
  
  dispatch_async(queue, ^{
    NSLog(@"8");
  });
  
  dispatch_async(queue, ^{
    NSLog(@"9");
  });
  
}

输出顺序:1 2 3(无序实现,可能是2 3 1)0 (0的位置是确定的) 7 8 9(7 8 9也是无序的,可能是8 9 7)

解释:并发队列,异步执行,任务1,任务2没有顺序,任务3也没有顺序,同步执行堵塞的是同步执行以下的代码,所以在任务3完成之前,分割线以下的任务无法执行,前三个任务执行完之后,执行任务0,接着,同上,任务7,8,9顺序也不一定。

posted @ 2020-04-16 14:06  阿-栋  阅读(177)  评论(0编辑  收藏  举报