OC-多线程GCD

参考:
GCD源码
深入理解 GCD
iOS多线程--彻底学会多线程之『GCD』
关于iOS多线程,我说,你听,没准你就懂了

任务执行方式(同步、异步)

一般理解

1、同步执行(dispatch_sync):只能在当前线程中执行任务,不具备开启新线程的能力。必须等到Block函数执行完毕后,dispatch函数才会返回。
2、异步执行(dispatch_async):可以在新的线程中执行任务,具备开启新线程的能力。dispatch函数会立即返回, 然后Block在后台异步执行。

高级理解

1、dispatch_sync 的实现略简单一些,它不涉及线程池(因此一般都在当前线程执行),而是利用与线程绑定的信号量来实现串行
2、dispatch_async 会把任务添加到队列的一个链表中,添加完后会唤醒队列,根据 vtable 中的函数指针,调用 wakeup 方法。在 wakeup 方法中,从线程池里取出工作线程(如果没有就新建),然后在工作线程中取出链表头部指向的 block 并执行。

任务管理方式(串行队列、并行队列)

1、串行队列:按照FIFO(先进先出)的原则,每次只能有一个任务执行,等待执行完毕后才会执行下一个任务
2、并行队列:按照FIFO(先进先出)的原则,把任务拿出来执行,不需要等待前一个任务执行情况。
外: dispatch_async 加入主队列的任务由 runloop 处理,加入其他队列由线程池处理

    // 主队列--串行,所有放在主队列中的任务,都会放到主线程中执行
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    
    // 全局队列--并行,系统为我们创建好的一个并行队列,使用起来与我们自己创建的并行队列无本质差别
    dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
    
    // new串行队列
    dispatch_queue_t queue1 = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
    
    // new并行队列
    dispatch_queue_t queue2 = dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);

注意:避免使用 GCD Global队列创建Runloop常驻线程
全局队列的底层是一个线程池(线程数有限),向全局队列中提交的 block,都会被放到这个线程池中执行,如果线程池已满,后续再提交 block 就不会再重新创建线程。等待有空闲的线程在执行任务。
所以:
避免使用 GCD Global 队列创建 Runloop 常驻线程,如果n条线程都被霸占了,Global队列就费了。

dispatch_group (组)

dispatch_group 的本质就是一个 value 非常大的信号量,等待 group 完成实际上就是等待 value 恢复初始值。而 notify 的作用是将所有注册的回调组装成一个链表,在 dispatch_async 完成时判断 value 是不是恢复初始值,如果是则调用 dispatch_async 异步执行所有注册的回调。

dispatch_once (单次)

dispatch_once 通过一个静态变量来标记 block 是否已被执行,同时使用信号量确保只有一个线程能执行,执行完 block 后会唤醒其他所有等待的线程

dispatch_barrier_async (栅栏)

dispatch_barrier_async 改变了 block 的 vtable 标记位,当它将要被取出执行时,会等待前面的 block 都执行完,然后在下一次循环中被执行

任务+队列

串行队列 并行队列 主队列
同步(sync) 当前线程,串行执行 队列当前线程,串行执行 主线程,串行执行(注意死锁)
异步(async) 开1条新线程,串行执行 开n条新线程,异步执行(n在iphone7上面最大是几十个) 主线程,串行执行
判断当前队列
    static char *queueKey = "queueKey";
    dispatch_queue_t queue1 = dispatch_queue_create(queueKey, nil);
    dispatch_queue_set_specific(queue, queueKey, &queueKey, NULL); // 设置标识
    dispatch_sync(queue1, ^{
        
        if (dispatch_get_specific(queueKey)) {
            //说明当前的队列就是queue1
        }else{
            //说明当前的队列不是是queue1
        }
    });
容易误解的概念

1、主线程只会执行主队列的任务--(主线程也可以执行其他队列,比如sync执行其他队列)

dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);

dispatch_queue_set_specific(mainQueue, "key", "main", NULL);
dispatch_sync(globalQueue, ^{
    BOOL res1 = [NSThread isMainThread];
    BOOL res2 = dispatch_get_specific("key") != NULL;
    
    NSLog(@"is main thread: %zd --- is main queue: %zd", res1, res2);
});

根据正常逻辑的理解来说,这里的两个判断结果应该都是NO,但运行后,第一个判断为YES,后者为NO,输出说明了主线程此时执行了work queue的任务.

