iOS开发:多线程之理解任务和队列

Posted wuwuFQ

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS开发:多线程之理解任务和队列相关的知识,希望对你有一定的参考价值。

本文着重点是任务和队列,线程是什么?多线程有什么用?怎么正确使用任务和队列?案例使用GCD讲解。

进程和线程

  • 进程:它是操作系统分配资源的基本单元,是在系统中正在运行的一个程序,可以理解为手机上的一个app;进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内,拥有独立运行的全部资源。
  • 线程:程序执行流的最小单元,线程是进程中的一个实体;一个进程要想执行任务,必须至少有一条线程,应用程序启动的时候,系统会默认开启一条线程,也就是主线程。
  • 进程和线程的关系:线程是进程的执行单元,进程的所有任务都在线程中执行;线程是CPU分配资源和调度的最小单位;一个程序可以对应多个进程(多进程),一个进程可以对应多个线程,但至少有一个线程;同一个进程内的线程共享进程资源

线程是进程的执行单元,一个进程至少有一个线程。

多进程和多线程

  • 多进程:进程是程序在计算机上的一次执行活动。当你运行一个程序,你就启动了一个进程。在同一个时间里,同 一个计算机系统中如果允许两个或两个以上的进程处于运行状态,这便是多进程。
  • 多线程:同一时间,CPU只能处理1条线程,只有1条线程在执行。多线程并发执行,其实是CPU快速地在多条线程之间调度(切换)。如果 CPU 调度线程的时间足够快,就造成了多线程并发执行的假象。

多线程的优点: 能适当提高程序的执行效率 能适当提高资源利用率(CPU、内存利用率);

多线程的缺点: 开启线程需要占用一定的内存空间(默认情况下,主线程占用 1M,子线程占用 512KB),如果开启大量的线程,会占用大量的内存空间,降低程序的性能,CPU会在N多线程之间调度,消耗大量的CPU资源,每条线程被调度执行的频次会降低。

任务和队列

  • 任务:就是执行操作的意思,也就是在线程中执行的业务代码。在GCD中是放在block中的。执行任务有两种方式:同步执行(sync)和异步执行(async)。
    • 同步(Sync):只能在当前线程中执行任务,不具备开启新线程的能力,任务立刻马上执行,会阻塞当前线程并等待 Block 中的业务代码执行完毕 dispatch 函数才会返回,然后当前线程才会继续往下运行。
    • 异步(Async):可以在新的线程中执行任务,具备开启线程的能力,但不一定会开启新的线程,dispatch 函数会立即返回, 然后Block在后台异步执行,即当前线程会直接往下执行,不会阻塞当前线程。
  • 队列(Dispatch Queue):这里的队列指执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的线性表,采用 FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。分为串行队列和并行队列。
    • 串行队列(Serial Dispatch Queue):同一时间内,队列中只能执行一个任务,只有当前的任务执行完成之后(Block中的业务代码执行完毕),才能执行下一个任务。
    • 并发(并行)队列(Concurrent Dispatch Queue): 同时允许多个任务并发执行(不会等待Block中的业务代码执行完毕)。(可以开启多个线程,并且同时执行任务)。并发队列的并发功能只有在异步dispatch_async函数下才有效。

串行队列

串行队列里面只会有一个线程,按序执行。

//创建串行队列
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);

并发队列

并发队列可以存在多个线程。

//创建并行队列
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);

全局并发队列

本质是一个并发队列,由系统提供,方便编程,可以不用创建就直接使用。

//获取全局并发队列
/**
   第一个参数:优先级 也可直接填后面的数字
   #define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高
   #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默
   #define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低
   #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后台
   第二个参数: 预留参数  0
*/
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

主队列

本质是一个串行队列,专门负责调度主线程,添加到主队列的任务不会开启新的线程。

//获取主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();

正确使用线程

以上都是对线程的理解,执行任务需要开启线程,线程在队列里面按序执行,接下来我们看看实际的应用

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

这是六种最基本的排列组合,我们一一讲解。

