GCD

# GCD优点

1.GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程)

2.GCD更接近底层,性能较高

3.GCD 会自动利用更多的 CPU 内核(比如双核、四核),GCD 可用于多核的并行运算;

4.程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码。

## GCD 任务

任务:就是执行操作的意思,换句话说就是你在线程中执行的那段代码。在 GCD 中是放在 block 中的。

执行任务有两种方式:『同步执行』 和 『异步执行』。

两者的主要区别是:是否等待队列的任务执行结束,以及是否具备开启新线程的能力。

1.同步执行(sync):

同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行。

只能在当前线程中执行任务,不具备开启新线程的能力。

2.异步执行(async):

异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务。

可以在新的线程中执行任务,具备开启新线程的能力。

## 队列(Dispatch Queue)

队列是指执行任务的等待队列,即用来存放任务的队列。队列遵循FIFO(先进先出),新任务总是插到队列的末尾,读取任务总是从队列的头部开始读取,没读取一个任务,就会释放一个任务。GCD中,有两种队列:串行队列,并发队列,两者都遵循FIFO原则。

在 GCD 中有两种队列:『串行队列』 和 『并发队列』。两者都符合 FIFO(先进先出)的原则。两者的主要区别是:执行顺序不同,以及开启线程数不同。

1.串行队列(Serial Dispatch Queue)

每次只有一个任务被执行。让任务一个接着一个地执行。(只开启一个线程,一个任务执行完毕后,再执行下一个任务)

2.并发队列(Concurrent Dispatch Queue):

可以让多个任务并发(同时)执行。(可以开启多个线程,并且同时执行任务)

注意:并发队列 的并发功能只有在异步(dispatch_async)方法下才有效。

## GCD 的使用步骤

2.创建一个队列(串行队列或并发队列);

2.将任务追加到任务的等待队列中,然后系统就会根据任务类型执行任务(同步执行或异步执行)。

## 队列的创建方法 / 获取方法

可以使用 dispatch_queue_create 方法来创建队列。

// 串行队列的创建方法

dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);

// 并发队列的创建方法

dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);

// 主队列的获取方法

dispatch_queue_t queue = dispatch_get_main_queue();

// 全局并发队列的获取方法

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

## 任务的创建方法

GCD 提供了同步执行任务的创建方法 dispatch_sync 和异步执行任务创建方法 dispatch_async。

// 同步执行任务创建方法

dispatch_sync(queue, ^{

    // 这里放同步执行任务代码

});

// 异步执行任务创建方法

dispatch_async(queue, ^{

    // 这里放异步执行任务代码

});

组合方法

同步执行 + 并发队列

异步执行 + 并发队列

同步执行 + 串行队列

异步执行 + 串行队列

同步执行 + 主队列

异步执行 + 主队列

## GCD的使用

1.同步+并发

/**

 同步并发执行

 在当前线程执行任务,不会开启新线程,执行完一个任务,在执行下一个任务

 */

-(void)syncWithConcurrent{

    NSLog(@"currentThread -- %@", [NSThread currentThread]);


    dispatch_queue_t queueu = dispatch_queue_create("com.easyfly.queue", DISPATCH_QUEUE_CONCURRENT);


    dispatch_sync(queueu, ^{

        for (int i = 0; i < 2; i++) {

            [NSThread sleepForTimeInterval:1];

            NSLog(@"111----%@", [NSThread currentThread]);

        }

    });

    //追加任务

    dispatch_sync(queueu, ^{

        for (int i = 0; i < 2; i++) {

            [NSThread sleepForTimeInterval:1];

            NSLog(@"222----%@", [NSThread currentThread]);

        }

    });


    dispatch_sync(queueu, ^{

        for (int i = 0; i < 2; i++) {

            [NSThread sleepForTimeInterval:1];

            NSLog(@"333----%@", [NSThread currentThread]);

        }

    });

}

2018-05-16 14:38:55.788806+0800 MultiThread[5657:399983] currentThread -- <NSThread: 0x60400006ca80>{number = 1, name = main}

2018-05-16 14:38:55.789158+0800 MultiThread[5657:399983] begin