Dispatch Block

队列执行任务都是block的方式,

创建block
- (void)createDispatchBlock {
    // 一般的block
    dispatch_queue_t concurrentQueue = dispatch_queue_create("com.starming.gcddemo.concurrentqueue",DISPATCH_QUEUE_CONCURRENT);
    dispatch_block_t block = dispatch_block_create(0, ^{
        NSLog(@"run block");
    });
    dispatch_async(concurrentQueue, block);

    //QOS优先级的block
    dispatch_block_t qosBlock = dispatch_block_create_with_qos_class(0, QOS_CLASS_USER_INITIATED, -1, ^{
        NSLog(@"run qos block");
    });
    dispatch_async(concurrentQueue, qosBlock);
}

dispatch_block_wait:可以根据dispatch block来设置等待时间,参数DISPATCH_TIME_FOREVER会一直等待block结束

- (void)dispatchBlockWaitDemo {
    dispatch_queue_t serialQueue = dispatch_queue_create("com.starming.gcddemo.serialqueue", DISPATCH_QUEUE_SERIAL);
    dispatch_block_t block = dispatch_block_create(0, ^{
        NSLog(@"star");
        [NSThread sleepForTimeInterval:5.f];
        NSLog(@"end");
    });
    dispatch_async(serialQueue, block);
    //设置DISPATCH_TIME_FOREVER会一直等到前面任务都完成
    dispatch_block_wait(block, DISPATCH_TIME_FOREVER);
    NSLog(@"ok, now can go on");
}

dispatch_block_notify:可以监视指定dispatch block结束,然后再加入一个block到队列中。三个参数分别为,第一个是需要监视的block,第二个参数是需要提交执行的队列,第三个是待加入到队列中的block

- (void)dispatchBlockNotifyDemo {
    dispatch_queue_t serialQueue = dispatch_queue_create("com.starming.gcddemo.serialqueue", DISPATCH_QUEUE_SERIAL);
    dispatch_block_t firstBlock = dispatch_block_create(0, ^{
        NSLog(@"first block start");
        [NSThread sleepForTimeInterval:2.f];
        NSLog(@"first block end");
    });
    dispatch_async(serialQueue, firstBlock);
    dispatch_block_t secondBlock = dispatch_block_create(0, ^{
        NSLog(@"second block run");
    });
    //first block执行完才在serial queue中执行second block
    dispatch_block_notify(firstBlock, serialQueue, secondBlock);
}

dispatch_block_cancel:iOS8之后可以调用dispatch_block_cancel来取消(需要注意必须用dispatch_block_create创建dispatch_block_t)
需要注意的是,未执行的可以用此方法cancel掉,若已经执行则cancel不了
如果想中断(interrupt)线程,可以使用dispatch_block_testcancel方法

- (void)dispatchBlockCancelDemo {
    dispatch_queue_t serialQueue = dispatch_queue_create("com.starming.gcddemo.serialqueue", DISPATCH_QUEUE_SERIAL);
    dispatch_block_t firstBlock = dispatch_block_create(0, ^{
        NSLog(@"first block start");
        [NSThread sleepForTimeInterval:2.f];
        NSLog(@"first block end");
    });
    dispatch_block_t secondBlock = dispatch_block_create(0, ^{
        NSLog(@"second block run");
    });
    dispatch_async(serialQueue, firstBlock);
    dispatch_async(serialQueue, secondBlock);
    //取消secondBlock
    dispatch_block_cancel(secondBlock);
}

1. 串行队列 + 同步执行

不会开启新线程,在当前线程执行任务。任务是串行的,执行完一个任务,再执行下一个任务

