美文网首页
iOS函数与队列

iOS函数与队列

作者: 奉灬孝 | 来源:发表于2020-11-03 19:30 被阅读0次

    1 GCD 介绍

    • 全称是 Grand Central Dispatch,也简称 Dispatch
    • C 语言,提供了非常多强大的函数;
    • GCD 是苹果公司为多核的并行运算提出的解决方案;
    • GCD 会自动充分利用设备的多核(比如双核、四核);
    • GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程);
    • 开发者只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码。
    • 将任务添加到队列,并且指定执行任务的函数。

    2 GCD 的使用步骤

    GCD 的两个核心

    • 任务:执行什么操作
    • 队列:用来存放任务

    GCD 的任务

    GCD 中的任务有两种封装:dispatch_block_tdispatch_function_t

    ● dispatch_block_t(常用)

    提交给指定队列的 block,无参无返回值。

    typedef void (^dispatch_block_t)(void);
    

    ● dispatch_function_t

    提交给指定队列的 functionvoid(*)()类型的函数指针。

    typedef void (*dispatch_function_t)(void *);
    

    GCD 的使用步骤

    1. 创建/获取队列:创建/获取一个并发/串行队列;

    2. 创建任务:确定要做的事;

    3. 将任务添加进队列中(同时指定任务的执行方式):

      • GCD 会自动将队列中的任务取出,放到对应的线程中执行;
      • 任务的取出遵循队列的 FIFO 原则:先进先出,后进后出;
      • GCD 中,要执行队列中的任务时,会自动开启一个线程,当任务执行完,线程不会立刻销毁,而是放到了线程池中。如果接下来还要执行任务的话就从线程池中取出线程,这样节省了创建线程所需要的时间。但如果一段时间内没有执行任务的话,该线程就会被销毁,再执行任务就会创建新的线程。
    队列
        // 1.创建一个队列
        dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_SERIAL);
        // 2.创建一个任务
        dispatch_block_t block = ^{
            NSLog(@"%@",[NSThread currentThread]);
        };
        // 3.将任务添加进队列中(同时指定任务的执行方式)  
        dispatch_async(queue, block);
    

    3 GCD 执行任务的方式

    3.1 同步

    ● dispatch_sync

    提交一个 block 对象到指定队列以同步执行,并在该 block 完成执行后返回(阻塞)。 (因为这个特性,使用该函数要注意死锁的问题,后面会讲到)

    /*!
     * @param queue
     * 提交block的队列,这个队列会被系统retain直到block运行完成;
     * 此参数不能为空(NULL)
     *
     * @param block
     * 要执行的block,block会被自动copy与release;
     * 该block没有返回值,也没有参数;
     * 此参数不能为空(NULL)
     */
    void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
    
    
    - (void)test
    {
        dispatch_queue_t queue = dispatch_queue_create("com.junteng.queue", DISPATCH_QUEUE_SERIAL);
        NSLog(@"0");
        dispatch_sync(queue, ^{
            NSLog(@"1");
        });
        dispatch_sync(queue, ^{
            NSLog(@"2");
        });
        NSLog(@"3");
    }
    /*
    2020-01-31 20:35:48.958272+0800 多线程[4653:706706] 0
    2020-01-31 20:35:48.958533+0800 多线程[4653:706706] 1
    2020-01-31 20:35:48.958696+0800 多线程[4653:706706] 2
    2020-01-31 20:35:48.958810+0800 多线程[4653:706706] 3
     */
    

    ● dispatch_sync

    提交一个 function 到指定队列以同步执行,并在该 function 完成执行后返回(阻塞)。

    /*!
     * @param queue
     * 提交函数的队列,这个队列会被系统retain直到block运行完成;
     * 此参数不能为空(NULL)
     *
     * @param context
     * 传递给函数的参数,即work的参数
     * 
     * @param work
     * 要执行的函数;
     * 此参数不能为空(NULL)
     */
    void dispatch_sync(dispatch_queue_t queue, void *context, dispatch_function_t work);
    
    - (void)test
    {
        dispatch_queue_t queue = dispatch_queue_create("com.junteng.queue", DISPATCH_QUEUE_SERIAL);
        NSLog(@"0");
        dispatch_sync_f(queue, NULL, testFunc);
        NSLog(@"2");
    }
    
    void testFunc() {
        NSLog(@"1");
    }
    /*
    2020-01-31 21:05:35.017838+0800 多线程[4757:726399] 0
    2020-01-31 21:05:35.017959+0800 多线程[4757:726399] 1
    2020-01-31 21:05:35.018047+0800 多线程[4757:726399] 2
     */
    

    3.2 异步

    ● dispatch_async

    提交一个 block 对象到指定队列以异步执行,并直接返回(不会阻塞)。

    void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
    
    - (void)test
    {
        dispatch_queue_t queue = dispatch_queue_create("com.junteng.queue", DISPATCH_QUEUE_SERIAL);
        NSLog(@"0");
        dispatch_async(queue, ^{
            NSLog(@"1");
        });
        dispatch_async(queue, ^{
            NSLog(@"2");
        });
        NSLog(@"3");
    }
    /*
    2020-01-31 21:09:43.675233+0800 多线程[4801:730375] 0
    2020-01-31 21:09:43.675389+0800 多线程[4801:730375] 3
    2020-01-31 21:09:43.675458+0800 多线程[4801:730469] 1
    2020-01-31 21:09:43.675550+0800 多线程[4801:730469] 2
     */
    

    ● dispatch_async

    道理同 dispatch_sync,不再赘述。

    3.3 同步和异步的区别

    • 同步:必须等待当前语句执行完毕,才会执行下一条语句(阻塞);
         在当前线程中执行任务,不具备开启新线程的能力。
    • 异步:不用等待当前语句执行完毕,就可以执行下一条语句(不会阻塞);
         在新的线程中执行任务,具备开启新线程的能力。
      (具备开启新线程的能力,不代表一定能开启新线程。如在主队列异步执行,不会开启新线程,因为主队列的任务在主线程上执行)

    4 GCD 的队列

    4.1 GCD 队列介绍

    Dispatch Queue: 一个用于管理主线程或子线程上串行或并发执行的任务的对象。
    调度队列是 FIFO 队列,您可以以 block 对象的形式向其提交任务。调度队列可以串行或并发执行任务。提交给调度队列的任务在系统管理的线程池上执行。除了主队列在主线程上执行以外,系统无法保证它使用哪个线程来执行任务。

    4.2 GCD 队列类型

    • 串行队列DISPATCH _QUEUE _SERIAL
      以 FIFO 顺序处理传入的任务,即让任务一个接着一个执行。
    • 并发队列DISPATCH _QUEUE _CONCURRENT
      可以让多个任务并发(同时)执行(自动开启多个线程执行任务);
      并发功能只有在异步函数dispatch_async下才有效;
      尽管任务同时执行,但是您可以使用 barrier 栅栏函数 在队列中创建同步点(关于栅栏函数后面会讲到)。
    • 主队列dispatch_queue_main_t
      主队列是一种特殊的串行队列,它特殊在与主线程关联,主队列的任务都在主线程上执行,主队列在程序一开始就被系统创建并与主线程关联。
    串行队列与并发队列

    ● dispatch_get_main_queue

    // @return 主队列
    dispatch_queue_main_t dispatch_get_main_queue(void); 
    

    系统创建主队列并与主线程进行关联的时机:

    ① 调用 `dispatch_main()`;
    ② 调用 `UIApplicationMain`(`iOS`)或者 `NSApplicationMain`(`macOS`);
    ③ 在主线程使用 `CFRunLoopRef`。
    

    大多数情况下我们的应用程序会在 main() 函数里使用第 2 种方式。

    • 全局并发队列dispatch_queue_global_t
      一种特殊的并发队列,可以指定服务质量(服务质量有助于确定队列执行的任务的优先级)。

    ● dispatch_get_global_queue

    /*!
     * @param identifier
     * 队列的服务质量,传0就是默认
     *
     * @param flags
     * 苹果留着以后用的,传0就行
     * 
     * @return dispatch_queue_global_t
     * 可以指定服务质量的系统定义的全局并发队列
     */
    dispatch_queue_global_t dispatch_get_global_queue(long identifier, unsigned long flags);
    

    注意: 对主队列和全局并发队列使用dispatch_suspenddispatch_resumedispatch_set_context是无效的。

    全局并发队列与手动创建的并发队列的区别:

    1. 手动创建的并发队列可以设置唯一标识,可以跟踪错误,而全局并发队列没有;
    2. ARC 中不需要考虑释放内存,dispatch_release(q);不需要也不允许调用。而在 MRC 中由于手动创建的并发队列是 create 出来的,所以需要调用dispatch_release(q);来释放内存,而全局并发队列不需要;
    3. 全局并发队列可以指定服务质量(服务质量有助于确定队列执行的任务的优先级);
    4. 一般我们使用全局并发队列。

    ● dispatch_queue_t(队列)

    • 好的解释了在 MRC 下为何要手动管理dispatch_queue_t的内存。
    • 队列遵循 FIFO 原则。串行队列一次只能调用一个块,但是不同队列可以各自相对于彼此同时调用它们的块。并发队列也是按 FIFO 顺序调用块,但不等待它们完成,从而允许并发调用多个块。
    • 系统管理一个线程池,该线程池处理队列并调用提交给它们的块。
    • 队列是通过调用dispatch_retaindispatch_release来进行引用计数的。提交到队列的待处理块也保留对该队列的引用,直到它们完成为止。一旦释放了对队列的所有引用,系统将重新分配该队列。
    typedef NSObject<OS_dispatch_queue> *dispatch_queue_t;
    

    ● dispatch_queue_create

    创建队列。

    /*!
     * @param label
     * 给队列一个字符串标签进行唯一标识,以便在调试时区分队列
     * 建议使用反向DNS命名方式(com.example.myqueue)
     * 该参数可以为空(NULL)
     *
     * @param attr
     * 指定队列类型
     * DISPATCH_QUEUE_SERIAL     为串行队列
     * DISPATCH_QUEUE_CONCURRENT 为并发队列
     * 该参数可以为空(NULL),传空时默认为串行队列(在iOS4.3版本之前该参数只能传空)
     * 
     * @return dispatch_queue_t
     * 新创建的队列
     */
    dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
    

    创建/获取一个队列:

    // 创建一个串行队列 
    dispatch_queue_t queue = dispatch_queue_create("com.junteng.myqueue", DISPATCH_QUEUE_SERIAL);
    // 创建一个并发队列
    dispatch_queue_t queue = dispatch_queue_create("com.junteng.myqueue", DISPATCH_QUEUE_CONCURRENT);
    // 获取主队列
    dispatch_queue_t queue = dispatch_get_main_queue();
    // 获取全局并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    

    ● dispatch_queue_get_label

    获取队列的唯一标识 label。

    /*!
     * @param queue
     * 需要获取label的队列;
     * 如果需要获取当前队列的label则使用 DISPATCH_CURRENT_QUEUE_LABEL
     * 
     * @return 
     * 创建队列时给队列设置的标签
     */
    const char * dispatch_queue_get_label(dispatch_queue_t queue);
    
        dispatch_queue_t queue = dispatch_queue_create("com.junteng.myqueue", NULL);
        dispatch_sync(queue, ^{
            NSLog(@"%s", dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL));
        });
        NSLog(@"%s", dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL));
        NSLog(@"%s", dispatch_queue_get_label(queue));
    /*
    com.junteng.myqueue
    com.apple.main-thread
    com.junteng.myqueue
     */
    

    4.3 GCD 各种队列的执行效果

    执行方式 并发队列 手动创建的串行队列 主队列
    同步(sync) 没有开启新线程 串行执行任务 没有开启新线程 串行执行任务 没有开启新线程 串行执行任务
    异步(async) 开启新线程 并发执行任务 开启新线程 串行执行任务 没有开启新线程 串行执行任务
    // 同步并发
        dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_CONCURRENT);
        for (int i = 0; i < 5; i++) {
            dispatch_sync(queue, ^{
                NSLog(@"%@",[NSThread currentThread]);
            });
        }
    /*
    <NSThread: 0x600001e6cbc0>{number = 1, name = main}
    <NSThread: 0x600001e6cbc0>{number = 1, name = main}
    <NSThread: 0x600001e6cbc0>{number = 1, name = main}
    <NSThread: 0x600001e6cbc0>{number = 1, name = main}
    <NSThread: 0x600001e6cbc0>{number = 1, name = main}
     */
    
    // 同步串行(手动创建的串行队列)
        dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_SERIAL);
        for (int i = 0; i < 5; i++) {
            dispatch_sync(queue, ^{
                NSLog(@"%@",[NSThread currentThread]);
            });
        }
    /*
    <NSThread: 0x600001e6cbc0>{number = 1, name = main}
    <NSThread: 0x600001e6cbc0>{number = 1, name = main}
    <NSThread: 0x600001e6cbc0>{number = 1, name = main}
    <NSThread: 0x600001e6cbc0>{number = 1, name = main}
    <NSThread: 0x600001e6cbc0>{number = 1, name = main}
     */
    
    // 同步串行(主队列)
        dispatch_queue_t queue = dispatch_get_main_queue();
        dispatch_async(dispatch_get_main_queue(), ^{
            for (int i = 0; i < 5; i++) {
                dispatch_sync(queue, ^{
                    NSLog(@"%@",[NSThread currentThread]);
                });
            }
        });
    /*
    <NSThread: 0x600001e6cbc0>{number = 1, name = main}
    <NSThread: 0x600001e6cbc0>{number = 1, name = main}
    <NSThread: 0x600001e6cbc0>{number = 1, name = main}
    <NSThread: 0x600001e6cbc0>{number = 1, name = main}
    <NSThread: 0x600001e6cbc0>{number = 1, name = main}
     */
    
    // 异步并发:开多个线程,线程数由 GCD 决定
        dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_CONCURRENT);
        for (int i = 0; i < 5; i++) {
            dispatch_async(queue, ^{
                NSLog(@"%@",[NSThread currentThread]);
            });
        }
    /*
    <NSThread: 0x600001ee53c0>{number = 8, name = (null)}
    <NSThread: 0x600001ee57c0>{number = 9, name = (null)}
    <NSThread: 0x600001e16a80>{number = 10, name = (null)}
    <NSThread: 0x600001e17500>{number = 11, name = (null)}
    <NSThread: 0x600001ee53c0>{number = 8, name = (null)}
     */
    
    // 异步串行(手动创建的串行队列)
        dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_SERIAL);
        for (int i = 0; i < 5; i++) {
            dispatch_async(queue, ^{
                NSLog(@"%@",[NSThread currentThread]);
            });
        }
    /*
    <NSThread: 0x600001ee53c0>{number = 8, name = (null)}
    <NSThread: 0x600001ee53c0>{number = 8, name = (null)}
    <NSThread: 0x600001ee53c0>{number = 8, name = (null)}
    <NSThread: 0x600001ee53c0>{number = 8, name = (null)}
    <NSThread: 0x600001ee53c0>{number = 8, name = (null)}
     */
    
    // 异步串行(主队列)
        dispatch_queue_t queue = dispatch_get_main_queue();
        for (int i = 0; i < 5; i++) {
            dispatch_async(queue, ^{
                NSLog(@"%@",[NSThread currentThread]);
            });
        }
    /*
    <NSThread: 0x600002658680>{number = 1, name = main}
    <NSThread: 0x600002658680>{number = 1, name = main}
    <NSThread: 0x600002658680>{number = 1, name = main}
    <NSThread: 0x600002658680>{number = 1, name = main}
    <NSThread: 0x600002658680>{number = 1, name = main}
     */
    

    5 死锁

    5.1 死锁的四大条件

    1. 互斥:某种资源一次只允许一个进程访问,即该资源一旦分配给某个进程,其他进程就不能再访问,直到该进程访问结束。
    2. 占有且等待:一个进程本身占有资源(一种或多种),同时还有资源未得到满足,正在等待其他进程释放该资源。
    3. 不可抢占:别人已经占有了某项资源,你不能因为自己也需要该资源,就去把别人的资源抢过来。
    4. 循环等待:存在一个进程链,使得每个进程都占有下一个进程所需的至少一种资源。

    5.2 GCD 中的死锁

    • 死锁情况: 使用dispatch_sync函数往当前串行队列中添加任务,会卡住当前的串行队列(产生死锁)。
    • 死锁原因: 队列引起的循环等待。
    • 示例1
    /*
     队列的特点:FIFO (First In First Out) 先进先出
     以下将 block(任务2)提交到主队列,主队列将来要取出这个任务放到主线程执行。
     而主队列此时已经有任务,就是执行(viewDidLoad方法),
     所以主队列要想取出 block(任务2),就要等上一个任务(viewDidLoad方法)先执行完,才能取出该任务执行。
     而 dispatch_sync 函数必须执行完 block(任务2)才会返回,才能往下执行代码。
     所以(任务2)要等待(viewDidLoad方法)执行完,(viewDidLoad方法)要等待(任务2)执行完。互相等待,就产生了死锁。
     */
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        NSLog(@"执行任务1");
    
        dispatch_queue_t queue = dispatch_get_main_queue();
        dispatch_sync(queue, ^{
            NSLog(@"执行任务2");
        });
    
        NSLog(@"执行任务3");
    }
    /*
     打印:
     2020-01-19 00:16:26.980630+0800 多线程[25011:5507937] 执行任务1
     (lldb) 
     */
    
    /*
     解决方案:打破(使用`dispatch_sync`函数往`当前串行队列`中添加任务)这一条件即可
     以下将(任务2)异步执行,打印结果为:132
     */
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        NSLog(@"执行任务1");
    
        dispatch_queue_t queue = dispatch_get_main_queue();
        dispatch_async(queue, ^{
            NSLog(@"执行任务2");
        });
    
        NSLog(@"执行任务3");
    }
    /*
     打印:
     2020-01-19 03:16:47.472682+0800 多线程[25416:5603048] 执行任务1
     2020-01-19 03:16:47.472890+0800 多线程[25416:5603048] 执行任务3
     2020-01-19 03:16:47.474389+0800 多线程[25416:5603048] 执行任务2
     */
    
    • 示例2
    /*
     block0(任务2)和 block1(任务3)都添加到串行队列里去,
     由于队列任务先进先出,在当前子线程执行 block1 必须要先执行完 block0
     而 block0 执行完的前提是 sync 的 block1(任务3)要执行完,才能执行(任务4)
     所以产生了死锁
     */
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        NSLog(@"执行任务1");
        dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_SERIAL);
        dispatch_async(queue, ^{
            NSLog(@"执行任务2");
            dispatch_sync(queue, ^{
                NSLog(@"执行任务3");
            });
            NSLog(@"执行任务4");
        });
        NSLog(@"执行任务5");
    }
    /*
     打印:
     2020-01-19 02:55:20.608987+0800 多线程[25339:5586331] 执行任务1
     2020-01-19 02:55:20.609307+0800 多线程[25339:5586331] 执行任务5
     2020-01-19 02:55:20.609446+0800 多线程[25339:5586387] 执行任务2
     (lldb) 
     */
    
    /*
     解决方案:打破(使用`dispatch_sync`函数往`当前串行队列`中添加任务)这一条件即可
     1.以下将(任务3)异步执行,打印结果为:15243
     */
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        NSLog(@"执行任务1");
        dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_SERIAL);
        dispatch_async(queue, ^{
            NSLog(@"执行任务2");
            dispatch_async(queue, ^{
                NSLog(@"执行任务3");
            });
            NSLog(@"执行任务4");
        });
        NSLog(@"执行任务5");
    }
    /*
     2020-01-19 03:25:52.761192+0800 多线程[25474:5609516] 执行任务1
     2020-01-19 03:25:52.761393+0800 多线程[25474:5609516] 执行任务5
     2020-01-19 03:25:52.761429+0800 多线程[25474:5609578] 执行任务2
     2020-01-19 03:25:52.761584+0800 多线程[25474:5609578] 执行任务4
     2020-01-19 03:25:52.761749+0800 多线程[25474:5609578] 执行任务3
     */
    /*
     2.以下将(任务3)添加到其他串行队列,打印结果为:15234
     */
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        NSLog(@"执行任务1");
        dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);
        dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_SERIAL);
        dispatch_async(queue1, ^{
            NSLog(@"执行任务2");
            dispatch_sync(queue2, ^{
                NSLog(@"执行任务3");
            });
            NSLog(@"执行任务4");
        });
        NSLog(@"执行任务5");
    }
    /*
     2020-01-19 03:25:52.761192+0800 多线程[25474:5609516] 执行任务1
     2020-01-19 03:25:52.761393+0800 多线程[25474:5609516] 执行任务5
     2020-01-19 03:25:52.761429+0800 多线程[25474:5609578] 执行任务2
     2020-01-19 03:25:52.761584+0800 多线程[25474:5609578] 执行任务3
     2020-01-19 03:25:52.761749+0800 多线程[25474:5609578] 执行任务4
     */
    /*
     3.改为并发队列,打印结果为:15234
     */
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        NSLog(@"执行任务1");
        dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_CONCURRENT);
        dispatch_async(queue, ^{
            NSLog(@"执行任务2");
            dispatch_sync(queue, ^{
                NSLog(@"执行任务3");
            });
            NSLog(@"执行任务4");
        });
        NSLog(@"执行任务5");
    }
    /*
     2020-01-19 03:25:52.761192+0800 多线程[25474:5609516] 执行任务1
     2020-01-19 03:25:52.761393+0800 多线程[25474:5609516] 执行任务5
     2020-01-19 03:25:52.761429+0800 多线程[25474:5609578] 执行任务2
     2020-01-19 03:25:52.761584+0800 多线程[25474:5609578] 执行任务3
     2020-01-19 03:25:52.761749+0800 多线程[25474:5609578] 执行任务4
     */
    

    相关文章

      网友评论

          本文标题:iOS函数与队列

          本文链接:https://www.haomeiwen.com/subject/yysivktx.html