并发队列

并发 + 同步(sync)

- (void)syncConcurrent 
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"wuwuFQ:syncConcurrent---Begin");
    //创建并行队列
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(queue, ^
        //        任务1
        for (int i = 0; i < 2; ++i) 
            NSLog(@"wuwuFQ:1---%@",[NSThread currentThread]);      // 打印当前线程
        
    );
    dispatch_sync(queue, ^
        //        任务2
        for (int i = 0; i < 2; ++i) 
            NSLog(@"wuwuFQ:2---%@",[NSThread currentThread]);      // 打印当前线程
        
    );
    dispatch_sync(queue, ^
        //        任务3
        for (int i = 0; i < 2; ++i) 
            NSLog(@"wuwuFQ:3---%@",[NSThread currentThread]);      // 打印当前线程
        
    );
    NSLog(@"wuwuFQ:syncConcurrent---End");

首先明确一点,同步(sync)会阻塞线程等待block回调,并且是不具备开启线程能力的,即使放在并发队列里面,还是需要当前线程也就是主线程去执行任务的

所以结论是:并发+同步 = 没有开启新线程,串行执行任务

并发 + 异步(async)

- (void)asyncConcurrent 
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"wuwuFQ:asyncConcurrent---Begin");
    //创建并行队列
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^
        //        任务1
        for (int i = 0; i < 2; ++i) 
            NSLog(@"wuwuFQ:1---%@",[NSThread currentThread]);      // 打印当前线程
        
    );
    dispatch_async(queue, ^
        //        任务2
        for (int i = 0; i < 2; ++i) 
            NSLog(@"wuwuFQ:2---%@",[NSThread currentThread]);      // 打印当前线程
        
    );
    dispatch_async(queue, ^
        //        任务3
        for (int i = 0; i < 2; ++i) 
            NSLog(@"wuwuFQ:3---%@",[NSThread currentThread]);      // 打印当前线程
        
    );
    NSLog(@"wuwuFQ:asyncConcurrent---End");

这个组合是我们开发过程中经常使用的,异步(async)开启了新线程,并行队列不会阻塞线程,不会等待block的回调,CPU在几个线程之间快速切换,我运行了多次,每次的执行顺序都不通。

所以结论是:并发+异步 = 开启了新线程,并发执行任务

串行队列

串行 + 同步(sync)

- (void)syncSerial 
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"wuwuFQ:syncSerial---Begin");
    //创建串行队列
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(queue, ^
        //        任务1
        for (int i = 0; i < 2; ++i) 
            NSLog(@"wuwuFQ:1---%@",[NSThread currentThread]);      // 打印当前线程
        
    );
    dispatch_sync(queue, ^
        //        任务2
        for (int i = 0; i < 2; ++i) 
            NSLog(@"wuwuFQ:2---%@",[NSThread currentThread]);      // 打印当前线程
        
    );
    dispatch_sync(queue, ^
        //        任务3
        for (int i = 0; i < 2; ++i) 
            NSLog(@"wuwuFQ:3---%@",[NSThread currentThread]);      // 打印当前线程
        
    );
    //        任务4
	for (int i = 0; i < 2; ++i)  
        NSLog(@"wuwuFQ:4---%@",[NSThread currentThread]);      // 打印当前线程
    
    NSLog(@"wuwuFQ:syncSerial---End");

同步不具备开启新线程能力,任务都在主线程按序执行

所以结论是:串行+同步 = 没有开启新线程,串行执行任务

串行 + 异步(async)

