美文网首页
iOS GCD的一些理解

iOS GCD的一些理解

作者: 楼上那只猫 | 来源:发表于2020-03-29 16:50 被阅读0次
    void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block)
    

    Submits a block to the specified dispatch queue for synchronous execution. Unlike dispatch_async, this function does not return until the block has finished. Calling this function and targeting the current queue results in deadlock.
    Unlike with dispatch_async, no retain is performed on the target queue. Because calls to this function are synchronous, it "borrows" the reference of the caller. Moreover, no Block_copy is performed on the block.
    As a performance optimization, this function executes blocks on the current thread whenever possible, with one obvious exception. Specifically, blocks submitted to the main dispatch queue always run on the main thread.

    在主线程中,或者在主队列dispatch_get_main_queue()中,调用dispatch_async,那么,无论是将任务添加到串行队列还是并行队列,最终任务都是在主线程中执行。需要注意的是,在使用dispatch_sync的时候,不能将任务添加到调用所处的线程,否则会造成死锁。最典型的就是在主线程使用同步,并且添加在主队列。

    dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"22%@", [NSThread currentThread]);
        });
    NSLog(@"11");
    

    死锁原因:dispatch_sync阻塞了NSLog(@"11")的执行,因为是在主队列添加任务,主队列是串行队列,因此,block中的代码要等待外面主队列中其它代码执行完毕,但是因为dispatch_sync阻塞了主线程,需要等待block中任务执行完才行,这样导致block中任务等待主线程其余任务执行完才执行,主线程又等待block执行完才继续往下执行,互相等待,造成死锁。

    下面举几种可能的调用情况。

    1. 同步+串行
      在主线程中,使用同步方式,将任务添加到自定义的串行队列中。
    dispatch_sync(serialQ, ^{
        NSLog(@"33%@", [NSThread currentThread]);
    });
    NSLog(@"11");
    dispatch_sync(serialQ, ^{
        NSLog(@"44%@", [NSThread currentThread]);
    });
    

    输出顺序如下:

    2020-03-26 22:47:53.685918+0800 gcd[66502:747525] 33<NSThread: 0x600002970ac0>{number = 1, name = main}
    2020-03-26 22:47:53.686041+0800 gcd[66502:747525] 11
    2020-03-26 22:47:53.686158+0800 gcd[66502:747525] 44<NSThread: 0x600002970ac0>{number = 1, name = main}
    

    因为是同步,所以不会开辟新线程,且会阻塞后面代码的执行,所以会按照顺序执行。

    1. 主线程中 同步+并行
    dispatch_sync(concurrentQ, ^{
            NSLog(@"33%@", [NSThread currentThread]);
        });
        NSLog(@"11");
        dispatch_sync(concurrentQ, ^{
            NSLog(@"44%@", [NSThread currentThread]);
        });
    

    输出同1

    2020-03-26 22:47:53.685918+0800 gcd[66502:747525] 33<NSThread: 0x600002970ac0>{number = 1, name = main}
    2020-03-26 22:47:53.686041+0800 gcd[66502:747525] 11
    2020-03-26 22:47:53.686158+0800 gcd[66502:747525] 44<NSThread: 0x600002970ac0>{number = 1, name = main}
    
    1. 主线程 异步+串行
    dispatch_async(serialQ, ^{
            NSLog(@"33%@", [NSThread currentThread]);
        });
        NSLog(@"11");
        dispatch_async(serialQ, ^{
            NSLog(@"44%@", [NSThread currentThread]);
        });
    

    输出

    2020-03-26 23:01:31.000280+0800 gcd[69701:770474] 33<NSThread: 0x60000084d7c0>{number = 5, name = (null)}
    2020-03-26 23:01:31.000452+0800 gcd[69701:770222] 11
    2020-03-26 23:01:31.001008+0800 gcd[69701:770474] 44<NSThread: 0x60000084d7c0>{number = 5, name = (null)}
    

    因为异步,所以不会阻塞主线程后面代码执行,又因为是串行,33比44先加到队列,所以33总是在44之前输出。

    1. 异步+并行
    dispatch_async(concurrentQ, ^{
            NSLog(@"33%@", [NSThread currentThread]);
        });
        NSLog(@"11");
        dispatch_async(concurrentQ, ^{
            NSLog(@"44%@", [NSThread currentThread]);
        });
        dispatch_async(concurrentQ, ^{
            NSLog(@"55%@", [NSThread currentThread]);
        });
        dispatch_async(concurrentQ, ^{
            NSLog(@"66%@", [NSThread currentThread]);
        });
    

    因为是异步,所以不会阻塞后面代码执行,又因为是并行队列,添加多个任务,因此会开辟多条线程执行,输出顺序不定。11通常会比较早输出。

    2020-03-26 23:06:45.275300+0800 gcd[69799:777005] 11
    2020-03-26 23:06:45.275424+0800 gcd[69799:777164] 33<NSThread: 0x60000034dbc0>{number = 4, name = (null)}
    2020-03-26 23:06:45.275543+0800 gcd[69799:777162] 44<NSThread: 0x60000037a040>{number = 5, name = (null)}
    2020-03-26 23:06:45.275582+0800 gcd[69799:777163] 66<NSThread: 0x60000037f9c0>{number = 6, name = (null)}
    2020-03-26 23:06:45.275669+0800 gcd[69799:777164] 55<NSThread: 0x60000034dbc0>{number = 4, name = (null)}
    

    需要注意的是,并不是向并行队列添加几次任务就会创建几条线程,gcd有可能对线程重复利用。

    常用API

    dispatch_group_async

    作用:将一组任务关联到一个group,当一组任务执行完成后,最后执行一个指定的行为。

    dispatch_group_t group = dispatch_group_create();
        dispatch_group_async(group, concurrentQ, ^{
            NSLog(@"111 %@",[NSThread currentThread]);
        });
        dispatch_group_async(group, concurrentQ, ^{
            NSLog(@"333");
        });
        dispatch_group_async(group, concurrentQ, ^{
            NSLog(@"222");
        });
        dispatch_group_async(group, concurrentQ, ^{
            NSLog(@"444");
        });
        dispatch_group_notify(group, concurrentQ, ^{
            //上面的任务都执行完毕后,执行这个语句
            NSLog(@"finish");
        });
    

    输入如下:

    2020-03-29 15:03:21.251412+0800 gcd[65833:585440] 111 <NSThread: 0x600001189340>{number = 5, name = (null)}
    2020-03-29 15:03:21.251537+0800 gcd[65833:585440] 333
    2020-03-29 15:03:21.251617+0800 gcd[65833:585440] 222
    2020-03-29 15:03:21.251700+0800 gcd[65833:585440] 444
    2020-03-29 15:03:21.251897+0800 gcd[65833:585437] finish
    

    还有一种实现方式需要使用到下面两个api

    dispatch_group_enter(group);
    dispatch_group_leave(group);
    

    一个任务开始前执行enter,完成后执行leave,需要成对出现。

     dispatch_group_t group = dispatch_group_create();
        
        dispatch_group_enter(group);
        dispatch_async(concurrentQ, ^{
            dispatch_group_leave(group);
            NSLog(@"111 %@",[NSThread currentThread]);
        });
        
        dispatch_group_enter(group);
        dispatch_async(concurrentQ, ^{
            dispatch_group_leave(group);
               NSLog(@"222 %@",[NSThread currentThread]);
           });
        
        dispatch_group_enter(group);
        dispatch_async(concurrentQ, ^{
            dispatch_group_leave(group);
               NSLog(@"444 %@",[NSThread currentThread]);
           });
        
        dispatch_group_enter(group);
        dispatch_async(concurrentQ, ^{
            dispatch_group_leave(group);
               NSLog(@"999 %@",[NSThread currentThread]);
           });
        
        dispatch_group_enter(group);
        dispatch_async(concurrentQ, ^{
            dispatch_group_leave(group);
               NSLog(@"555 %@",[NSThread currentThread]);
           });
        
        dispatch_group_notify(group, concurrentQ, ^{
            //上面的任务都执行完毕后,执行这个语句
            NSLog(@"finish");
        });
    

    dispatch_barrier_sync和dispatch_barrier_async

    这两个函数的作用基本相同,都是起到任务分割的作用。

    dispatch_async(concurrentQ, ^{
            NSLog(@"44%@", [NSThread currentThread]);
        });
        
        dispatch_async(concurrentQ, ^{
            NSLog(@"55%@", [NSThread currentThread]);
        });
      //sync会阻塞后面代码的执行,必须先执行完barrie里的block才会执行后面的代码
        dispatch_barrier_sync(concurrentQ, ^{
            NSLog(@"-----");
        });
        NSLog(@"xxxxxxx");
        dispatch_async(concurrentQ, ^{
            NSLog(@"66%@", [NSThread currentThread]);
        });
    

    输出如下:

    2020-03-29 15:42:28.676543+0800 gcd[66313:617802] 33
    2020-03-29 15:42:28.676562+0800 gcd[66313:617795] 44
    2020-03-29 15:42:28.676566+0800 gcd[66313:617796] 55
    2020-03-29 15:42:28.676714+0800 gcd[66313:617538] -----
    2020-03-29 15:42:28.676785+0800 gcd[66313:617538] xxxxxxx
    2020-03-29 15:42:28.676872+0800 gcd[66313:617795] 66
    
    dispatch_async(concurrentQ, ^{
            NSLog(@"44%@", [NSThread currentThread]);
        });
        
        dispatch_async(concurrentQ, ^{
            NSLog(@"55%@", [NSThread currentThread]);
        });
        //不会阻塞后面代码的执行,xxx会在---之前输出。
        dispatch_barrier_async(concurrentQ, ^{
            NSLog(@"-----");
        });
        NSLog(@"xxxxxxx");
        dispatch_async(concurrentQ, ^{
            NSLog(@"66%@", [NSThread currentThread]);
        });
    

    输出如下:

    2020-03-29 15:43:00.968419+0800 gcd[66328:618700] 33
    2020-03-29 15:43:00.968419+0800 gcd[66328:618581] xxxxxxx
    2020-03-29 15:43:00.968466+0800 gcd[66328:618702] 55
    2020-03-29 15:43:00.968429+0800 gcd[66328:618698] 44
    2020-03-29 15:43:00.968574+0800 gcd[66328:618702] -----
    2020-03-29 15:43:00.968663+0800 gcd[66328:618702] 66
    

    dispatch_semaphore

    信号量,通常用来实现线程同步
    主要涉及3个函数

    //1是初始化指定的信号量值
    dispatch_semaphore_t sem = dispatch_semaphore_create(1);
    // 判断当前信号量的值,如果大于0,对当前信号量-1,然后执行后续代码,如果当前信号量为0,则阻塞当前线程。
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
    // 对当前信号量+1,如果信号量的值大于0,那么被dispatch_semaphore_wait阻塞的线程会继续执行。
    dispatch_semaphore_signal(sem);
    
    image.png image.png

    还有一个经典的使用场景就是售票

    func initTickets() {
            let queue1 = DispatchQueue(label: "com.song1")
            let queue2 = DispatchQueue(label: "com.song2")
            queue1.async { [weak self] in
                self?.saleTickets()
            }
            queue2.async { [weak self] in
                self?.saleTickets()
            }
        }
        
        func saleTickets() {
            while true {
                //每次进入时,先将信号量-1,如果<=0,说明当前有线程在访问,则暂时阻塞,直到当前线程执行完后才允许另一个售票请求进入
                semaphoreLock.wait()
                if tickets > 0 {
                    tickets -= 1
                    print("剩余\(tickets), 窗口:\(Thread.current)")
                    Thread.sleep(forTimeInterval: 1)
                } else {
                    print("无剩余")
                    semaphoreLock.signal()
                    break
                }
                semaphoreLock.signal()
            }
        }
    

    相关文章

      网友评论

          本文标题:iOS GCD的一些理解

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