2018-05-16 14:38:56.790163+0800 MultiThread[5657:399983] 111----<NSThread: 0x60400006ca80>{number = 1, name = main}

2018-05-16 14:38:57.790860+0800 MultiThread[5657:399983] 111----<NSThread: 0x60400006ca80>{number = 1, name = main}

2018-05-16 14:38:58.791470+0800 MultiThread[5657:399983] 222----<NSThread: 0x60400006ca80>{number = 1, name = main}

2018-05-16 14:38:59.794281+0800 MultiThread[5657:399983] 222----<NSThread: 0x60400006ca80>{number = 1, name = main}

2018-05-16 14:39:00.795110+0800 MultiThread[5657:399983] 333----<NSThread: 0x60400006ca80>{number = 1, name = main}

2018-05-16 14:39:01.795762+0800 MultiThread[5657:399983] 333----<NSThread: 0x60400006ca80>{number = 1, name = main}

2018-05-16 14:39:01.796005+0800 MultiThread[5657:399983] end

所有的任务都是在当前线程(主线程)中执行的。并没有开启新线程。

按顺序执行任务。并发队列虽然可以开启多个线程,同时执行任务,但是不能创建新线程。所以并发并不存在,实际效果相当于串行。

2.异步 + 并发

/**

 可以开启多个线程,交替执行任务

 */

-(void)asyncConcurrent{

    NSLog(@"currentThread -- %@", [NSThread currentThread]);


    NSLog(@"begin");


    dispatch_queue_t queueu = dispatch_queue_create("com.easyfly.queue", DISPATCH_QUEUE_CONCURRENT);


    dispatch_async(queueu, ^{

        for (int i = 0; i < 2; i++) {

            [NSThread sleepForTimeInterval:1];

            NSLog(@"111----%@", [NSThread currentThread]);

        }

    });

    //追加任务

    dispatch_async(queueu, ^{

        for (int i = 0; i < 2; i++) {

            [NSThread sleepForTimeInterval:1];

            NSLog(@"222----%@", [NSThread currentThread]);

        }

    });


    dispatch_async(queueu, ^{

        for (int i = 0; i < 2; i++) {

            [NSThread sleepForTimeInterval:1];

            NSLog(@"333----%@", [NSThread currentThread]);

        }

    });


    NSLog(@"end");

}

2018-05-16 14:45:25.548159+0800 MultiThread[5724:408572] currentThread -- <NSThread: 0x604000068c40>{number = 1, name = main}

2018-05-16 14:45:25.548681+0800 MultiThread[5724:408572] begin

2018-05-16 14:45:25.548907+0800 MultiThread[5724:408572] end

2018-05-16 14:45:26.549549+0800 MultiThread[5724:408638] 111----<NSThread: 0x60000007a8c0>{number = 5, name = (null)}

2018-05-16 14:45:26.549660+0800 MultiThread[5724:408804] 333----<NSThread: 0x600000263d80>{number = 7, name = (null)}

2018-05-16 14:45:26.549679+0800 MultiThread[5724:408803] 222----<NSThread: 0x600000263380>{number = 6, name = (null)}

2018-05-16 14:45:27.550524+0800 MultiThread[5724:408803] 222----<NSThread: 0x600000263380>{number = 6, name = (null)}

2018-05-16 14:45:27.550526+0800 MultiThread[5724:408804] 333----<NSThread: 0x600000263d80>{number = 7, name = (null)}

2018-05-16 14:45:27.550526+0800 MultiThread[5724:408638] 111----<NSThread: 0x60000007a8c0>{number = 5, name = (null)}

除了当前线程(主线程),系统有开启了三个线程,任务交替执行。

begin和end先打印了,说明线程并没有等待,开启了新线程,在新线程中执行任务。

5.同步 + 主队列

/**

 卡死

 */

