• 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);
    
    • 1
    • 2

    并发队列

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

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

    全局并发队列

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

    //获取全局并发队列
    /**
       第一个参数:优先级 也可直接填后面的数字
       #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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    主队列

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

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

    正确使用线程

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

    任务并发队列串行队列主队列
    同步(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");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    请添加图片描述

    首先明确一点,同步(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");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    请添加图片描述

    这个组合是我们开发过程中经常使用的,异步(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");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    请添加图片描述

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

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

    串行 + 异步(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
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    请添加图片描述

    异步具有开启新线程的能力,但是在串行队列里面任务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
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    请添加图片描述

    主队列负责管理主线程,不会创建新的线程,创建的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
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    请添加图片描述

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

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

  • 相关阅读:
    球场输了?卡塔尔背地里可能赢麻了!
    Spark 基础教程:wordcount+Spark SQL
    资源管理游戏模版进入The Sandbox
    音视频开发项目:H.265播放器:视频解码篇
    LVS负载均衡集群
    dart 学习 之命名参数
    Redis事务管理
    数据结构C语言:单链表和双链表的实现(不带头节点的单链表、带头的双链表)
    编译过程,gcc/g++,make命令
    【网络编程】- TCP/UDP 套接字编程
  • 原文地址:https://blog.csdn.net/wujakf/article/details/126055985