iOS 底层探究:多线程之GCD的函数与队列

942 阅读6分钟

这是我参与8月更文挑战的第17天,活动详情查看:8月更文挑战

1. GCD简介

  • GCD全称是Grand Central Dispatch
  • 纯C语言,提供了非常多强大的函数
  • GCD作用,将任务添加到队列,并指定任务执行的函数

1.1 GCD的优势

  • GCD是苹果公司为多核的并行运算提出的解决方案
  • GCD会自动利用更多的CPU内核(比如双核,四核)
  • GCD会自动管理线程的生命周期(创建线程、调度任务、销毁任务)
  • 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码

1.2 GCD基础

GCD的基本用法

dispatch_async(dispatch_get_global_queue(0, 0), ^{ 
    NSLog(@"%@",[NSThread currentThread]); 
});

还原最原始的写法,可拆分成任务,队列和函数

- (void)syncTest{ 
// 任务 
dispatch_block_t block = ^{ 
    NSLog(@"hello GCD"); 
}; 
// 串行队列 
dispatch_queue_t queue = dispatch_queue_create("com.lg.cn", NULL); 
// 函数 
dispatch_async(queue, block); }
  • 使用dispatch_block_t创建任务
    • 任务使用block封装
    • 任务的block没有参数,也没有返回值
  • 使用dispatch_queue_t创建队列
  • 将任务添加到队列,并指定执行任务的函数dispatch_async

2. 函数

在GCD中执行任务的方式有两种,同步执行和异步执行

  • dispatch_sync:同步函数
  • dispatch_async:异步函数

2.1 dispatch_sync

同步函数dispatch_sync的特性

  • 必须等待当前语句执行完毕,才能执行下一条语句
  • 不会开启线程,即不具备开启新线程的能力
  • 在当前线程中执行block任务

2.2 dispatch_async

异步函数dispatch_async的特性

  • 不同等待当前语句执行完毕,就可以执行下一条语句
  • 会开启线程执行block任务,即具备开启新线程的能力(但并不一定开启新线程,这个与任务所指定的队列类型有关)
  • 异步是多线程的代名词

2.3 二者的区别

  • 是否等待队列的任务执行完毕
  • 是否具备开启新线程的能力

3. 队列

队列分为串行队列和并发队列,用来存放任务。队列是一种数据结构,属于特殊的线性表,遵循先进先出(FIFC)原则。新任务被插入到队尾,而任务的读取从队首开时。每读取一个任务,则队伍中释放一个任务。 在GCD中,还提供了两个特殊的队列,分别是主队列全局并发队列。主队列属于串行队列,而全局并发队列属于并发队列。 队列和线程并没有关系,队列负责任务的调度,任务的执行依赖于线程,优先调度的任务不一定优先执行。

3.1 串行队列

image.png 串行队列:Serial Dispatch Queue

dispatch_queue_t serialQueue1 = dispatch_queue_create("com.lg.serial", NULL); 
dispatch_queue_t serialQueue2 = dispatch_queue_create("com.lg.serial", DISPATCH_QUEUE_SERIAL);
  • 每次只有一个任务被执行,等待上一个任务执行完毕再执行下一个
  • 只开启一个线程,同一时刻只调度一个任务执行
  • 使用DISPATCH_QUEUE_SERIAL创建串行队列
  • DISPATCH_QUEUE_SERIAL也可传入NULL,默认创建为串行队列

3.2 并发队列

image.png 并发队列:Concurrent Dispatch Queue

dispatch_queue_t concurrentQueue = dispatch_queue_create("com.lg.concurrent", DISPATCH_QUEUE_CONCURRENT);
  • 一次可以并发执行多个任务
  • 开启多线程,同一时刻可以调度多个任务执行
  • 使用DISPATCH_QUEUE_CONCURRENT创建并发队列
  • 并发队列的并发功能只有在异步函数下才有效

3.3 主队列

主队列:Main Dispatch Queue

dispatch_queue_t mainQueue = dispatch_get_main_queue();
  • 主队列:GCD提供的特殊的串行队列
  • 专门用来在主线程上调度任务的串行队列,依赖于主线程、主Runloop,在main函数调用之前自动创建
  • 不会开启线程
  • 如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度
  • 使用dispatch_get_main_queue获得主队列
  • 通常在返回主线程更新UI时使用

3.4 全局并发队列

全局并发队列:Global Dispatch Queue

dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
  • GCD提供的默认的并发队列
  • 为了方便开发者使用,苹果提供了全局队列
  • 在使用所线程开发时,如果对队列没有特殊需求,在执行一步任务时,可以直接使用全局队列
  • 使用dispatch_get_global_queue获取全局并发队列,最简单的是dispatch_get_global_queue(0,0)
    • 参数1表示队列优先级,默认优先级为DISPATCH_QUEUE_PRIORITY_DEFAULT = 0,被服务质量quality of service取代
    • 参数2为标记,是为了未来使用保留的。所以这个参数应该永远指定为0 优先级从高到低,对应服务质量
  • DISPATCH_QUEUE_PRIORITY_HIGHQOS_CLASS_USER_INITIATED
  • DISPATCH_QUEUE_PRIORITY_DEFAULTQOS_CLASS_DEFAULT
  • DISPATCH_QUEUE_PRIORITY_LOWQOS_CLASS_UTILITY
  • DISPATCH_QUEUE_PRIORITY_BACKGROUNDQOS_CLASS_BACKGROUND 日常开发中,主队列+全局并发队列的使用:
dispatch_async(dispatch_get_global_queue(0, 0), ^{ 
    //执行耗时操作 
    dispatch_async(dispatch_get_main_queue(), ^{ 
        //回到主线程进行UI操作 
    }); 
});

4.队列与函数的搭配使用

队列/函数同步函数异步函数
串行队列不会开启线程,在当前线程执行任务串行执行,一个接一个开启新线程任务串行执行,一个接一个
并发队列不会开启线程,在当前线程执行任务串行执行,一个接一个开启新线程任务异步执行,没有顺序,和CPU调度有关
主队列死锁不会开启线程,在当前线程执行任务串行执行,一个接一个
全局队列不会开启线程,在当前线程执行任务串行执行,一个接一个开启新线程任务异步执行,没有顺序,和CPU调度有关

4.1 串行队列

4.1.1 同步队列

// 同步串行队列 
- (void)serialSyncTest{ 
    dispatch_queue_t queue = dispatch_queue_create("com.lg.cn", DISPATCH_QUEUE_SERIAL); 
    for (int i = 0; i<5; i++) { 
        dispatch_sync(queue, ^{ 
            NSLog(@"%d:%@",i,[NSThread currentThread]); 
        }); 
    } 
    NSLog(@"hello queue"); 
} 
------------------------- 
//输出以下内容: 
0:<NSThread: 0x2807e83c0>{number = 1, name = main} 
1:<NSThread: 0x2807e83c0>{number = 1, name = main} 
2:<NSThread: 0x2807e83c0>{number = 1, name = main} 
3:<NSThread: 0x2807e83c0>{number = 1, name = main} 
4:<NSThread: 0x2807e83c0>{number = 1, name = main} 
hello queue
  • 不会开启线程,在当前线程执行
  • 任务串行执行,一个接一个

4.1.2 异步函数

// 异步串行队列 
- (void)serialAsyncTest{ 
    dispatch_queue_t queue = dispatch_queue_create("com.lg.cn", DISPATCH_QUEUE_SERIAL); 
    for (int i = 0; i<5; i++) { 
        dispatch_async(queue, ^{ 
            NSLog(@"%d:%@",i,[NSThread currentThread]); 
        }); 
    } 
    NSLog(@"hello queue"); 
} 
------------------------- 
//输出以下内容: 
hello queue 
0:<NSThread: 0x282ba5740>{number = 5, name = (null)} 
1:<NSThread: 0x282ba5740>{number = 5, name = (null)} 
2:<NSThread: 0x282ba5740>{number = 5, name = (null)} 
3:<NSThread: 0x282ba5740>{number = 5, name = (null)} 
4:<NSThread: 0x282ba5740>{number = 5, name = (null)}
  • 开启新线程
  • 任务串行执行,一个接一个

4.2 并发队列

4.2.1 同步函数

// 同步并发队列 
- (void)concurrentSyncTest{ 
    dispatch_queue_t queue = dispatch_queue_create("com.lg.cn", DISPATCH_QUEUE_CONCURRENT); 
    for (int i = 0; i<5; i++) { 
        dispatch_sync(queue, ^{ 
            NSLog(@"%d-%@",i,[NSThread currentThread]); 
        }); 
    } 
    NSLog(@"hello queue"); 
} 
------------------------- 
//输出以下内容: 
0:<NSThread: 0x2807e83c0>{number = 1, name = main} 
1:<NSThread: 0x2807e83c0>{number = 1, name = main} 
2:<NSThread: 0x2807e83c0>{number = 1, name = main} 
3:<NSThread: 0x2807e83c0>{number = 1, name = main} 
4:<NSThread: 0x2807e83c0>{number = 1, name = main} 
hello queue
  • 不会开启线程,在当前线程执行
  • 任务串行执行,一个接一个