- (void)asyncSerial 
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"wuwuFQ:asyncSerial---Begin");
    //创建串行队列
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^
        //        任务1
        for (int i = 0; i < 2; ++i) 
            NSLog(@"wuwuFQ:1---%@",[NSThread currentThread]);      // 打印当前线程
        
    );
    dispatch_async(queue, ^
        //        任务2
        for (int i = 0; i < 2; ++i) 
            NSLog(@"wuwuFQ:2---%@",[NSThread currentThread]);      // 打印当前线程
        
    );
    dispatch_async(queue, ^
        //        任务3
        for (int i = 0; i < 2; ++i) 
            NSLog(@"wuwuFQ:3---%@",[NSThread currentThread]);      // 打印当前线程
        
    );
    //        任务4
    for (int i = 0; i < 2; ++i) 
        NSLog(@"wuwuFQ:4---%@",[NSThread currentThread]);      // 打印当前线程
    
    NSLog(@"wuwuFQ:asyncSerial---End");


异步具有开启新线程的能力,但是在串行队列里面任务1、2、3只开启了一个线程,任务1、2、3在子线程按顺序执行,任务4在主线程里面,CPU在两个线程之间快速切换,所以任务4的打印会穿插在任务1、2、3中间,但任务1、2、3的执行顺序不会变。

所以结论是:串行+异步 = 开启了一个新线程,串行执行任务

主队列

主队列 + 同步(sync)

- (void)syncMainQueue 
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"wuwuFQ:syncMainQueue---Begin");
    //创建并行队列
    dispatch_queue_t main_queue = dispatch_get_main_queue();
    dispatch_sync(main_queue, ^
        //        任务1
        for (int i = 0; i < 2; ++i) 
            NSLog(@"wuwuFQ:1---%@",[NSThread currentThread]);      // 打印当前线程
        
    );
    dispatch_sync(main_queue, ^
        //        任务2
        for (int i = 0; i < 2; ++i) 
            NSLog(@"wuwuFQ:2---%@",[NSThread currentThread]);      // 打印当前线程
        
    );
    dispatch_sync(main_queue, ^
        //        任务3
        for (int i = 0; i < 2; ++i) 
            NSLog(@"wuwuFQ:3---%@",[NSThread currentThread]);      // 打印当前线程
        
    );
    NSLog(@"wuwuFQ:syncMainQueue---End");


主队列负责管理主线程,不会创建新的线程,创建的1、2、3任务遵循FIFO(先进先出)插入到队尾最后执行,但是任务是同步执行,所以后面的任务需要等待任务1、2、3的执行,而任务1、2、3却在队列的队尾要等待前面的任务执行,这样线程就会卡主,造成死锁。而程序也会报错: __DISPATCH_WAIT_FOR_QUEUE__

所以结论是:主队列+同步 = 阻塞主线程,死锁

主队列 + 异步(async)

- (void)asyncMainQueue 
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"wuwuFQ:asyncMainQueue---Begin");
    //创建并行队列
    dispatch_queue_t main_queue = dispatch_get_main_queue();
    dispatch_async(main_queue, ^
        //        任务1
        for (int i = 0; i < 2; ++i) 
            NSLog(@"wuwuFQ:1---%@",[NSThread currentThread]);      // 打印当前线程
        
    );
    dispatch_async(main_queue, ^
        //        任务2
        for (int i = 0; i < 2; ++i) 
            NSLog(@"wuwuFQ:2---%@",[NSThread currentThread]);      // 打印当前线程
        
    );
    dispatch_async(main_queue, ^
        //        任务3
        for (int i = 0; i < 2; ++i) 
            NSLog(@"wuwuFQ:3---%@",[NSThread currentThread]);      // 打印当前线程
        
    );
    NSLog(@"wuwuFQ:asyncMainQueue---End");

主队列不会创建新的线程,首先任务1、2、3会被插入到主队列尾部,然后任务异步执行,无需等待block回调,主线程按序执行,最后执行任务1、2、3。

所以结论是:主队列+异步 = 没有开启新线程,串行执行任务

以上是关于iOS开发:多线程之理解任务和队列的主要内容,如果未能解决你的问题,请参考以下文章

iOS开发:多线程之理解任务和队列

iOS开发之多线程重点总结

Objective-C IOS多线程之GCD深入理解

iOS 多线程之GCD的使用

SpringBoot开发案例之多任务并行+线程池处理

测开之并发编程篇・《并发并行线程队列进程和协程》