zoukankan      html  css  js  c++  java
  • 多线程

    GCD

    1.简介

    1.1 GCD官方解释

      Grand Central Dispatch(GCD)是异步执行任务的技术之一,一般将应用程序中记述的线程管理用的代码再系统级中实现。开发者只需要定义想执行的任务并追加到适当的 Dispatch Queue 中,GCD就能生成必要的线程并计划执行任务。由于线程管理是作为系统的一部分来实现的,因此可以统一管理,也可执行任务,这样就比以前的线程更有效率。

    1.2 GCD的好处

    • GCD可用于多核的并行运算
    • GCD会自动利用更多的CPU内核(比如双核、四核)
    • GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
    • 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码

    2.任务和队列

    学习GCD之前,我们要了解GCD中两个核心概念:任务和队列。

    2.1 任务

      任务:就是执行操作的意思,换句话说就是你在线程中执行的那段代码。在GCD中是放在block中的。执行任务有两种方式:同步执行异步执行。两者的主要区别是是否具备开启新线程的能力。

    • 同步执行:只能在当前线程中执行任务,不具备开启新线程的能力。
    • 异步执行:可以在新的线程中执行任务,具备开启新线程的能力。

    2.2 队列

      队列:这里的队列指任务队列,即用来存放任务的队列。队列是一种特殊的线性表,采用FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。在GCD中有两种队列:串行队列并行队列

    • 并行队列:可以让多个任务并行(同时)执行(自动开启多个线程同时执行任务),并行功能只有在异步函数下才有效。
    • 串行队列:让任务一个接着一个的执行(一个任务执行完毕后,再执行下一个任务)

    3.GCD的使用步骤

      GCD的使用步骤其实很简单,只有两步。

    1.创建一个队列(串行队列或并行队列)

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

    3.1 队列的创建方法

      可以使用 dispatch_queue_create 来创建对象,需要传入两个参数,第一个参数表示队列的唯一标识符,用于Debug,可为空;第二个参数用来识别是串行队列还是并行队列。DISPATCH_QUEUE_SERIAL 表示串行队列,DISPATCH_QUEUE_CONCURRENT 表示并行队列。

    // 串行队列的创建方法
    dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
    // 并行队列的创建方法
    dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
    

     在下图中,我们可以看到,唯一标识符的作用,在Debug时,更容易确定是哪个线程除了问题。

    对于并行队列,还可以使用 dispatch_get_global_queue 来创建全局并行队列。GCD默认提供了全局的并行队列,需要传入两个参数。第一个参数表示队列优先级,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT,第二个参数暂时没用,用0即可。

    3.2 任务的创建方法

    // 同步执行任务创建方法
    dispatch_sync(queue, ^{
        NSLog(@"%@",[NSThread currentThread]);    // 这里放任务代码
    });
    // 异步执行任务创建方法
    dispatch_async(queue, ^{
        NSLog(@"%@",[NSThread currentThread]);    // 这里放任务代码
    });
    

     虽然使用GCD只需两步,但是既然我们有两种队列,两种任务执行方式,那么我们就有了四种不同的组合方式,这4种不同的组合方式是:

    1. 并行队列 + 同步执行
    2. 并行队列 + 异步执行
    3. 串行队列 + 同步执行
    4. 串行队列 + 异步执行

    实际上,我们还有一种特殊队列是主队列,那样就有6种不同的组合方式:

    1. 主队列 + 同步执行
    2. 主队列 + 异步执行

    那么这几种不同的组合方式有什么区别呢,看图:

      并行队列 串行队列 主队列
    同步(sync) 没有开启新线程,串行执行任务 没有开启新线程,串行执行任务 没有开启新线程,串行执行任务
    异步(async) 有开启新线程,并行执行任务 有开启新线程(1条),串行执行任务 没有开启新线程,串行执行任务

    4. GCD的基本使用

    4.1 并行队列 + 同步执行

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

    - (void)lesson2{
        // 创建一个并行队列
        NSLog(@"syncConcurrent---begin");
        
        dispatch_queue_t queue = dispatch_queue_create("并行队列", DISPATCH_QUEUE_CONCURRENT);
        
        dispatch_sync(queue, ^{
            for (NSInteger i = 0; i < 3; i++) {
                NSLog(@" 1 - - - - %@",[NSThread currentThread]);
            }
        });
        
        dispatch_sync(queue, ^{
            for (NSInteger i = 0; i < 3; i++) {
                NSLog(@" 2 - - - - %@",[NSThread currentThread]);
            }
        });
        
        dispatch_sync(queue, ^{
            for (NSInteger i = 0; i < 3; i++) {
                NSLog(@" 3 - - - - %@",[NSThread currentThread]);
            }
        });
        
        NSLog(@"syncConcurrent---end");
        
        /*
         我们可以看出,任务都是在主线程中执行的,由于只有一个线程,所以任务只能一个一个执行
         同时,我们还可以看到,都打印在 begin 和 end之间,说明,任务添加到队列之后,马上会执行的
         */
        
    //    2017-12-06 18:30:42.338766+0800 GCD[20449:496505] syncConcurrent---begin
    //    2017-12-06 18:30:42.339001+0800 GCD[20449:496505]  1 - - - - <NSThread: 0x600000065640>{number = 1, name = main}
    //    2017-12-06 18:30:42.339187+0800 GCD[20449:496505]  1 - - - - <NSThread: 0x600000065640>{number = 1, name = main}
    //    2017-12-06 18:30:42.339325+0800 GCD[20449:496505]  1 - - - - <NSThread: 0x600000065640>{number = 1, name = main}
    //    2017-12-06 18:30:42.339448+0800 GCD[20449:496505]  2 - - - - <NSThread: 0x600000065640>{number = 1, name = main}
    //    2017-12-06 18:30:42.339603+0800 GCD[20449:496505]  2 - - - - <NSThread: 0x600000065640>{number = 1, name = main}
    //    2017-12-06 18:30:42.339763+0800 GCD[20449:496505]  2 - - - - <NSThread: 0x600000065640>{number = 1, name = main}
    //    2017-12-06 18:30:42.339919+0800 GCD[20449:496505]  3 - - - - <NSThread: 0x600000065640>{number = 1, name = main}
    //    2017-12-06 18:30:42.340269+0800 GCD[20449:496505]  3 - - - - <NSThread: 0x600000065640>{number = 1, name = main}
    //    2017-12-06 18:30:42.340406+0800 GCD[20449:496505]  3 - - - - <NSThread: 0x600000065640>{number = 1, name = main}
    //    2017-12-06 18:30:42.340537+0800 GCD[20449:496505] syncConcurrent---end
    }
    

     4.2 并行队列 + 异步执行

    可同时开启多线程,任务交替执行

    - (void)lesson3{
        
        // 创建一个并行队列
        NSLog(@"syncConcurrent---begin");
        
        dispatch_queue_t queue = dispatch_queue_create("并行队列", DISPATCH_QUEUE_CONCURRENT);
        
        dispatch_async(queue, ^{
            for (NSInteger i = 0; i < 3; i++) {
                NSLog(@" 1 - - - - %@",[NSThread currentThread]);
            }
        });
        
        dispatch_async(queue, ^{
            for (NSInteger i = 0; i < 3; i++) {
                NSLog(@" 2 - - - - %@",[NSThread currentThread]);
            }
        });
        
        dispatch_async(queue, ^{
            for (NSInteger i = 0; i < 3; i++) {
                NSLog(@" 3 - - - - %@",[NSThread currentThread]);
            }
        });
        
        NSLog(@"syncConcurrent---end");
      /*
       我们可以看出,除了主线程,又开启了3个线程,并且任务是交替同时执行的,另一方面可以看出,任   务不是马上执行,而是将所有任务添加到队列之后,才开始异步执行。
      */ // 2017-12-06 22:16:40.036449+0800 GCD[1001:46014] syncConcurrent---begin // 2017-12-06 22:16:40.036735+0800 GCD[1001:46014] syncConcurrent---end // 2017-12-06 22:16:40.036914+0800 GCD[1001:46328] 2 - - - - <NSThread: 0x6000004648c0>{number = 4, name = (null)} // 2017-12-06 22:16:40.036912+0800 GCD[1001:46325] 3 - - - - <NSThread: 0x60400027d380>{number = 5, name = (null)} // 2017-12-06 22:16:40.036956+0800 GCD[1001:46330] 1 - - - - <NSThread: 0x60400027d440>{number = 3, name = (null)} // 2017-12-06 22:16:40.037143+0800 GCD[1001:46328] 2 - - - - <NSThread: 0x6000004648c0>{number = 4, name = (null)} // 2017-12-06 22:16:40.037306+0800 GCD[1001:46330] 1 - - - - <NSThread: 0x60400027d440>{number = 3, name = (null)} // 2017-12-06 22:16:40.037337+0800 GCD[1001:46325] 3 - - - - <NSThread: 0x60400027d380>{number = 5, name = (null)} // 2017-12-06 22:16:40.037505+0800 GCD[1001:46328] 2 - - - - <NSThread: 0x6000004648c0>{number = 4, name = (null)} // 2017-12-06 22:16:40.037608+0800 GCD[1001:46330] 1 - - - - <NSThread: 0x60400027d440>{number = 3, name = (null)} // 2017-12-06 22:16:40.039677+0800 GCD[1001:46325] 3 - - - - <NSThread: 0x60400027d380>{number = 5, name = (null)} }

     4.3 串行队列 + 同步执行

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

    - (void)lesson4{
        NSLog(@"syncConcurrent - begin");
        
        dispatch_queue_t queue = dispatch_queue_create("串行队列", DISPATCH_QUEUE_SERIAL);
        
        dispatch_sync(queue, ^{
            for (NSInteger i = 0; i < 3; i++) {
                NSLog(@" 1 - - - - %@",[NSThread currentThread]);
            }
        });
        
        dispatch_sync(queue, ^{
            for (NSInteger i = 0; i < 3; i++) {
                NSLog(@" 2 - - - - %@",[NSThread currentThread]);
            }
        });
        
        dispatch_sync(queue, ^{
            for (NSInteger i = 0; i < 3; i++) {
                NSLog(@" 3 - - - - %@",[NSThread currentThread]);
            }
        });
        
        NSLog(@"syncConcurrent - end");
      /* 我们可以看到,所有的任务都是在主线程中执行的,并没有开启新的线程,而且由于是串行队列,所以按顺序一个一个执行,而且任务都是添加到队列中马上执行的。
      */ // 2017-12-06 22:23:10.808603+0800 GCD[1066:51102] syncConcurrent - begin // 2017-12-06 22:23:10.809058+0800 GCD[1066:51102] 1 - - - - <NSThread: 0x604000079ec0>{number = 1, name = main} // 2017-12-06 22:23:10.809336+0800 GCD[1066:51102] 1 - - - - <NSThread: 0x604000079ec0>{number = 1, name = main} // 2017-12-06 22:23:10.809539+0800 GCD[1066:51102] 1 - - - - <NSThread: 0x604000079ec0>{number = 1, name = main} // 2017-12-06 22:23:10.809706+0800 GCD[1066:51102] 2 - - - - <NSThread: 0x604000079ec0>{number = 1, name = main} // 2017-12-06 22:23:10.809884+0800 GCD[1066:51102] 2 - - - - <NSThread: 0x604000079ec0>{number = 1, name = main} // 2017-12-06 22:23:10.810055+0800 GCD[1066:51102] 2 - - - - <NSThread: 0x604000079ec0>{number = 1, name = main} // 2017-12-06 22:23:10.810226+0800 GCD[1066:51102] 3 - - - - <NSThread: 0x604000079ec0>{number = 1, name = main} // 2017-12-06 22:23:10.810494+0800 GCD[1066:51102] 3 - - - - <NSThread: 0x604000079ec0>{number = 1, name = main} // 2017-12-06 22:23:10.810713+0800 GCD[1066:51102] 3 - - - - <NSThread: 0x604000079ec0>{number = 1, name = main} // 2017-12-06 22:23:10.810813+0800 GCD[1066:51102] syncConcurrent - end }

     4.4 串行队列 + 异步执行

      会开启新线程,但是因为任务是串行的,执行完一个任务,再执行下一个任务。

    - (void)lesson5{
        NSLog(@"syncConcurrent - begin");
        
        dispatch_queue_t queue = dispatch_queue_create("串行队列", DISPATCH_QUEUE_SERIAL);
        
        dispatch_async(queue, ^{
            for (NSInteger i = 0; i < 3; i++) {
                NSLog(@" 1 - - - - %@",[NSThread currentThread]);
            }
        });
        
        dispatch_async(queue, ^{
            for (NSInteger i = 0; i < 3; i++) {
                NSLog(@" 2 - - - - %@",[NSThread currentThread]);
            }
        });
        
        dispatch_async(queue, ^{
            for (NSInteger i = 0; i < 3; i++) {
                NSLog(@" 3 - - - - %@",[NSThread currentThread]);
            }
        });
        
    /* 我们可以看到,开启了一条新线程,但是任务还是串行,所以一个一个执行,任务不是马上执行,而是将所有任务添加到队列后才开始同步执行。*/ NSLog(@"syncConcurrent - end"); // 2017-12-06 22:25:04.040810+0800 GCD[1082:52532] syncConcurrent - begin // 2017-12-06 22:25:04.041050+0800 GCD[1082:52532] syncConcurrent - end // 2017-12-06 22:25:04.041171+0800 GCD[1082:52765] 1 - - - - <NSThread: 0x6040004607c0>{number = 3, name = (null)} // 2017-12-06 22:25:04.041814+0800 GCD[1082:52765] 1 - - - - <NSThread: 0x6040004607c0>{number = 3, name = (null)} // 2017-12-06 22:25:04.042084+0800 GCD[1082:52765] 1 - - - - <NSThread: 0x6040004607c0>{number = 3, name = (null)} // 2017-12-06 22:25:04.043690+0800 GCD[1082:52765] 2 - - - - <NSThread: 0x6040004607c0>{number = 3, name = (null)} // 2017-12-06 22:25:04.043849+0800 GCD[1082:52765] 2 - - - - <NSThread: 0x6040004607c0>{number = 3, name = (null)} // 2017-12-06 22:25:04.044082+0800 GCD[1082:52765] 2 - - - - <NSThread: 0x6040004607c0>{number = 3, name = (null)} // 2017-12-06 22:25:04.044733+0800 GCD[1082:52765] 3 - - - - <NSThread: 0x6040004607c0>{number = 3, name = (null)} // 2017-12-06 22:25:04.045220+0800 GCD[1082:52765] 3 - - - - <NSThread: 0x6040004607c0>{number = 3, name = (null)} // 2017-12-06 22:25:04.045922+0800 GCD[1082:52765] 3 - - - - <NSThread: 0x6040004607c0>{number = 3, name = (null)} }

    主队列:GCD自带的一种特殊的串行队列,所有放在主队列中的任务,都会放到主线程中执行,可以通过dispatch_get_main_queue() 获得主队列。

     4.5 主队列 + 同步执行

    互等卡住不可行(在主线程中调用)

    - (void)lesson6{
        
        NSLog(@"主线程 + 同步执行 - begin");
        
        dispatch_queue_t queue = dispatch_get_main_queue();
        
        dispatch_sync(queue, ^{
            for (NSInteger i = 0; i < 3; i++) {
                NSLog(@" 1 - - - - %@",[NSThread currentThread]);
            }
        });
        
        dispatch_sync(queue, ^{
            for (NSInteger i = 0; i < 3; i++) {
                NSLog(@" 2 - - - - %@",[NSThread currentThread]);
            }
        });
        
        dispatch_sync(queue, ^{
            for (NSInteger i = 0; i < 3; i++) {
                NSLog(@" 3 - - - - %@",[NSThread currentThread]);
            }
        });
    
      /*因为,我们在主线程中执行这段代码。我们把任务放到了主队列中,也就是放到了主线程的队列中。而同步执行有个特点,就是对于任务是立马执行的。那么当我们把第一个任务放进主队列中,它就会立马执行,但是主线程正在处理 lesson6 的方法,所以任务1需要等待 lesson6 执行完成才能执行,当lesson6执行到任务1的使用,又需要等任务1执行完成才能执行任务2和任务3,
    那么,现在的情况就是 lesson6 方法和任务1都在等对方执行完毕。这样大家互相等待,所以就卡住了。*/

    因为,此时主线程正在执行 方法lesson6, 但是由于是同步执行, 当将任务1添加到队列中时会马上执行,但是由于主线程正在执行方法lesson6,任务1需要等待lesson6执行完成,当lesson6执行到任务1的时候,也需要任务1执行完成才能继续执行,所以相互等待。

        
    NSLog(@"主线程 + 同步执行 - end"); // 2017-12-06 22:31:04.600063+0800 GCD[1120:56056] 主线程 + 同步执行 - begin // (lldb) }

    那么,如果不在主线程中调用,而在其它线程中调用会如何呢?

    不会开启新线程,执行完一个任务,在执行下一个任务(在其它线程中调用)

    - (void)lesson7{
        dispatch_queue_t queue = dispatch_queue_create("111", NULL);
        dispatch_async(queue, ^{
            [self lesson6];
        });
        /*所有任务都是在主线程中执行的,并没有开启新的线程,而且由于主队列是串行队列,所以按顺序一个一个执行,并且任务添加到队列中就会马上执行*/
    //    2017-12-06 22:35:25.873497+0800 GCD[1140:58833] 主线程 + 同步执行 - begin
    //    2017-12-06 22:35:25.878078+0800 GCD[1140:58563]  1 - - - - <NSThread: 0x600000066b80>{number = 1, name = main}
    //    2017-12-06 22:35:25.878277+0800 GCD[1140:58563]  1 - - - - <NSThread: 0x600000066b80>{number = 1, name = main}
    //    2017-12-06 22:35:25.878440+0800 GCD[1140:58563]  1 - - - - <NSThread: 0x600000066b80>{number = 1, name = main}
    //    2017-12-06 22:35:25.879158+0800 GCD[1140:58563]  2 - - - - <NSThread: 0x600000066b80>{number = 1, name = main}
    //    2017-12-06 22:35:25.879723+0800 GCD[1140:58563]  2 - - - - <NSThread: 0x600000066b80>{number = 1, name = main}
    //    2017-12-06 22:35:25.880138+0800 GCD[1140:58563]  2 - - - - <NSThread: 0x600000066b80>{number = 1, name = main}
    //    2017-12-06 22:35:25.880857+0800 GCD[1140:58563]  3 - - - - <NSThread: 0x600000066b80>{number = 1, name = main}
    //    2017-12-06 22:35:25.881020+0800 GCD[1140:58563]  3 - - - - <NSThread: 0x600000066b80>{number = 1, name = main}
    //    2017-12-06 22:35:25.881485+0800 GCD[1140:58563]  3 - - - - <NSThread: 0x600000066b80>{number = 1, name = main}
    //    2017-12-06 22:35:25.881695+0800 GCD[1140:58833] 主线程 + 同步执行 - end
    }
    

     4.6 主队列 + 异步执行

    只有在主线程中执行任务,执行完一个任务,再执行下一个任务

    - (void)lesson8{
        
        NSLog(@"主线程 + 异步执行 - begin");
        
        dispatch_queue_t queue = dispatch_get_main_queue();
        
        dispatch_async(queue, ^{
            for (NSInteger i = 0; i < 3; i++) {
                NSLog(@" 1 - - - - %@",[NSThread currentThread]);
            }
        });
        
        dispatch_async(queue, ^{
            for (NSInteger i = 0; i < 3; i++) {
                NSLog(@" 2 - - - - %@",[NSThread currentThread]);
            }
        });
        
        dispatch_async(queue, ^{
            for (NSInteger i = 0; i < 3; i++) {
                NSLog(@" 3 - - - - %@",[NSThread currentThread]);
            }
        });
        /*我们发现所有任务都是在主线程中,虽然是异步执行,具备开启线程的能力,但因为是主队列,所以所有任务都在主线程中,并一个一个执行,并且任务不是马上执行,而是将所有任务添加到队列之后,才开始同步执行。
      */ NSLog(@"主线程 + 异步执行 - end"); // 2017-12-06 22:40:01.452127+0800 GCD[1176:61800] 主线程 + 异步执行 - begin // 2017-12-06 22:40:01.452394+0800 GCD[1176:61800] 主线程 + 异步执行 - end // 2017-12-06 22:40:01.457218+0800 GCD[1176:61800] 1 - - - - <NSThread: 0x60000006d940>{number = 1, name = main} // 2017-12-06 22:40:01.457404+0800 GCD[1176:61800] 1 - - - - <NSThread: 0x60000006d940>{number = 1, name = main} // 2017-12-06 22:40:01.457966+0800 GCD[1176:61800] 1 - - - - <NSThread: 0x60000006d940>{number = 1, name = main} // 2017-12-06 22:40:01.458234+0800 GCD[1176:61800] 2 - - - - <NSThread: 0x60000006d940>{number = 1, name = main} // 2017-12-06 22:40:01.458409+0800 GCD[1176:61800] 2 - - - - <NSThread: 0x60000006d940>{number = 1, name = main} // 2017-12-06 22:40:01.458602+0800 GCD[1176:61800] 2 - - - - <NSThread: 0x60000006d940>{number = 1, name = main} // 2017-12-06 22:40:01.458829+0800 GCD[1176:61800] 3 - - - - <NSThread: 0x60000006d940>{number = 1, name = main} // 2017-12-06 22:40:01.458990+0800 GCD[1176:61800] 3 - - - - <NSThread: 0x60000006d940>{number = 1, name = main} // 2017-12-06 22:40:01.459240+0800 GCD[1176:61800] 3 - - - - <NSThread: 0x60000006d940>{number = 1, name = main} }

     5.GCD线程之间的通讯

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

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"1------%@",[NSThread currentThread]);
        }
    
        // 回到主线程
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"2-------%@",[NSThread currentThread]);
        });
    });
    

    6.GCD其它的方法 

    6.1 dispatch_after

      如果想在指定时间后执行处理的情况,可以使用 dispatch_after 方法来实现。

      在3秒后将指定的Block追加到 Main Dispatch Queue 中的源代码如下:

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"等待至少三秒钟");
        });
    

       这里需要注意的是,dispatch_after 并不是在指定时间后执行处理,而只是在指定时间追加处理到 Dispatch Queue,此源代码与在3秒后用dispatch_async函数追加 Block 到 Main Dispatch Queue 的相同。

      因为 Main Dispatch Queue 在主线程的 RunLoop 中执行,所以在比如每隔 1/60 秒执行的 RunLoop 中,Block 最快在 3 秒后执行,最慢在 3 秒 + 1/60 秒后执行,并且在 Main Dispatch Queue 有大量处理追加或主线程的处理本身有延迟时,这个时间会延长。

      虽然在有严格时间的要求下使用时会出现问题,但是想在大致延迟执行处理时,这个方法很好用。  

      第一个参数经常使用的值是 DISPATCH_TIME_NOW ,这表示现在的时间。

      第二个参数是指定延后的时间

      第三个参数是指定要追加处理的 Dispatch Queue

      第四个参数是要执行处理的Block 

    6.2 dispatch Group

      在追加到 dispatch queue 中的多个处理全部结束后想执行结束处理,这种情况经常出现,只使用一个 串行队列时,只要将想执行的处理全部加到该串行队列,并在最后追加结束处理,就可以实现,但是在使用 并行队列,或同时使用多个 dispatch queue 时,就需要用到 dispatch group。

      例如,追加3个Block 到 Global Dispatch Queue,这些 Block 如果全部执行完毕,就会执行 Main Dispatch Queue 中结束处理用的 Block。

    - (void)viewDidLoad {
        [super viewDidLoad];
        
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        
        dispatch_group_t group = dispatch_group_create();
        
        dispatch_group_async(group, queue, ^{
            for (NSInteger i = 0; i < 3; i++) {
                sleep(1);
                NSLog(@"async1 ------ %ld ----- %@",i,[NSThread currentThread]);
            }
        });
        
        dispatch_group_async(group, queue, ^{
            for (NSInteger i = 0; i < 3; i++) {
                sleep(1);
                NSLog(@"async2 ------ %ld ----- %@",i,[NSThread currentThread]);
            }
        });
        
        dispatch_group_async(group, queue, ^{
            for (NSInteger i = 0; i < 3; i++) {
                sleep(1);
                NSLog(@"async3 ------ %ld ----- %@",i,[NSThread currentThread]);
            }
        });
        
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            NSLog(@"终于到我了 --- %@",[NSThread currentThread]);
        });
        
    }
    

     输出结果:

    2017-12-13 18:04:07.948776+0800 GCD[16160:439266] async1 ------ 0 ----- <NSThread: 0x600000278a00>{number = 3, name = (null)}
    2017-12-13 18:04:07.948781+0800 GCD[16160:439267] async3 ------ 0 ----- <NSThread: 0x60400046dac0>{number = 5, name = (null)}
    2017-12-13 18:04:07.948816+0800 GCD[16160:439268] async2 ------ 0 ----- <NSThread: 0x60400046d9c0>{number = 4, name = (null)}
    2017-12-13 18:04:08.949200+0800 GCD[16160:439267] async3 ------ 1 ----- <NSThread: 0x60400046dac0>{number = 5, name = (null)}
    2017-12-13 18:04:08.949217+0800 GCD[16160:439266] async1 ------ 1 ----- <NSThread: 0x600000278a00>{number = 3, name = (null)}
    2017-12-13 18:04:08.949224+0800 GCD[16160:439268] async2 ------ 1 ----- <NSThread: 0x60400046d9c0>{number = 4, name = (null)}
    2017-12-13 18:04:09.949591+0800 GCD[16160:439267] async3 ------ 2 ----- <NSThread: 0x60400046dac0>{number = 5, name = (null)}
    2017-12-13 18:04:09.949769+0800 GCD[16160:439266] async1 ------ 2 ----- <NSThread: 0x600000278a00>{number = 3, name = (null)}
    2017-12-13 18:04:09.955017+0800 GCD[16160:439268] async2 ------ 2 ----- <NSThread: 0x60400046d9c0>{number = 4, name = (null)}
    2017-12-13 18:04:09.955370+0800 GCD[16160:438995] 终于到我了 --- <NSThread: 0x6000002613c0>{number = 1, name = main}
    

       我们可以看到,当三个追加Block的任务执行完毕后,主线程的任务才执行,无论向什么样的 dispatch queue 中追加处理,使用 dispatch group 都可以监视这些处理执行的结束。一旦检测到所有处理执行结束,就可将结束的处理追加到 dispatch queue 中,这就是使用 dispatch group 的原因。

    6.3 dispatch_semaphore (信号量)

      在这里我们可以先考虑一个问题:假设现在系统有两个空闲资源可以使用,但同一时间要有三个线程需要访问,这种情况下,该怎么处理呢?或者,我们要下载很多图片,并发异步执行,但是我们又担心太多线程cpu吃不消,那么我们就可以使用信号量来控制一下线程最大开辟数。

      信号量:就是一种可用来控制访问资源的数量的标识。设定了一个信号量,在线程访问之前,加上信号量的处理,则可告知系统可以按照我们们指定的信号量数量来执行多个线程。有点类似于锁的机制。

      主要有三个函数:

    //创建信号量,参数:信号量的初值,如果小于0则会返回NULL
    dispatch_semaphore_create(信号量值)
     
    //等待信号量(减少信号量)
    dispatch_semaphore_wait(信号量,等待时间)
     
    //发送信号量 (增加信号量)
    dispatch_semaphore_signal(信号量)       一般都是先 降低,然后再提高
    

       一切都在代码中:

      

    // creat 的 value 表示,最多有几个资源可以访问
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
        // 获得全局队列
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        
        // 任务1
        dispatch_async(queue, ^{
            // 如果没有就无限制的等待
            long result = dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            NSLog(@"任务1 result - %ld",result);
            NSLog(@"开始执行任务1");
            sleep(4);
            NSLog(@"结束执行任务1");
            dispatch_semaphore_signal(semaphore);
        });
        
        // 任务2
        dispatch_async(queue, ^{
            // 如果没有就无限制的等待
            long result = dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            NSLog(@"任务2 result - %ld",result);
            NSLog(@"开始执行任务2");
            sleep(4);
            NSLog(@"结束执行任务2");
            dispatch_semaphore_signal(semaphore);
        });
        
        // 任务3
        dispatch_async(queue, ^{
            // 如果没有就无限制的等待
            long result = dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            NSLog(@"任务3 result - %ld",result);
            NSLog(@"开始执行任务3");
            sleep(3);
            NSLog(@"结束执行任务3");
            dispatch_semaphore_signal(semaphore);
        });
    

       执行完的代码:

    2018-02-01 14:33:45.866447+0800 定时器[1465:121647] 任务2 result - 0
    2018-02-01 14:33:45.866480+0800 定时器[1465:121648] 任务1 result - 0
    2018-02-01 14:33:45.867231+0800 定时器[1465:121647] 开始执行任务2
    2018-02-01 14:33:45.867241+0800 定时器[1465:121648] 开始执行任务1
    2018-02-01 14:33:47.869069+0800 定时器[1465:121648] 结束执行任务1
    2018-02-01 14:33:47.869292+0800 定时器[1465:121645] 任务3 result - 0
    2018-02-01 14:33:47.869420+0800 定时器[1465:121645] 开始执行任务3
    2018-02-01 14:33:49.869980+0800 定时器[1465:121647] 结束执行任务2
    2018-02-01 14:33:50.870619+0800 定时器[1465:121645] 结束执行任务3
    

       我们可以看出来,因为只有2个资源,所以当任务1任务2占了两个资源之后,任务3就只能一直等待,直到任务1和任务2之间有一个任务完成之后,才能往下执行,我们设置的是无限制等待,如果我们设置任务3的等待时间为1的话,且任务1和任务2的执行时间超过1,那么 wait 方法就会返回一个不为 0 的长整型。这个例子只是简单的理解一下信号量的作用。

     6.4 dispatch_barrier 

      简单的先来理解一下,在一个并行队列中,有多个线程在执行多个任务,在这个并行队列中,有一个dispatch_barrier任务,这样会有一个什么效果呢?我们直接上代码:

    // 创建一个并行队列
        dispatch_queue_t queue = dispatch_queue_create(0,DISPATCH_QUEUE_CONCURRENT);
        
        // 任务1
        dispatch_async(queue, ^{
            for (NSInteger i = 0; i < 10; i++) {
                sleep(1);
                NSLog(@"任务1中的 - %ld",i);
            }
            NSLog(@"----------------------------");
        });
        
        // 任务2
        dispatch_barrier_async(queue, ^{
            for (NSInteger i = 10; i < 20; i++) {
                sleep(1);
                NSLog(@"任务2中的 - %ld",i);
            }
            NSLog(@"任务2当前线程 - %@",[NSThread currentThread]);
            NSLog(@"----------------------------");
        });
        
        NSLog(@"我是主线程");
        
        // 任务3
        dispatch_async(queue, ^{
            for (NSInteger i = 20; i < 30; i++) {
                sleep(1);
                NSLog(@"任务3中的 - %ld",i);
            }
        });
    

       我们可以看到,任务1先执行完,然后执行 barrier 里面的,再执行 任务3。dispatch_barrier_sync确实是会在队列中充当一个栅栏的作用,凡是在他之后进入队列的任务,总会在dispatch_barrier_sync之前的所有任务执行完毕之后才执行。见名知意,dispatch_barrier_sync是会在主线程执行队列中的任务的,所以,Running on Main Thread这句话会被阻塞,从而在barrier之后执行。也就是说 dispatch_barrier_sync 和 dispatch_barrier_async 的区别就在于,是否会阻塞主线程。

      注意:这个函数仅对于自己创建的并行队列有效。如果在全局队列中,因为每一次使用,系统都可能给你分配不同的并行队列,所以也就变得没有意义,串行队列也就不用说了。

    6.5 GCD 精准的定时器

    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
    // 第二个参数是间隔时间,第四个参数是精确度,0表示没有误差
        dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
        dispatch_source_set_event_handler(timer, ^{
            NSLog(@"123123123");
        });
        
        dispatch_resume(timer);
    
  • 相关阅读:
    Jython:java调用python文件之第三方包路径问题
    待研究的技术第二版
    mysql主从数据库不同步的2种解决方法(转)
    mysql分表和表分区详解
    mysql-binlog日志恢复数据库
    mysql delete删除记录数据库空间不减少问题解决方法
    mySQL 增量备份方案(转)
    mysql 查看 删除 日志操作总结(包括单独和主从mysql)
    Memcache的部署和使用(转)
    Linux下memcache的安装和启动(转)
  • 原文地址:https://www.cnblogs.com/chenjiangxiaoyu/p/7994173.html
Copyright © 2011-2022 走看看