- (void)serialQueueSync{
    
    NSLog(@"0========%@",[NSThread currentThread]);
    dispatch_queue_t serialQueue = dispatch_queue_create("", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(serialQueue, ^{
        NSLog(@"1========%@",[NSThread currentThread]);
    });
    dispatch_sync(serialQueue, ^{
        NSLog(@"2========%@",[NSThread currentThread]);
    });
    dispatch_sync(serialQueue, ^{
        NSLog(@"3========%@",[NSThread currentThread]);
    });
    NSLog(@"4========%@",[NSThread currentThread]);
    
    /*
     NSLog输出
     0========<NSThread: 0x60800007f840>{number = 3, name = (null)}
     1========<NSThread: 0x60800007f840>{number = 3, name = (null)}
     2========<NSThread: 0x60800007f840>{number = 3, name = (null)}
     3========<NSThread: 0x60800007f840>{number = 3, name = (null)}
     4========<NSThread: 0x60800007f840>{number = 3, name = (null)}
     */
     
}
2. 串行队列 + 异步执行

开一个新线程,一个一个执行任务

    NSLog(@"0========%@",[NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        NSLog(@"1========%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"2========%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"3========%@",[NSThread currentThread]);
    });
    NSLog(@"4========%@",[NSThread currentThread]);
    
    /*
     NSLog输出
     0========<NSThread: 0x6000002616c0>{number = 1, name = main}
     4========<NSThread: 0x6000002616c0>{number = 1, name = main}
     1========<NSThread: 0x608000270540>{number = 3, name = (null)}
     2========<NSThread: 0x608000270540>{number = 3, name = (null)}
     3========<NSThread: 0x608000270540>{number = 3, name = (null)}
     */
3. 并行队列 + 同步执行

当前线程,一个一个执行任务

    NSLog(@"0========%@",[NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(queue, ^{
        NSLog(@"1========%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"2========%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"3========%@",[NSThread currentThread]);
    });
    NSLog(@"4========%@",[NSThread currentThread]);
    
    /*
     NSLog输出
     0========<NSThread: 0x60800007f840>{number = 3, name = (null)}
     1========<NSThread: 0x60800007f840>{number = 3, name = (null)}
     2========<NSThread: 0x60800007f840>{number = 3, name = (null)}
     3========<NSThread: 0x60800007f840>{number = 3, name = (null)}
     4========<NSThread: 0x60800007f840>{number = 3, name = (null)}
     */
4. 并行队列 + 异步执行

开多个线程,异步执行

    NSLog(@"0========%@",[NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"1========%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"2========%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"3========%@",[NSThread currentThread]);
    });
    NSLog(@"4========%@",[NSThread currentThread]);
    
    /*
     NSLog输出
     0========<NSThread: 0x608000070300>{number = 1, name = main}
     4========<NSThread: 0x608000070300>{number = 1, name = main}
     2========<NSThread: 0x608000264140>{number = 4, name = (null)}
     1========<NSThread: 0x60000007a800>{number = 3, name = (null)}
     3========<NSThread: 0x6080002642c0>{number = 5, name = (null)}
     */
5. 主队列 + 异步执行

主线程,同步执行

    NSLog(@"0========%@",[NSThread currentThread]);
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue, ^{
        NSLog(@"1========%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"2========%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"3========%@",[NSThread currentThread]);
    });
    NSLog(@"4========%@",[NSThread currentThread]);
    
    /*
     NSLog输出
     0========<NSThread: 0x60000026e000>{number = 3, name = (null)}
     4========<NSThread: 0x60000026e000>{number = 3, name = (null)}
     1========<NSThread: 0x60000007e2c0>{number = 1, name = main}
     2========<NSThread: 0x60000007e2c0>{number = 1, name = main}
     3========<NSThread: 0x60000007e2c0>{number = 1, name = main}
     */
6. 主队列 + 同步执行 (不能在主队列这么用,死锁)

主线程,同步执行

    NSLog(@"0========%@",[NSThread currentThread]);
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        NSLog(@"1========%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"2========%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"3========%@",[NSThread currentThread]);
    });
    NSLog(@"4========%@",[NSThread currentThread]);
    
    /*
     NSLog输出
     0========<NSThread: 0x600000263840>{number = 3, name = (null)}
     1========<NSThread: 0x608000078ec0>{number = 1, name = main}
     2========<NSThread: 0x608000078ec0>{number = 1, name = main}
     3========<NSThread: 0x608000078ec0>{number = 1, name = main}
     4========<NSThread: 0x600000263840>{number = 3, name = (null)}
     */

GCD其他用法

dispatch_after延时

1、time = 0,是直接调用异步dispatch_async
2、time > 0, 只是延时提交block,不是延时执行。

    //2秒延时、在主队列
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
        
    });
dispatch_once与dispatch_once_t
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //
    });

1、dispatch_once并不是简单的只执行一次那么简单
2、dispatch_once本质上可以接受多次请求,会对此维护一个请求链表
3、如果在block执行期间,多次进入调用同类的dispatch_once函数(即单例函数),会导致整体链表无限增长,造成永久性死锁。