4.2.2 异步函数

// 异步并发队列 
- (void)concurrentAsyncTest{ 
    dispatch_queue_t queue = dispatch_queue_create("com.lg.cn", DISPATCH_QUEUE_CONCURRENT); 
    for (int i = 0; i<5; i++) { 
        dispatch_async(queue, ^{ 
            NSLog(@"%d:%@",i,[NSThread currentThread]); 
        }); 
    } 
    NSLog(@"hello queue"); 
}
------------------------- 
//输出以下内容: 
hello queue 
1:<NSThread: 0x2801ef300>{number = 10, name = (null)} 
0:<NSThread: 0x2801ee300>{number = 8, name = (null)} 
2:<NSThread: 0x2801e9b00>{number = 9, name = (null)} 
3:<NSThread: 0x2801edf00>{number = 6, name = (null)} 
4:<NSThread: 0x2801ee7c0>{number = 4, name = (null)}
  • 开启新线程
  • 任务异步执行,没有顺序,和CPU调度有关

4.3 主队列

4.3.1 同步函数

// 同步主队列 
- (void)mainSyncTest{ 
    dispatch_queue_t queue = dispatch_get_main_queue(); 
    for (int i = 0; i<5; i++) { 
        dispatch_sync(queue, ^{ 
            NSLog(@"%d:%@",i,[NSThread currentThread]); 
        }); 
    } 
    NSLog(@"hello queue"); 
}
------------------------- 
//程序崩溃: 
Thread 1: EXC_BREAKPOINT (code=1, subcode=0x1010b4be0)
  • 主队列的作用,专门用来在主线程上调度任务的串行队列
  • 主队列中增加同步函数,导致主线程需要等待同步函数完成后再执行
  • 由于主队列是特殊的串行队列,同步函数需要等待主线程完成后再执行
  • 所以,两个任务相互等待,产生死锁

4.3.2 异步函数

// 异步主队列 
- (void)mainAsyncTest{ 
    dispatch_queue_t queue = dispatch_get_main_queue(); 
    for (int i = 0; i<5; i++) { 
        dispatch_async(queue, ^{ 
            NSLog(@"%d:%@",i,[NSThread currentThread]); 
        }); 
    }
    NSLog(@"hello queue"); 
} 
------------------------- 
//输出以下内容: 
hello queue 
0:<NSThread: 0x280ed0200>{number = 1, name = main} 
1:<NSThread: 0x280ed0200>{number = 1, name = main} 
2:<NSThread: 0x280ed0200>{number = 1, name = main} 
3:<NSThread: 0x280ed0200>{number = 1, name = main} 
4:<NSThread: 0x280ed0200>{number = 1, name = main}
  • 不会开启线程,在当前线程执行
  • 任务串行执行,一个接一个

4.4 全局队列

4.4.1 同步函数

// 同步全局队列 
- (void)globalSyncTest{ 
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0); 
    for (int i = 0; i<5; i++) { 
        dispatch_sync(queue, ^{ 
            NSLog(@"%d:%@",i,[NSThread currentThread]); 
        }); 
    } 
    NSLog(@"hello queue"); 
} 
------------------------- 
//输出以下内容: 
0:<NSThread: 0x283a948c0>{number = 1, name = main} 
1:<NSThread: 0x283a948c0>{number = 1, name = main} 
2:<NSThread: 0x283a948c0>{number = 1, name = main} 
3:<NSThread: 0x283a948c0>{number = 1, name = main} 
4:<NSThread: 0x283a948c0>{number = 1, name = main} 
hello queue
  • 不会开启线程,在当前线程执行
  • 任务串行执行,一个接一个

4.4.2 异步函数

// 异步全局队列 
- (void)globalAsyncTest{ 
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0); 
    for (int i = 0; i<5; i++) { 
        dispatch_async(queue, ^{ 
            NSLog(@"%d:%@",i,[NSThread currentThread]); 
        }); 
    } 
    NSLog(@"hello queue"); 
} 
------------------------- 
//输出以下内容: 
hello queue 
0:<NSThread: 0x2813099c0>{number = 9, name = (null)} 
1:<NSThread: 0x281372b80>{number = 4, name = (null)} 
2:<NSThread: 0x281371e40>{number = 10, name = (null)} 
3:<NSThread: 0x281372080>{number = 6, name = (null)} 
4:<NSThread: 0x281372c00>{number = 3, name = (null)}
  • 开启新线程
  • 任务异步执行,没有顺序,和CPU调度有关

4.5 线程死锁

所谓线程死锁,是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行

image.png

  • 函数调用栈中,_dispatch_sync_f_slow即为死锁的异常