-(void)syncMain{

    NSLog(@"currentThread -- %@", [NSThread currentThread]);


    NSLog(@"begin");


    dispatch_queue_t queueu = dispatch_get_main_queue();


    dispatch_sync(queueu, ^{

        for (int i = 0; i < 2; i++) {

            [NSThread sleepForTimeInterval:1];

            NSLog(@"111----%@", [NSThread currentThread]);

        }

    });

    //追加任务

    dispatch_sync(queueu, ^{

        for (int i = 0; i < 2; i++) {

            [NSThread sleepForTimeInterval:1];

            NSLog(@"222----%@", [NSThread currentThread]);

        }

    });


    dispatch_sync(queueu, ^{

        for (int i = 0; i < 2; i++) {

            [NSThread sleepForTimeInterval:1];

            NSLog(@"333----%@", [NSThread currentThread]);

        }

    });


    NSLog(@"end");

}

我们将syncMain()方法放到主线程的队列中执行,而syncMain()方法中,又在主线程中追加了打印任务。并发情况下,syncMain()在等值打印任务完成,而打印任务又在等着syncMain()的完成,相互等待,导致卡死。

## GCD 线程间的通信

在 iOS 开发过程中,我们一般在主线程里边进行 UI 刷新,例如:点击、滚动、拖拽等事件。我们通常把一些耗时的操作放在其他线程,比如说图片下载、文件上传等耗时操作。而当我们有时候在其他线程完成了耗时操作时,需要回到主线程,那么就用到了线程之间的通讯。

/**

 *线程间通信

 */

-(void)communication {

    // 获取全局并发队列

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    // 获取主队列

    dispatch_queue_t mainQueue = dispatch_get_main_queue();


    dispatch_async(queue, ^{

        // 异步追加任务 1

        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作

        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程


        // 回到主线程

        dispatch_async(mainQueue, ^{

            // 追加在主线程中执行的任务

            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作

            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程

        });

    });

}

输出结果:

2019-08-08 14:56:22.973318+0800 YSC-GCD-demo[17573:4253201] 1---<NSThread: 0x600001846080>{number = 3, name = (null)}

2019-08-08 14:56:24.973902+0800 YSC-GCD-demo[17573:4253108] 2---<NSThread: 0x60000181e940>{number = 1, name = main}

可以看到在其他线程中先执行任务,执行完了之后回到主线程执行主线程的相应操作。

## GCD的其他方法

1.栅栏方法(dispatch_barrier_async)

如果我们需要实现异步请求两个数据,同时B请求必须在A请求完成后才能继续请求,这就需要一种手段分割开A和B请求,这就是栅栏方法

2.延迟执行方法(dispatch_after)

/**

 *延时执行方法 dispatch_after

 */

-(void)after {

    NSLog(@"currentThread---%@", [NSThread currentThread]);

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

        NSLog(@"after---%@", [NSThread currentThread]);

    });

}

3.GCD一次性代码(dispatch_once)

-(void)once {

    static dispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{

        // 这里的代码指挥执行一次

    });

}

4.循环遍历方法(dispatch_apply)

dispatch_apply方法会开启多个线程来异步遍历数据,无论串行队列,还是并发队列,该方法都会等待全部任务完毕,有点像同步操作。

-(void)apply {

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);


    NSLog(@"apply---begin");

    dispatch_apply(5, queue, ^(size_t index) {

        NSLog(@"%zd---%@",index, [NSThread currentThread]);

    });

    NSLog(@"apply---end");

}

5.GCD队列组(dispatch_group)

有时候会遇到这种情况,要求异步执行两个耗时任务,然后当两个任务完成后调回带主线程,执行主线程的任务。dispatch_group可以把相关的任务归并到一个组内来执行,通过监听组内所有任务的执行情况来做相应处理。

6.GCD信号量(dispatch_semaphore)

dispatch_semaphore是GCD中的信号量,可以处理多线程中线程并发的问题,也可以用作同步处理。

1.创建信号量,里面的参数是表示信号的总量,值必须>=0

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

2.发送一个信号,信号量的总数会+1

dispatch_semaphore_signal(semaphore);

信号等待,当信号量的总数<=0的时候,会一直等待,直到信号量的总数>0的时候才会继续下面的执行

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

注意:dispatch_semaphore_wait

当信号量的总数<=0时候,该函数所在的线程就会等待,而信号量的总数>0的时候,该函数就会继续往下执行,同时信号量的总数-1