递归互相嵌套,如下:

- (void)viewDidLoad {
    [super viewDidLoad];
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{ // 链表无限增长
        [self viewDidLoad];
    });
}

dispatch_once源码

static void
dispatch_once_f_slow(dispatch_once_t *val, void *ctxt, dispatch_function_t func)
{
#if DISPATCH_GATE_USE_FOR_DISPATCH_ONCE
    dispatch_once_gate_t l = (dispatch_once_gate_t)val;

    if (_dispatch_once_gate_tryenter(l)) {
        _dispatch_client_callout(ctxt, func);
        _dispatch_once_gate_broadcast(l);
    } else {
        _dispatch_once_gate_wait(l);
    }
#else
    _dispatch_once_waiter_t volatile *vval = (_dispatch_once_waiter_t*)val;
    struct _dispatch_once_waiter_s dow = { };
    _dispatch_once_waiter_t tail = &dow, next, tmp;
    dispatch_thread_event_t event;


    if (os_atomic_cmpxchg(vval, NULL, tail, acquire)) {
        
        // 第一次dispatch_once,原子性操作
        
        // 当前线程
        dow.dow_thread = _dispatch_tid_self();
        // 执行block
        _dispatch_client_callout(ctxt, func);

        // 第一次执行完了,设置token = DISPATCH_ONCE_DONE
        next = (_dispatch_once_waiter_t)_dispatch_once_xchg_done(val);
        while (next != tail) {
            
            // 继续去下一个
            tmp = (_dispatch_once_waiter_t)_dispatch_wait_until(next->dow_next);
            event = &next->dow_event;
            next = tmp;
            
            // 信号量++
            _dispatch_thread_event_signal(event);
        }
    } else {
        
        // 第二次dispatch_once进来
        _dispatch_thread_event_init(&dow.dow_event);
        next = *vval;
        for (;;) {
            if (next == DISPATCH_ONCE_DONE) { // token是否等于DISPATCH_ONCE_DONE
                // 第一次执行完之后,都是走这里
                break;
            }
            // 如果是嵌套使用,第一次没有完成,又要执行一次
            if (os_atomic_cmpxchgv(vval, next, tail, &next, release)) {
                // 原子性
                dow.dow_thread = next->dow_thread;
                dow.dow_next = next;
                if (dow.dow_thread) {
                    pthread_priority_t pp = _dispatch_get_priority();
                    _dispatch_thread_override_start(dow.dow_thread, pp, val);
                }
                // 等待信号量
                _dispatch_thread_event_wait(&dow.dow_event);
                if (dow.dow_thread) {
                    _dispatch_thread_override_end(dow.dow_thread, val);
                }
                break;
            }
        }
        _dispatch_thread_event_destroy(&dow.dow_event);
    }
#endif
}
dispatch_apply(count,queue,block(index))迭代方法

该函数按指定的次数将指定的block追加到指定的队列;使用的地方,阻塞当前线程

    NSLog(@"CurrentThread------%@", [NSThread currentThread]);
    //dispatch_queue_t queue = dispatch_get_global_queue(0,0);
    dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
    // 6是次数
    dispatch_apply(6, queue, ^(size_t index) {
        NSLog(@"%zd------%@",index, [NSThread currentThread]);
    });
    /*
     并发队列:开多线程异步执行
     NSLogx信息
     CurrentThread------<NSThread: 0x600000268a40>{number = 3, name = (null)}
     0------<NSThread: 0x600000268a40>{number = 3, name = (null)}
     1------<NSThread: 0x608000266e40>{number = 4, name = (null)}
     2------<NSThread: 0x608000266f00>{number = 5, name = (null)}
     3------<NSThread: 0x608000266f40>{number = 6, name = (null)}
     4------<NSThread: 0x600000268a40>{number = 3, name = (null)}
     5------<NSThread: 0x608000266e40>{number = 4, name = (null)}
     */
    
    
    /*
     同步队列:当前线程同步执行
     NSLogx信息
     CurrentThread------<NSThread: 0x608000072c00>{number = 3, name = (null)}
     0------<NSThread: 0x6000000694c0>{number = 1, name = main}
     1------<NSThread: 0x6000000694c0>{number = 1, name = main}
     2------<NSThread: 0x6000000694c0>{number = 1, name = main}
     3------<NSThread: 0x6000000694c0>{number = 1, name = main}
     4------<NSThread: 0x6000000694c0>{number = 1, name = main}
     5------<NSThread: 0x6000000694c0>{number = 1, name = main}
     */

