笔记整理:GCD

2021-06-08  本文已影响0人  双鱼子曰1987

一、概述

GCD是用纯C编写,效率很高;其内部自动维护一个线程池,自动管理线程的生命周期;其会利用CPU的多核特性。


image.png

二、GCD 基础

1、GCD使用的核心是队列Queue 和 任务Block;

2、组dispatch_group —— 针对的是队列queue 和 任务task

相当于在队列中设置一个里程碑任务,这个里程碑任务 必须等到队列中其他任务都完成,才会被触发。

dispatch_group_t group =  dispatch_group_create();
dispatch_group_notify(group, queue, ^{
    // 里程碑任务
});

注意:wait方法,其阻塞当前线程

dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
执行,里程碑任务
dispatch_group_async(group, queue, ^{
    // 追加任务 
});

手动控制Group的进入和退出(enter和leave必须配对)

// 进入group
dispatch_group_enter(group);
dispatch_async(queue, ^{
    // 追加任务 1

    // 当前任务退出group
    dispatch_group_leave(group);
});

3、栅栏dispatch_barrier_async —— 针对的是队列queue

以栅栏函数调用为分界点,调用之前加入队列的任务组先执行,然后执行栅栏任务,最后执行调用之后加入队列的任务组。

- (void)barrier {
    dispatch_queue_t queue = dispatch_queue_create("xxx", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        // 追加任务 1
    });

    dispatch_async(queue, ^{
        // 追加任务 2
    });
    
    dispatch_barrier_async(queue, ^{
        // 追加任务 barrier
    });
    
    dispatch_async(queue, ^{
        // 追加任务 3
    });
    dispatch_async(queue, ^{
        // 追加任务 4
    });
}

4、dispatch_semaphore

主要提供三个方法
dispatch_semaphore_create(counts) 创建信号量,初始信号总量
dispatch_semaphore_signal(semp) 信号量+1
dispatch_semaphore_wait(semp, time) 信号量为0则阻塞线程,大于0则不会阻塞。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    
dispatch_async(queue, ^{
    // 任务
    dispatch_semaphore_signal(semaphore);
});
    
// 阻塞线程,且只有执行完任务后,才会继续往下
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

5、GCD定时器 - dispatch source

dispatch_source_t CreateDispatchTimer(uint64_t interval, uint64_t leeway, dispatch_queue_t queue, dispatch_block_t block) {
     // 创建source
     dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,
       0, 0, queue);
     if (timer) {
        // 设置定时器的定时间隔、开始时间
        dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), interval, leeway);
        // 设置定时器的执行handler
        dispatch_source_set_event_handler(timer, block);
        // 唤起定时器
        dispatch_resume(timer);
     }
     return timer;
}
dispatch_suspend(_timer);
_timer = nil; // 崩溃
dispatch_source_cancel(_timer);
_timer = nil; // OK

6、其他常用接口

dispatch_apply(runCounts, queue, ^(size_t index) {
        NSLog(@"%zd---%@",index, [NSThread currentThread]);
});

三、队列相关配置

1、dispatch_queue_set_specific,把任意数据以键值对的形式关联到队列中

void dispatch_queue_set_specific(
    dispatch_queue_t queue, //待设置标记的队列
    const void *key, //标记的键
    void *context, //标记的值。注意,这里键和值是指针,即地址,故context中可以放任何数据,但必须手动管理context的内存
    dispatch_function_t destructor //析构函数,但所在队列内存被回收,或者context值改变时,会被调用
);
dispatch_queue_t queueA = dispatch_queue_create("queueA", NULL);
dispatch_queue_t queueB = dispatch_queue_create("queueB", NULL);
dispatch_set_target_queue(queueB, queueA);
 
static int kQueueSpecific;
CFStringRef queueSpecificValue = CGSTR("queueA"); 
//这里使用CoreFoundation字符串,是因为ARC不会自动管理CoreFoundation对象的内存,dispatch_queue_set_specific的第三个参数(值)需要手动管理内存
 
dispatch_queue_set_specific(
    queueA, 
    &kQueueSpecific, 
    (void *)queueSpecificValue,  //需要手动管理内存
    (dispatch_function_t)CFRelease //用CFRelease清理旧值
); //给queueA队列做标记
 
dispatch_sync(queueB, ^{
    dispatch_block_t block = ^{ NSLog("No deadlock!"); };
    CFStringRef retrievedValue = dispatch_get_specific(&kQueueSpecific); //根据键获取值
    if(retrievedValue){ //根据键找到了值,就说明包含在queueA目标队列中
        block();
    }else{ //没有包含在queueA中
        dispatch_sync(queueA, block);
    }
})

2、获取当前queue的标签 dispatch_queue_get_label(queue),一般用来判断两个队列是否相同。

// 判断当前队列是否相等
if (dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL) == dispatch_queue_get_label(queue)) {
    ...
}

3、dispatch_set_target_queue

