美文网首页
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的一些理解

    Submits a block to the specified dispatch queue for synch...

  • iOS 开发之 GCD 不同场景使用

    iOS 开发之 GCD 不同场景使用 本文在iOS 开发值 GCD 基础 的基础上,继续总结了 GCD 的一些AP...

  • (链接)GCD

    一 通过GCD中的dispatch_barrier_(a)sync所谓等待的理解 二 iOS-GCD之初,disP...

  • 理解GCD

    (1)博客:深入理解GCD 理解iOS中的线程池 多线程理解 ?:(1)信号量--...

  • iOS GCD理解

    mark一下: http://www.cnblogs.com/wendingding/p/3806821.html

  • GCD

    iOS多线程 Swift4 GCD深入解析swift GCD 的一些高级用法GCD 之线程组(Dispatch G...

  • 1.多线程编程

    参考:Objective-C高级编程 深入浅出 iOS 并发编程 GCD 深入理解:第一部分 GCD 深入理解:第...

  • iOS开发多线程之GCD

    iOS开发多线程之GCDiOS开发之GCD同步任务加强iOS开发之GCD串行队列iOS开发之GCD并发队列 GCD...

  • [iOS 多线程] iOS多线程-GCD

    iOS多线程-GCD GCD的简介 GCD,全称为 Grand Central Dispatch ,是iOS用来管...

  • 2016.9第二周

    博客深入理解 GCD - IOS - 伯乐在线(iOS)模仿斗鱼的部分界面介绍一(部分使用RxSwiftzen/m...

网友评论

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

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