dispatch_apply能避免线程爆炸,因为GCD会管理并发

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int i = 0; i < 999; i++){
      dispatch_async(queue, ^{
         NSLog(@"%d,%@",i,[NSThread currentThread]);// 能开多大线程就开多大线程(几十个)
      });
}
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(999, queue, ^(size_t i){
     NSLog(@"%d,%@",i,[NSThread currentThread]); // 只开一定数量的线程(几个)
});
dispatch_suspend、dispatch_resume (用在dispatch_get_global_queue主队列无效)

dispatch_suspend,dispatch_resume提供了“挂起、恢复”队列的功能,简单来说,就是可以暂停、恢复队列上的任务。但是这里的“挂起”,并不能保证可以立即停止队列上正在运行的block

注意点:

1、如果队列没有使用dispatch_suspend,使用dispatch_resume会crash
- (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT);
    dispatch_resume(queue); // crash
}
2、如果queue被挂起,queue销毁时候没有被唤醒,会crash
        dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT);
        dispatch_suspend(queue);// 如果queue被挂起,queue销毁时候没有被唤醒,会crash
3、dispatch_suspend后面执行dispatch_sync,阻塞当前线程,需要其他线程恢复队列
        queue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT);
        dispatch_suspend(queue);
        // 后面执行dispatch_sync,阻塞当前线程,需要其他线程恢复队列
        dispatch_sync(queue, ^{
            NSLog(@"=====%@",@"111111");
        });
        NSLog(@"=====%@",@"22222");

GCD的队列组dispatch_group_t,其实就是封装了一个无限大的信号量,

注意事项
1、dispatch_group_async(只有async,无sync)等价于{dispatch_group_enter() + async}, async调用完了会执行dispatch_group_leave()。
2、dispatch_group_enter()就是信号量--;
3、dispatch_group_leave()就是信号量++
4、dispatch_group_enter() 必须运行在 dispatch_group_leave() 之前。
5、dispatch_group_enter() 和 dispatch_group_leave() 需要成对出现的

    //1.创建队列组
    dispatch_group_t group = dispatch_group_create();

    //2.1.全局队列
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        for (NSInteger i = 0; i < 3; i++) {
            NSLog(@"group-01 - %@", [NSThread currentThread]);
        }
    });
    
    //2.2.主队列
    dispatch_group_async(group, dispatch_get_main_queue(), ^{
        for (NSInteger i = 0; i < 3; i++) {
            NSLog(@"group-02 - %@", [NSThread currentThread]);
        }
    });
   
    //2.3.自建串行队列
    dispatch_group_async(group, dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL), ^{
        for (NSInteger i = 0; i < 3; i++) {
            NSLog(@"group-03 - %@", [NSThread currentThread]);
        }
    });
    
    //3.都完成后会自动通知,不阻塞当前线程
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"完成 - %@", [NSThread currentThread]);
    });
    NSLog(@"当前线程=====%@",[NSThread currentThread]);
    
     /*
      并行队列、自建串行队列的任务多线程异步执行
      主队列的任务主线程同步执行,且排在全部任务的最后
      
      NSLog信息
      当前线程=====<NSThread: 0x60000007c240>{number = 1, name = main}
      group-01 - <NSThread: 0x60800026d180>{number = 3, name = (null)}
      group-01 - <NSThread: 0x60800026d180>{number = 3, name = (null)}
      group-03 - <NSThread: 0x60000026c7c0>{number = 4, name = (null)}
      group-01 - <NSThread: 0x60800026d180>{number = 3, name = (null)}
      group-03 - <NSThread: 0x60000026c7c0>{number = 4, name = (null)}
      group-03 - <NSThread: 0x60000026c7c0>{number = 4, name = (null)}
      group-02 - <NSThread: 0x60000007c240>{number = 1, name = main}
      group-02 - <NSThread: 0x60000007c240>{number = 1, name = main}
      group-02 - <NSThread: 0x60000007c240>{number = 1, name = main}
      完成 - <NSThread: 0x60000007c240>{number = 1, name = main}
      */