* @discussion
 * An object's target queue is responsible for processing the object. 
 *
 * When no quality of service class and relative priority is specified for a
 * dispatch queue at the time of creation, a dispatch queue's quality of service
 * class is inherited from its target queue. The dispatch_get_global_queue()
 * function may be used to obtain a target queue of a specific quality of
 * service class, however the use of dispatch_queue_attr_make_with_qos_class()
 * is recommended instead.
 *
 * Blocks submitted to a serial queue whose target queue is another serial
 * queue will not be invoked concurrently with blocks submitted to the target
 * queue or to any other queue with that same target queue.
 *
 * The result of introducing a cycle into the hierarchy of target queues is
 * undefined.
 *
 * A dispatch source's target queue specifies where its event handler and
 * cancellation handler blocks will be submitted.
 *
 * A dispatch I/O channel's target queue specifies where where its I/O
 * operations are executed. If the channel's target queue's priority is set to
 * DISPATCH_QUEUE_PRIORITY_BACKGROUND, then the I/O operations performed by
 * dispatch_io_read() or dispatch_io_write() on that queue will be
 * throttled when there is I/O contention.
 *
 * For all other dispatch object types, the only function of the target queue
 * is to determine where an object's finalizer function is invoked.
 *
 * In general, changing the target queue of an object is an asynchronous
 * operation that doesn't take effect immediately, and doesn't affect blocks
 * already associated with the specified object.
 *
 * However, if an object is inactive at the time dispatch_set_target_queue() is
 * called, then the target queue change takes effect immediately, and will
 * affect blocks already associated with the specified object. After an
 * initially inactive object has been activated, calling
 * dispatch_set_target_queue() results in an assertion and the process being
 * terminated.
 *
 * If a dispatch queue is active and targeted by other dispatch objects,
 * changing its target queue results in undefined behavior.
 *
 *
 * @param object
 * The object to modify.
 * The result of passing NULL in this parameter is undefined.
 *
 * @param queue
 * The new target queue for the object. The queue is retained, and the  
 * previous target queue, if any, is released. 
 * If queue is DISPATCH_TARGET_QUEUE_DEFAULT, set the object's target queue
 * to the default target queue for the given object type.
void
dispatch_set_target_queue(dispatch_object_t object,
        dispatch_queue_t _Nullable queue);

应用1:改变queue的优先级与目标queue相同

dispatch_queue_t serialQueue = dispatch_queue_create("com.oukavip.www",NULL);
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0);
 
// 第一个参数为要设置优先级的queue,第二个参数queue是参照物,
// 既将第一个queue的优先级和第二个queue的优先级设置一样。
dispatch_set_target_queue(serialQueue, globalQueue);

应用2:
一般都是把一个任务放到一个串行的queue中,如果这个任务被拆分了,被放置到多个串行的queue中,但实际还是需要这个任务同步执行,那么就会有问题,因为多个串行queue之间是并行的。

那该如何是好呢?就可以使用dispatch_set_target_queue了。

如果将多个串行的queue使用dispatch_set_target_queue指定到了同一目标,那么多个串行queue在目标queue上就是串行执行的,不再是并行执行。

dispatch_get_current_queue可能导致死锁

dispatch_queue_t targetQueue = dispatch_queue_create("test.target.queue", DISPATCH_QUEUE_SERIAL);
    
dispatch_queue_t queue1 = dispatch_queue_create("test.1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("test.2", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue3 = dispatch_queue_create("test.3", DISPATCH_QUEUE_SERIAL);

dispatch_set_target_queue(queue1, targetQueue);
dispatch_set_target_queue(queue2, targetQueue);
dispatch_set_target_queue(queue3, targetQueue);


dispatch_async(queue1, ^{
    NSLog(@"1 in");
    [NSThread sleepForTimeInterval:3.f];
    NSLog(@"1 out");
});

dispatch_async(queue2, ^{
    NSLog(@"2 in");
    [NSThread sleepForTimeInterval:2.f];
    NSLog(@"2 out");
});
dispatch_async(queue3, ^{
    NSLog(@"3 in");
    [NSThread sleepForTimeInterval:1.f];
    NSLog(@"3 out");
});

// 1 in 1 out  , 2 in 2 out , 3in 3 out

4、dispatch_suspenddispatch_resume

简单理解,就是可以暂停、恢复队列上的任务。
需要注意的是,针对的是那些已经放入队列中,但是还没有被执行的任务,已经正在执行的会继续执行。


GCD注意事项:

造成死锁的情况:

dispatch_queue_t serialQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
    // task 1
    dispatch_sync(serialQueue, ^{
        // task 2
    });    
});  
dispatch_queue_t serialQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
dispatch_sync(serialQueue, ^{
    // task 1
    dispatch_sync(serialQueue, ^{
        // task 2
    });    
});  
dispatch_sync(dispatch_get_main_queue(), ^{
    // block
});    

安全的主线程判断:

+ (BOOL)isMainQueue {
    static const void* mainQueueKey = @"mainQueue";
    static void* mainQueueContext = @"mainQueue";
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        dispatch_queue_set_specific(dispatch_get_main_queue(), mainQueueKey, mainQueueContext, nil);
    });
    return dispatch_get_specific(mainQueueKey) == mainQueueContext;
}

主线程中也不绝对安全的 UI 操作


iOS 多线程:『GCD』详尽总结
深入理解 GCD
Concurrent Programming: APIs and Challenges
深入理解GCD之dispatch_queue

上一篇 下一篇

猜你喜欢

热点阅读