这里有个等待时间的参数,如果在等待的时间内获得了信号量,那么函数继续往下执行,如果等待时间内信号量一直为0,那么函数也会继续往下执行了

/**

 模拟两个窗口售票

 */

-(void)initTickets{


    self.semaphoreTicket = dispatch_semaphore_create(1);

    self.ticketCount = 10;


    dispatch_queue_t queueOne = dispatch_queue_create("com.easyFly.one", 0);

    dispatch_queue_t queueTwo = dispatch_queue_create("com.easyFly.Two", 0);


    __weak typeof(self) weakSelf = self;


    dispatch_async(queueOne, ^{

        [weakSelf saleTicket];

    });

    dispatch_async(queueTwo, ^{

        [weakSelf saleTicket];

    });


}

-(void)saleTicket{

    while (true) {

//        dispatch_semaphore_wait(_semaphoreTicket, DISPATCH_TIME_FOREVER);

        if (self.ticketCount > 0) {

            self.ticketCount --;

            NSLog(@"剩余票 --- %ld  窗口 --- %@", self.ticketCount, [NSThread currentThread]);

        }else{

            NSLog(@"所有票均已售完");

            // 相当于解锁

//            dispatch_semaphore_signal(_semaphoreTicket);

            break;

        }

//        dispatch_semaphore_signal(_semaphoreTicket);

    }

}

2018-05-17 10:58:17.676200+0800 MultiThread[12006:1085650] 剩余票 --- 9 窗口 --- <NSThread: 0x600000471080>{number = 6, name = (null)}

2018-05-17 10:58:17.676247+0800 MultiThread[12006:1085651] 剩余票 --- 9 窗口 --- <NSThread: 0x600000471040>{number = 5, name = (null)}

2018-05-17 10:58:17.676370+0800 MultiThread[12006:1085651] 剩余票 --- 8 窗口 --- <NSThread: 0x600000471040>{number = 5, name = (null)}

2018-05-17 10:58:17.676370+0800 MultiThread[12006:1085650] 剩余票 --- 8 窗口 --- <NSThread: 0x600000471080>{number = 6, name = (null)}

2018-05-17 10:58:17.676578+0800 MultiThread[12006:1085651] 剩余票 --- 7 窗口 --- <NSThread: 0x600000471040>{number = 5, name = (null)}

2018-05-17 10:58:17.676641+0800 MultiThread[12006:1085650] 剩余票 --- 6 窗口 --- <NSThread: 0x600000471080>{number = 6, name = (null)}

2018-05-17 10:58:17.676925+0800 MultiThread[12006:1085650] 剩余票 --- 4 窗口 --- <NSThread: 0x600000471080>{number = 6, name = (null)}

2018-05-17 10:58:17.676751+0800 MultiThread[12006:1085651] 剩余票 --- 5 窗口 --- <NSThread: 0x600000471040>{number = 5, name = (null)}

2018-05-17 10:58:17.677039+0800 MultiThread[12006:1085650] 剩余票 --- 3 窗口 --- <NSThread: 0x600000471080>{number = 6, name = (null)}

2018-05-17 10:58:17.677602+0800 MultiThread[12006:1085651] 剩余票 --- 2 窗口 --- <NSThread: 0x600000471040>{number = 5, name = (null)}

2018-05-17 10:58:17.677828+0800 MultiThread[12006:1085650] 剩余票 --- 1 窗口 --- <NSThread: 0x600000471080>{number = 6, name = (null)}

2018-05-17 10:58:17.678013+0800 MultiThread[12006:1085651] 剩余票 --- 0 窗口 --- <NSThread: 0x600000471040>{number = 5, name = (null)}

2018-05-17 10:58:17.678449+0800 MultiThread[12006:1085650] 所有票均已售完

2018-05-17 10:58:17.678646+0800 MultiThread[12006:1085651] 所有票均已售完

解析一下,两个队列相当于两个售票窗口,初始化信号量为1,当一个队列进入售票过程,dispatch_semaphore_wait使得信号量-1,信号量为0,并执行下方售票代码,dispatch_semaphore_signal使得信号量+1,相当于解锁。实现了线程安全。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容