手动标记group完成
  • dispatch_group_enter(group)
  • dispatch_group_leave(group);
dispatch_group_notify(不阻塞)相当于把block任务加在最后
    NSLog(@"start");
    //1.创建队列组
    dispatch_group_t group = dispatch_group_create();
    for (int i=0; i< 5; i++) {
        dispatch_group_enter(group); // enter
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            // something
            NSLog(@"something===%zd",i);
            dispatch_group_leave(group); // eave
        });
    }
    // 都完成后会自动通知,不阻塞当前线程
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"完成 - %@", [NSThread currentThread]);
    });
    NSLog(@"end");

     /*
      NSLog信息:
      start
      end
      something===1
      something===0
      something===2
      something===3
      something===4
      完成 - <NSThread: 0x60800006e900>{number = 1, name = main}
      */
    

dispatch_group_wait就是等待group的信号量回到初始值(阻塞当前线程)
    NSLog(@"start");
    //1.创建队列组
    dispatch_group_t group = dispatch_group_create();
    
    for (int i=0; i< 5; i++) {
        
        dispatch_group_enter(group); // enter
        
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            
            // something
            
            [NSThread sleepForTimeInterval:2];
            NSLog(@"something===%zd",i);
            dispatch_group_leave(group); // eave
        });
        
    }
    // 阻塞当前线程的、等待5秒
    dispatch_time_t waitTime = dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC);
    dispatch_group_wait(group, waitTime);//特殊值有:DISPATCH_TIME_FOREVER是无限等,DISPATCH_TIME_NOW是不等
    NSLog(@"end");
    
    
     /*
      等待时间 < 执行需要时间
      NSLog信息:
      start
      end
      something===0
      something===1
      something===3
      something===2
      something===4
      */
    
    /*
     等待时间 > 执行需要时间
     NSLog信息:
     start
     something===1
     something===0
     something===4
     something===2
     something===3
     end
     */
dispatch_semaphore_create & dispatch_semaphore_signal & dispatch_semaphore_wait

信号量是控制任务执行的重要条件,当信号量为0时,所有任务等待,信号量越大,允许可并行执行的任务数量越多。

  • dispatch_semaphore_create(long value);创建信号量,初始值不能小于0;value信号数值
  • dispatch_semaphore_wait(semaphore, timeout);等待降低信号量,也就是信号量-1;timeout不是调用dispatch_semaphore_wait后等待的时间,而是信号量创建后的时间
  • dispatch_semaphore_signal(semaphore);提高信号量,也就是信号量+1;
  • dispatch_semaphore_wait和dispatch_semaphore_signal通常配对使用。
    // 相当于控制新建的线程数
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
    for (int i=0; i< 10; i++) {
        
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            
            [NSThread sleepForTimeInterval:1];
            NSLog(@"第%@次_%@",@(i),[NSThread currentThread]);
            dispatch_semaphore_signal(semaphore);
        });
        
    }
    
    
     /*
      dispatch_semaphore_t semaphore = dispatch_semaphore_create(5);
      只有5条线程
      NSLog信息:
      第0次_<NSThread: 0x608000268b00>{number = 4, name = (null)}
      第1次_<NSThread: 0x600000269240>{number = 6, name = (null)}
      第3次_<NSThread: 0x600000269100>{number = 5, name = (null)}
      第2次_<NSThread: 0x608000268ac0>{number = 3, name = (null)}
      第4次_<NSThread: 0x600000269780>{number = 7, name = (null)}
      第8次_<NSThread: 0x608000268ac0>{number = 3, name = (null)}
      第7次_<NSThread: 0x600000269100>{number = 5, name = (null)}
      第6次_<NSThread: 0x608000268b00>{number = 4, name = (null)}
      第5次_<NSThread: 0x600000269240>{number = 6, name = (null)}
      第9次_<NSThread: 0x600000269780>{number = 7, name = (null)}
      */
    
    /*
     dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
     10条线程
     NSLog信息:
     第2次_<NSThread: 0x6080000661c0>{number = 3, name = (null)}
     第4次_<NSThread: 0x608000073e40>{number = 7, name = (null)}
     第1次_<NSThread: 0x600000079dc0>{number = 4, name = (null)}
     第5次_<NSThread: 0x6000000721c0>{number = 8, name = (null)}
     第3次_<NSThread: 0x608000073dc0>{number = 6, name = (null)}
     第0次_<NSThread: 0x608000073d40>{number = 5, name = (null)}
     第6次_<NSThread: 0x608000073d80>{number = 9, name = (null)}
     第9次_<NSThread: 0x608000073e00>{number = 10, name = (null)}
     第7次_<NSThread: 0x6000000717c0>{number = 11, name = (null)}
     第8次_<NSThread: 0x600000066b40>{number = 12, name = (null)}
     */
