zoukankan      html  css  js  c++  java
  • Dispatch Queues调度队列

    前言-死锁案例

    // 在主线程中执行
    dispatch_queue_t queueMain = dispatch_get_main_queue();
    dispatch_sync(queueMain, ^{
            NSLog(@"+++++++");
    });
    NSLog(@"hahahaha");

    案例分析:运行结果是程序阻塞在dispatch_sync()处。由于main线程执行到dispatch_sync()处,线程处于等待状态。将block任务块添加到主串行队列最后,block等待当前任务(即正在主线程中执行的任务)执行完毕,而当前任务因为阻塞无法结束,导致两边都在等待,所以出现死锁。

    一、简介

    Dispatch queues是一种异步和并发执行任务的简单方式,将任务通过function或者block object的方式添加到dispatch queue。使用Dispatch queue还是使用thread?Dispatch queue中只需要封装任务,再将任务加到合适的Disptch queue,由系统去管理线程。如果用thread,需要做的工作就多了。使用Dispatch queue实现同步,实现效果比加锁要有效。

    由dispatch queue管理提交的任务,所有任务遵从先进先出的规则,先添加到dispatch queue中的任务会最先开始执行。GCD提供了一些现成的dispatch queue,或者也可以自己定制dispatch queue。dispatch queue可分为3类:

    1、Serial queues串行队列

    serial queues(private dispatch queue)按dispatch queue中的顺序一个时间执行一个任务,常用于同步的获取指定资源。你可以随意创建serial queues,多个serial queues之间是并行的。换句话说,创建了4个serial queues,单个queue中的任务串行,queue与queue中的任务是并行的。

    2、Concurrent queues并发队列

    concurrent queues(global dispatch queue)并发的执行一个或多个任务,但也是按照先进先出的顺序开始启动任务。在某个时间点,可执行任务数量的上限取决于系统状态。在iOS5以后,可以通过函数dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)获取4中queue,也可以通过以下代码创建:

    dispatch_queue_t queue = dispatch_queue_create("com.dispatch.concurrent", DISPATCH_QUEUE_CONCURRENT)

    3、Main dispatch queue主队列

    main dispatch queue(globally available serial queue)中的任务在主线程上串行执行。此queue和应用的run loop配合工作,主线程中在执行run loop的事件源后,插入queue任务。main dispatch queue为应用的主要同步queue。尽管不需要自己创建main dispatch queue,但使用时要注意。

    二、与Dispatch Queues相关的技术

    1、Dispatch groups

    2、Dispatch semaphores

    3、Dispatch sources(specific types of system events)

    三、Block任务

    1、block使用准则:

    1)dispatch queue中的blocks异步地执行,最好在block只捕捉上下文中纯变量,不要试图捕捉结构体或者其它基于指针的变量,这些变量由调用上下文来分配和回收。原因是在block执行中,捕获的变量内存可能已经被回收。当然有解决方案,比如为该对象分配内存,然后blcok指向并retain当前内存。

    2)dispatch queue会copy添加到其中的block。等任务结束再释放block。换句话说,在添加到queue时不需要明确写copy代码。

    3)尽管在执行小任务时,queues调度执行任务比原始的threads更有效,但调度和执行block仍然有开销。如果block中的任务轻,直接执行反而更高效。

    4)不要缓存与基础线程相关的数据,不要试图从其它的block中获取数据。如果希望同一个queue中的数据共享,使用dispatch queue的context指针来实现。

    5)如果block内部创建了很多的OC对象,将block代码加到@autorelease块中,这样能更及时释放不再使用的对象。尽管dispatch queue中带有自动释放池,但不保证会及时回收那些OC对象。

     四、创建和管理Dispatch Queue

    1、获取Global Concurrent Dispatch Queues。

    每个应用程序有4个global concurrent dispatch queues,他们之间只有优先级的区别,包括的优先级有:DISPATCH_QUEUE_PRIORITY_HIGHDISPATCH_QUEUE_PRIORITY_DEFAULT、DISPATCH_QUEUE_PRIORITY_LOW、DISPATCH_QUEUE_PRIORITY_BACKGROUND。尽管dispatch queue存在引用计数,但不需要retain/release该队列,因为他们在应用中是全局可访问的,只需要通过方法获取该queue即可,代码如下:

    dispatch_queue_t aQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    2、创建Serial Dispatch Queues

    dispatch_queue_t queue = dispatch_queue_create("com.example.MyQueue", NULL);

    第一个参数是队列的名称,第二个暂时固定为NULL。

    3、获取runtime的Common Queues

    1)dispatch_get_current_queue()获取当前队列。如果在block内部调用,则返回的是block添加到的queue。如果是block外部调用,则返回当前queue。

    2)dispatch_get_main_queue()获取主线程队列。

    3)dispatch_get_global_queue()获取全局并发队列。

    4、Dispatch Queue的内存管理

    并发队列和主队列都不需要考虑内存管理的问题,只需要考虑自己定义的串行队列。通过dispatch_retain和dispatch_release方法增加和减少队列对象的引用计数器。

    见到过在dealloc方法中,调用dispatch_release释放自定义的串行队列。

    5、Dispatch Queue中数据存储-Context

    dispatch queue能通过dispatch_set_context函数关联context data,通过dispatch_get_context方法来获取context data,context data存储了指向OC对象或者是数据结构的指针。context data需要手动分配和释放内存,可使用queue的finalizer函数完成释放。代码如下:

    dispatch_queue_t storeData()
    {
        MyDataContext *contextData = (MyDataContext*) malloc(sizeof(MyDataContext));
        myInitializeDataContextFunction(contextData);
     
        // 创建串行队列,设置context data
        dispatch_queue_t serialQueue = dispatch_queue_create("com.example.CriticalTaskQueue", NULL);
        dispatch_set_context(serialQueue, contextData);
    
        // 回收context data
        dispatch_set_finalizer_f(serialQueue, &myFinalizerFunction);
     
        return serialQueue;
    }
    
    void myFinalizerFunction(void *context)
    {
        MyDataContext* contextData = (MyDataContext*)context;
     
        // 清理内容结构
        myCleanUpDataContextFunction(contextData);
     
        // 释放
        free(contextData);
    }

    五、往Dispatch Queues中添加任务

    1、添加单一任务

    dispatch_sync()同步调用,会阻塞方法所在线程。

    dispatch_async()异步调用,不会阻塞线程。

    dispatch_barrier_async()。等待前面的任务全部结束,该task才能执行,然后后面的任务才能启动执行

    dispatch_once(&onceToken,^{});  // static dispatch_once_t onceToken;

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW,(int64_t)(2.0*NSEC_PER_SEC)),dispatch_get_main_queue(), ^{});// 将2秒后需执行的任务......

    2、Completion Block

    在dispatch queue中的某个任务执行结束后,通知做些后续操作。不同于回调机制,dispatch queue采用completion block实现。具体操作是,在其任务最后添加将completion block提交到指定queue中。

    //  myBlock和myQueue分别就是completion block和提交至的queue
    void sum_async(int data01, int data02, dispatch_queue_t myQueue, void (^myBlock)(int))
    {
       // Retain the queue provided by the user to make sure it does not disappear before the completion block can be called.
      dispatch_retain(myQueue);
     
       // Do the work on the default concurrent queue, then call the user-provided block with the results.        
      dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        int sumValue = data + len;
        dispatch_async(myQueue, ^{ myBlock(sumValue); 
      });
     
      // Release the user-provided queue when done
      dispatch_release(myQueue);
      });
    }

    3、并发执行循环迭代dispatch_apply()

    在执行指定次数的循环中,且单个循环间彼此独立、执行的先后顺序无关紧要。这时不应该再采用最初的for循环依次遍历,而应考虑通过concurrent dispatch queue提高性能。有个函数dispatch_apply(),能一次性的将所有循环提交给queue,当提交给concurrent queue时,就能同一时间执行多个循环。

    当然也可以将任务提交到serial queue,但没什么优势和意义,不建议。

    注意:跟普通的循环一样,dispatch_apply函数需等所有的循环运行结束后才会返回。因为会堵塞当前线程,在主线程中要慎用,避免执行所有循环阻碍主线程及时响应事件。如果循环要求比较长的处理时间,你应该在其它线程上调用dispatch_apply。

    避免出现死锁场景:执行dispatch_apply所在线程的queue和入参中的queue是同一个,且queue是serial queue,那么线程执行到dispatch_apply函数时,需等待所有循环运行结束,而serial queue中的任务又只执行到dispatch_apply,得等当前任务执行完毕,双方互相等待彼此结束任务。

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    // count是总共循环次数,i是当前循环数
    dispatch_apply(count, queue, ^(size_t i) {
       printf("%u
    ",i);
    });

    4、在主线程上执行任务

    GCD提供的一个特殊的main dispatch queue,其中的任务在主线程中串行。可通过dispatch_get_main_queue函数可获取该queue,它由应用自动提供且自动回收,给主线程创建了一个run loop。

    如果创建的不是cocoa应用程序,也不想明确创建run loop,那必须调用dispatch_main函数去明确执行main dispatch queue中的任务,否则,queue中的任务永远不会执行。

    5、在任务中使用OC对象

    GCD建立在cocoa内存管理的基础上,可以在任务中任意使用OC对象,每一个dispatch queue都拥有自己的autorelease pool。但如果应用内存要求严格,且在任务中添加了很多的autoreleased对象,想要及时释放这些对象,创建自己的autorelease pool。

    六、暂停和恢复Queue

    通过调用dispatch_suspend函数暂时停止queue中的任务执行,调用dispatch_resume函数恢复执行。有一个suspension reference count,当暂停时该引用计数增加,当恢复时引用计数减少,为0时,queue是暂停状态。因此,必须平衡调用suspend和resume方法。

    注意:这两个方法都是异步执行,在queue中任务之间生效,也就是说暂停不会让正在执行的任务中止,中止是下一个任务。

    7、使用Dispatch Semaphore来有效使用有限资源

    给某个有限资源,指定能同时访问的最大数量,可以通过dispatch semaphore实现。dispatch semaphore跟大多数semaphore一样,除了获取dispatch semaphore比系统的semaphore要快。这是因为GCD一般不访问内核,只有在资源不可用时,才会去内核调用,系统暂停线程以待semaphore发出信号。使用如下:

    1、通过dispatch_semaphore_create函数创建semaphore,可以选择指定一个正整数来显示一次可访问上限。

    2、在每个任务,通过dispatch_semaphore_wait函数等待信号量。

    3、当信号量发出信号,就能获取资源执行任务。

    4、当使用资源结束后,通过dispatch_semaphore_signal函数释放信号并发出信号。

    这些步骤如何工作可参考系统的file descriptor。每个应用提供了有限的file descriptors,如果某个任务是处理大量的文件,你不想一次打开这么多文件以至于耗尽file descriptors,这时,就可以采用semaphore去限制使用file descriptors的数量,使用部分代码如下:

    // Create the semaphore, specifying the initial pool size
    dispatch_semaphore_t fd_sema = dispatch_semaphore_create(getdtablesize() / 2);
     
    // Wait for a free file descriptor
    dispatch_semaphore_wait(fd_sema, DISPATCH_TIME_FOREVER);
    fd = open("/etc/services", O_RDONLY);
     
    // Release the file descriptor when done
    close(fd);
    dispatch_semaphore_signal(fd_sema);

    八、Group Queue组任务队列

     dispatch group阻塞线程直到一个或多个任务执行结束。可用的场景是等待所有指定任务完成后,再执行某任务。另一个类似于使用dispatch group的是thread join,两者实现方式不同。

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
     
    // Add a task to the group
    dispatch_group_async(group, queue, ^{
       // Some asynchronous work
    });
     
    // Do some other work while the tasks execute.
     
    // When you cannot make any more forward progress,
    // wait on the group to block the current thread.
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
     
    // Release the group when it is no longer needed.
    dispatch_release(group);

    九、Dispatch queues和线程安全

    任何时候实现并发,都需要知道以下几点:

    1、dispatch queue本身是线程安全的,换句话说,可以从任何线程提交任务到queue中,不需要考虑queue的锁和同步问题。

    2、dispatch_sync调用所在的queueA,与入参queueB,如果queueA和queueB是同一个queue,那么会造成死锁的情况。使用dispatch_async函数就不会。

    3、避免在任务中使用锁。如果是串行队列,如果获取不到锁,会阻塞整个队列中的任务执行。如果是并行队列,获取不到锁,会阻塞队列中其它任务的执行。所以需要同步执行代码,使用serial dispatch queue代替锁。

    4、尽管可以获取任务所在基础线程信息,最好不要这样做。

    十、该博客翻译自苹果官方文档

    https://developer.apple.com/library/content/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html#//apple_ref/doc/uid/TP40008091-CH102-SW8

  • 相关阅读:
    天梯赛5-12 愿天下有情人都是失散多年的兄妹 【dfs】
    poj2718 Smallest Difference【贪心】
    HDU problem 5635 LCP Array【思维】
    codeforces 782C Andryusha and Colored Balloons【构造】
    HDU 4278 Faulty Odometer【进制转换】
    codeforces B. The Meeting Place Cannot Be Changed【二分】
    POJ 3264 Balanced Lineup 【线段树】
    HDU 1850
    CodeForces-714C
    HDU Problem 1247 Hat's Words 【字典树】
  • 原文地址:https://www.cnblogs.com/zhouyi-ios/p/5223968.html
Copyright © 2011-2022 走看看