zoukankan      html  css  js  c++  java
  • Objective-C GCD深入理解

    GCD(Grand Central Dispatch),主要用于多线程编程。它屏蔽了繁琐的线程实现及管理细节,将其交由系统处理。开发者只需要定义任务block(在底层被封装成dispatch_continuation_t结构体),并提交到正确的dispatch queue中。GCD包含dispatch queue和dispatch source。

    一、dispatch queue是FIFO队列,分为两种:

    1、serial(串行),队列中的block同一个时刻只会派发给一个线程,所以要等当前block执行完,才会开始执行下一个block。

    2、concurrent(并行),队列中的block同一个时刻可能会派发给多个线程,所以多个block可以同时执行。

    3、苹果官方说明:

    Serial queues (also known as private dispatch queues) execute one task at a time in the order in which they are added to the queue.
    The currently executing task runs on a distinct thread (which can vary from task to task) that is managed by the dispatch queue.

    注意:串行队列中不同block可能在不同线程执行! 

     

    二、使用dispatch queue的方法,也分为两种:

    1、dispatch_sync(同步),调用dispatch_sync方法的线程会被阻塞,直到block执行结束。在当前线程调用dispatch_sync方法可能会导致死锁!!!

    2、dispatch_async(异步),调用dispatch_async方法的线程不会被阻塞。 

    NSLog(@"1"); // 死锁,只会输出1
    dispatch_sync(currentQueue, ^{
        NSlog(@"2"); // block也提交到当前线程对应的队列        
    });
    NSlog(@"3");   
    

    死锁的理解:

    1、dispatch_sync方法调用和block调用,是当前线程需要处理的两个任务。

    2、dispatch_sync方法调用首先提交到队列中。

    3、然后block调用提交到队尾,需要等待dispatch_sync方法调用完成。

    4、而dispatch_sync方法调用,又需要等到block执行结束才能返回。这就形成了等待环,即死锁。

    解决死锁:把block交由另一个线程执行

    // 依次输出1、2、3
    NSLog(@"1"); 
    dispatch_sync(notCurrentQueue, ^{
        NSlog(@"2"); // block提交到另外一个线程对应的队列        
    });
    NSlog(@"3");   

     

    三、创建队列:

    dispatch_queue_t dispatch_queue_create(const char *_Nullable label, dispatch_queue_attr_t _Nullable attr);
    // label:队列的唯一标识,方便调试
    // attr:队列类型,NULL或者DISPATCH_QUEUE_SERIAL表示串行,DISPATCH_QUEUE_CONCURRENT表示并行
    dispatch_queue_t serialQueue = dispatch_queue_create("com.tencent.wechat.file", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t concurrentQueue = dispatch_queue_create("com.tencent.wechat.network",DISPATCH_QUEUE_CONCURRENT);  

    在MRC下,dispatch_queue_create创建的队列,需要用dispatch_release释放掉。 

    四、有时候,并不需要开发者创建队列,系统已经提供了两种很好用的队列。

    主队列,对应主线程,是个串行队列:

    dispatch_queue_t serialMainQueue = dispatch_get_main_queue();
    

    全局队列,是并行队列:

    dispatch_queue_t dispatch_get_global_queue(long identifier, unsigned long flags);
    // identifier:队列优先级
    // flags:通常填0
    dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    

      

    五、dispatch_after用于延迟执行任务:

    void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
    // 3秒后提交block到主队列,这个时间并不精确,得看当时线程的繁忙程度
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            
    });

    六、dispatch_suspend和dispatch_resume:

    1、dispatch_suspend,挂起队列,只会暂停还没执行的任务,已经在执行的任务不会被影响。

    2、dispatch_resume,恢复队列,继续调度还没执行的任务。

    3、注意,resume一个没有被suspend的队列会导致crash!

    七、dispatch_once

    详见:https://www.cnblogs.com/yangwenhuan/p/9603472.html

    八、dispatch group

    在多个并行block都执行完,这就可以用到dispatch group

    九、dispatch_set_target_queue 

    修改优先级

    十、dispatch_barrier_async

    barrier,顾名思义,添加一个障碍,就是就是个同步点。

    dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    // blk1, blk2, blk3并行执行
    dispatch_async(globalConcurrentQueue, blk1);
    dispatch_async(globalConcurrentQueue, blk2);
    dispatch_async(globalConcurrentQueue, blk3);
    // blk1, blk2, blk3全部执行完,才开始执行blk4
    dispatch_barrier_async(globalConcurrentQueue, blk4);
    // blk4执行完,才开始并行执行blk5, blk6
    dispatch_async(globalConcurrentQueue, blk5);
    dispatch_async(globalConcurrentQueue, blk6);

    十一、dispatch_apply

    提交特定数量的block到队列中,并等待全部执行结束。dispatch_apply存在与dispatch_sync一样的死锁问题,因此推荐把dispatch_apply放到dispatch_async中执行。

    dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_apply(10, q, ^(size_t iter) {
        NSLog(@"iteration[%zu]", iter);
    });
    NSLog(@"done");
    
    输出:
    iteration[3]
    iteration[2]
    iteration[0]
    iteration[4]
    iteration[1]
    iteration[5]
    iteration[9]
    iteration[8]
    iteration[7]
    iteration[6]
    done // 最后输出一定是这个 

    十二、dispatch semaphore

    dispatch semaphore是信号量的封装,可用于实现线程安全,相比串行队列,粒度更小

    // 创建信号量,初始值可以不为1
    dispatch_semaphore_t s = dispatch_semaphore_create(1);
    // 阻塞等待信号量大于等于1
    dispatch_semaphore_wait(s, DISPATCH_TIME_FOREVER);
    // 信号量减1,开始执行临界区代码
    
    /* 临界区代码 */
    
    // 临界区代码执行结束,信号量+1,最先等待信号量的线程得到执行
    dispatch_semaphore_signal(s);

    十三、dispatch io

    // 获取文件描述符
    dispatch_fd_t fd = open(file_path, O_RDWR);
    // 创建用于处理dispatch io block的队列
    dispatch_queue_t queue = dispatch_queue_create(queue_name, NULL);
    // 创建dispatch io,绑定文件描述符
    dispatch_io_t io = dispatch_io_create(DISPATCH_IO_STREAM, fd, queue, ^(int error) {
        // 异常
        close(fd);
    });
    // 设置一次最多读取的字节数
    dispatch_io_set_high_water(io, size);
    // 这里书上说会使用global queue来并发读,但是实践没看出来,懂的同学请多多指教
    dispatch_io_read(io, 0, max_size, queue, ^(bool done, dispatch_data_t  _Nullable data, int error) {
        // dispatch_data_t转NSString
        dispatch_data_apply(data, ^bool(dispatch_data_t region, size_t offset, const void *buffer, size_t size) {
            NSString *sData = [[NSString alloc] initWithBytes:buffer length:size encoding: NSUTF8StringEncoding];
            return true;
        });
    });

    十四、dispatch queue VS NSThread, pthread

    利用NSThread和pthread也是可以进行多线程编程的,但是

    1)需要自己实现线程管理,如分配多少线程、什么时候分配、什么时候销毁等等

    2)性能很难比dispatch queue好,因为dispatch queue是基于XNU内核的workqueue实现的(中间还有一层封装是Libc中的pthread_workqueue)

    十五、dispatch source

    dispatch source是内核kqueue的封装。kqueue是一种多路复用技术,用于监听内核的各种事件通知,并做出相应处理。因为利用回调代替轮询,所以kqueue的CPU占用率很小。  

    // 创建source
    dispatch_source_t dispatch_source_create(dispatch_source_type_t type, uintptr_t handle, unsigned long mask, dispatch_queue_t queue);
    // 取消source,已经在执行的handler会继续执行完,还没执行的不再执行
    void dispatch_source_cancel(dispatch_source_t source); 
    // 事件source发生时,执行handler
    void dispatch_source_set_event_handler(dispatch_source_t source, dispatch_block_t handler);
    // 取消source时,执行handler
    void dispatch_source_set_cancel_handler(dispatch_source_t source, dispatch_block_t handler);
    // 启动source
    void dispatch_resume(dispatch_object_t object);
    

    十六、dispatch queue和block的持有关系

    block持有dispatch queue

    参考链接:

    https://developer.apple.com/library/archive/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html

  • 相关阅读:
    Martix工作室考核题 —— 打印一个菱形
    Martix工作室考核题 —— 打印一个菱形
    Martix工作室考核题 —— 打印九九乘法表
    Martix工作室考核题 —— 打印九九乘法表
    Martix工作室考核题 —— 打印九九乘法表
    Martix工作室考核题 —— 201938 第三题
    Martix工作室考核题 —— 201938 第三题
    Martix工作室考核题 —— 201938 第三题
    Martix工作室考核题 —— 201938 第一题
    fiddler模拟发送post请求
  • 原文地址:https://www.cnblogs.com/yangwenhuan/p/9439720.html
Copyright © 2011-2022 走看看