dispatch_barrier_async、dispatch_barrier_sync (承上启下--用于自建的并行队列)

保证此前的任务都先于自己执行,此后的任务也迟于自己执行。
dispatch_barrier_async 不阻塞当前线程;
dispatch_barrier_sync 阻塞当前线程;

注意:dispatch_barrier_(a)sync只在自己创建的并发队列上有效,在全局(Global)并发队列、串行队列上,效果跟dispatch_(a)sync效果一样。

- (void)test{

    dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
    dispatch_async(globalQueue, ^{
        NSLog(@"任务1");
    });
    dispatch_async(globalQueue, ^{
        NSLog(@"任务2");
    });
    dispatch_barrier_async(globalQueue, ^{
        NSLog(@"任务barrier");
    });
    dispatch_async(globalQueue, ^{
        NSLog(@"任务3");
    });
    dispatch_async(globalQueue, ^{
        NSLog(@"任务4");
    });
    /*
     2017-09-02 21:03:40.255 NSThreadTest[28856:21431532] 任务2
     2017-09-02 21:03:40.255 NSThreadTest[28856:21431535] 任务1
     2017-09-02 21:03:40.255 NSThreadTest[28856:21431533] 任务barrier
     2017-09-02 21:03:40.255 NSThreadTest[28856:21431551] 任务3
     2017-09-02 21:03:40.256 NSThreadTest[28856:21431550] 任务4
     */
}

GCD创建Timer

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    //创建队列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    //1.创建一个GCD定时器
    /*
     第一个参数:表明创建的是一个定时器
     第四个参数:队列
     */
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    // 需要对timer进行强引用,保证其不会被释放掉,才会按时调用block块
    // 局部变量,让指针强引用
    self.timer = timer;
    //2.设置定时器的开始时间,间隔时间,精准度
    /*
     第1个参数:要给哪个定时器设置
     第2个参数:开始时间
     第3个参数:间隔时间
     第4个参数:精准度 一般为0 在允许范围内增加误差可提高程序的性能
     GCD的单位是纳秒 所以要*NSEC_PER_SEC
     */
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);

    //3.设置定时器要执行的事情
    dispatch_source_set_event_handler(timer, ^{
        NSLog(@"---%@--",[NSThread currentThread]);
    });
    // 启动
    dispatch_resume(timer);
}

GCD各种死锁的情况
1、最常见的(主线中+主队列+同步)
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSLog(@"任务1========%@",[NSThread currentThread]);// 当前是主线程、主队列
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"任务2========%@",[NSThread currentThread]);
    });
    NSLog(@"任务3========%@",[NSThread currentThread]);
    
    /*
     NSLog信息
    任务1========<NSThread: 0x60000007c5c0>{number = 1, name = main}
     
     解析
     1.viewDidLoad是主队列,dispatch_sync也属于主队列,
     2.dispatch_sync是viewDidLoad里面的代码,viewDidLoad需要等待dispatch_sync执行完,dispatch_sync需要等待viewDidLoad执行完,这就死锁了
     */
    
}
主线程中:主队列+同步
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSLog(@"0========%@",[NSThread currentThread]);// 当前是主线程、主队列
    // 改成dispatch_get_global_queue或者new出来的队列
    dispatch_sync(dispatch_get_global_queue(0,0), ^{
        NSLog(@"1========%@",[NSThread currentThread]);
    });
    NSLog(@"2========%@",[NSThread currentThread]);
    
    /*
     NSLog信息
     0========<NSThread: 0x6000000690c0>{number = 1, name = main}
     1========<NSThread: 0x6000000690c0>{number = 1, name = main}
     2========<NSThread: 0x6000000690c0>{number = 1, name = main}
     
     解析
     1.viewDidLoad是主队列,dispatch_sync是global_queue,不在同一队列
     2.dispatch_sync是viewDidLoad里面的代码,viewDidLoad需要等待dispatch_sync执行完返回,但是dispatch_sync不需要等待viewDidLoad执行完,立即执行完返回
     */
    
}
2、串行队列,各种嵌套异步情况

死锁的原因:是同一个串行队列任务内部代码继续嵌套同步sync的任务

        // 串行队列
    dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_SERIAL);
    
    // 同步嵌异步----执行OK
    dispatch_sync(queue, ^{
        dispatch_async(queue, ^{
        });
    });
    
    // 异步嵌异步----执行OK
    dispatch_async(queue, ^{
        dispatch_async(queue, ^{
        });
    });
    
    // 异步嵌同步----死锁
    dispatch_async(queue, ^{
        dispatch_sync(queue, ^{
        });
    });
    
    // 同步嵌同步----死锁
    dispatch_sync(queue, ^{
        dispatch_sync(queue, ^{
        });
    });

3、并行队列,各种嵌套异步情况

并行 队列各种嵌套都不会死锁

    // 并行队列
    dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT);
    
    // 同步嵌异步----执行OK
    dispatch_sync(queue, ^{
        dispatch_async(queue, ^{
        });
    });
    
    // 异步嵌异步----执行OK
    dispatch_async(queue, ^{
        dispatch_async(queue, ^{
        });
    });
    
    // 异步嵌同步----执行OK
    dispatch_async(queue, ^{
        dispatch_sync(queue, ^{
        });
    });
    
    // 同步嵌同步----执行OK
    dispatch_sync(queue, ^{
        dispatch_sync(queue, ^{
        });
    });
4、dispatch_apply阻塞当前线程
// 主队列使用,死锁
dispatch_apply(5, dispatch_get_main_queue(), ^(size_t i) {
        NSLog(@"第%@次_%@",@(i),[NSThread currentThread]);
    });
// 嵌套使用,死锁
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(10, queue, ^(size_t) {
    // 任务
    ...
    dispatch_apply(10, queue, ^(size_t) {
        // 任务
        ...
    });
});

dispatch_barrier
dispatch_barrier_sync在串行队列和全局并行队列里面和dispatch_sync同样的效果,所以需考虑同dispatch_sync一样的死锁问题。

5、 信号量阻塞主线程
- (void)viewDidLoad {
    [super viewDidLoad];

    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    NSLog(@"semaphore create!");
    dispatch_async(dispatch_get_main_queue(), ^{
        dispatch_semaphore_signal(semaphore);
        NSLog(@"semaphore plus 1");
    });
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"semaphore minus 1");
}

原因:
如果当前执行的线程是主线程,以上代码就会出现死锁。
因为dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)阻塞了当前线程,而且等待时间是DISPATCH_TIME_FOREVER——永远等待,这样它就永远的阻塞了当前线程——主线程。导致主线中的dispatch_semaphore_signal(semaphore)没有执行,
而dispatch_semaphore_wait一直在等待dispatch_semaphore_signal改变信号量,这样就形成了死锁。

解决方法:
应该将信号量移到并行队列中,如全局调度队列。以下场景,移到串行队列也是可以的。但是串行队列还是有可能死锁的(如果执行dispatch_semaphore_signal方法还是在对应串行队列中的话,即之前提到的串行队列嵌套串行队列的场景)。

- (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
        NSLog(@"semaphore create!");
        dispatch_async(dispatch_get_main_queue(), ^{
            dispatch_semaphore_signal(semaphore); // +1
            NSLog(@"semaphore plus 1");
        });
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"semaphore minus 1");
    });
}
一些嵌套使用问题
    NSLog(@"1");
    dispatch_sync(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"2");
        dispatch_async(dispatch_get_main_queue(), ^{
           sleep(1);
            NSLog(@"3");
        });
    });
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"4");
    });
    NSLog(@"5");

    //结果是:12534
    //解析:“2”并发队列同步任务,所以125;“3”、“4”是两个主队列异步,串行执行任务34;最终就是12534
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_sync(queue, ^{
        
        dispatch_sync(queue, ^{
            NSLog(@"B");
        });
        NSLog(@"A");
    });
    // 结果是:BA (并发队列不会死锁) 并行队列,任务A加入队列执行中,然后任务B加入队列也立即执行,但是任务A会等任务B先执行完。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352

推荐阅读更多精彩内容