iOS 多线程-GCD

本文内容
任务、队列的概念、创建方式
任务 + 队列的6种组合的执行方式
线程间如何通信
dispatch_once、dispatch_after、dispatch_apply(快速迭代)、dispatch_barrier(栅栏函数)、dispatch_group(队列组)、dispatch_semaphore(信号量)如何实现线程安全与线程同步
iOS多线程demo地址

上文说到iOS 多线程- pThread和NSThread
这篇文章来讲讲GCD

GCD 􏰞􏲯􏰽􏴙􏲡的优点

  1. 可用于多核的并行运算
  2. 会自动利用更多的CPU内核
  3. 自动管理线程的生命周期(创建线程、调度任务、销毁线程)
  4. 只用关注执行什么任务,不用编写任何线程管理代码

1.任务

任务:执行的操作,放在block中的代码

执行任务的方式有两种,主要区别是:是否等待队列中的任务执行结束,是否具备开启新线程的能力

  1. 同步执行(sync):同步添加当前任务到指定的队列中,在队列中的任务全部结束之前,会一直等待,直到队列中的任务全部完成后,才开始下一个任务,只能在当前线程中执行任务,不具备开启线程的能力。

  2. 异步执行 (async):异步添加当前任务到指定队列中,不会等待队列的任务执行结束,直接开始执行下一个任务,可以在新的线程中执行任务,具备开启线程的能力。

*注意:异步执行 (async)虽然具有开启线程的能力,但是不一定会开启新的线程,这跟任务所指定的队列有关

2.队列

队列:存放任务的队列,队列是一种特殊的线性表,采用FIFO(先进先出)的原则,即新任务总是被插入到队列末尾,而读取任务总是从队列的头部开始读取,每读取一个任务,队列中则释放一个任务。

队列有两种方式,都满足FIFO原则,主要区别是:执行顺序不同,开启线程数不同

  1. 串行队列(Serial Dispatch Queue ):只开启一个线程,一个任务执行完毕后,再执行下一个任务
  2. 并行队列(Concurrent Dispatch Queue)􏴬􏰝􏰐􏲤􏲝􏴴􏴵 􏱍􏰄􏱍􏴶􏱦􏱧􏱤􏱥􏰐􏱠􏱡􏰨􏰤􏰂􏱩􏰓􏰆􏴋􏱍􏴶􏲻􏲼􏲟􏲷􏲸􏰾􏰡􏰐􏰴􏲽􏰲:可以开启多个线程,并且同时执行多个任务

3.使用步骤

  1. 创建一个队列
  2. 将任务添加到队列中,系统根据任务执行方式(同步、异步)进行执行

3.1 队列的创建、获取

使用dispatch_queue_create创建队列
第一个参数:队列的唯一标识符,用于DEBUG,可以为空,推荐使用应用程序ID这种逆序全局域名。
第二个参数:队列类型,串行队列DISPATCH_QUEUE_SERIAL,并行队列DISPATCH_QUEUE_CONCURRENT

    //创建串行队列
    dispatch_queue_t queue = dispatch_queue_create("com.xiuxiu.queque", DISPATCH_QUEUE_SERIAL);
    //创建并行队列
    dispatch_queue_t queue2 = dispatch_queue_create("com.xiuxiu.queque", DISPATCH_QUEUE_CONCURRENT);
     

主队列Main Dispatch Queue: GCD提供一种特殊串行队列:

  1. 所有放在主队列中的任务,都会放到主线程中执行
  2. 可使用dispatch_get_main_queue()获取主队列

主队列获取方法

    dispatch_queue_t queue = dispatch_get_main_queue()

全局并发队列Global Dispatch Queue : GCD默认提供的全局并发队列

并发队列获取方法:

 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_get_global_queue获取全局队列
第一个参数:队列的优先级

#define DISPATCH_QUEUE_PRIORITY_HIGH 2
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN

第二个参数:没有使用,用0 即可

3.2 任务的执行方式


    dispatch_async(queue, ^{
         //异步执行任务代码
    });
    
    dispatch_sync(queue, ^{
            //同步执行任务代码
    });

第一个参数是队列,那么队列 + 任务 执行方式就有6种组合(加上主队列)

同步执行 + 串行队列
异步执行 + 串行队列
同步执行 + 并行队列
异步执行 + 并行队列
同步执行 + 主队列
异步执行 + 主队列

3.2.1.同步执行 + 串行队列

/*
 同步执行 + 串行队列
 不会开启新线程,在当前线程中执行任务,一个任务执行完毕后,再执行下一个任务
 */
- (void)syncSerial{
    NSLog(@" syncSerial  start");
    dispatch_queue_t queue = dispatch_queue_create("com.xiuxiu.syncSerial", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任务一,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    dispatch_sync(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任务二,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    NSLog(@" syncSerial  end");
}

前面的编号代表进程的编号,一个APP的就是一个进程,进程编号总是一致的;
后面的编号代表线程的编号, 21898代表线程的编号

输出结果:

  1. 在当前线程中执行任务,没有开启新线程(同步执行不具备开启线程能力),根据线程编号看出
  2. 所有任务都在syncSerial startsyncSerial end 之间执行(同步执行需要等待队列中的任务执行结束)
  3. 任务按顺序执行(串行队列每次只有一个任务被执行,一个任务执行完毕后,再执行下一个任务)
2018-12-27 10:19:08.371664+0800 Thread[908:21898]  syncSerial  start
2018-12-27 10:19:08.371980+0800 Thread[908:21898]  任务一,i = 0
2018-12-27 10:19:09.372354+0800 Thread[908:21898]  任务一,i = 1
2018-12-27 10:19:10.372865+0800 Thread[908:21898]  任务一,i = 2
2018-12-27 10:19:11.373588+0800 Thread[908:21898]  任务二,i = 0
2018-12-27 10:19:12.374936+0800 Thread[908:21898]  任务二,i = 1
2018-12-27 10:19:13.375258+0800 Thread[908:21898]  任务二,i = 2
2018-12-27 10:19:14.376006+0800 Thread[908:21898]  syncSerial  end


3.2.2.异步执行 + 串行队列

/*
 异步执行 + 串行队列
 会开启新线程,但是因为队列是串行的,一个任务执行完毕后,再执行下一个任务
 */
- (void)asyncSerial{
    NSLog(@" asyncSerial  start");
    dispatch_queue_t queue = dispatch_queue_create("com.xiuxiu.asyncSerial", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任务一,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    dispatch_async(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任务二,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    NSLog(@" asyncSerial  end");
}

输出结果:

  1. 开启一条线程(异步执行具备开启线程的能力,因为是串行队列,只开启了一条线程)
  2. 所有任务都在asyncSerial startasyncSerial end 之后执行(异步执行不会等待,继续执行任务)
  3. 任务按顺序执行(串行队列每次只有一个任务被执行,任务一个接着一个执行)
2018-12-27 10:43:17.947620+0800 Thread[1105:32316]  asyncSerial  start
2018-12-27 10:43:17.947916+0800 Thread[1105:32316]  asyncSerial  end
2018-12-27 10:43:17.948034+0800 Thread[1105:32360]  任务一,i = 0
2018-12-27 10:43:18.953286+0800 Thread[1105:32360]  任务一,i = 1
2018-12-27 10:43:19.956284+0800 Thread[1105:32360]  任务一,i = 2
2018-12-27 10:43:20.961804+0800 Thread[1105:32360]  任务二,i = 0
2018-12-27 10:43:21.965620+0800 Thread[1105:32360]  任务二,i = 1
2018-12-27 10:43:22.967181+0800 Thread[1105:32360]  任务二,i = 2

3.2.3. 同步执行 + 并行队列

/*
 同步执行 + 并行队列
 不会开启新线程,在当前线程中执行任务,一个任务执行完毕后,再执行下一个任务
 */

- (void)syncConcurrent{
    NSLog(@" syncConcurrent  start");
    dispatch_queue_t queue = dispatch_queue_create("com.xiuxiu.syncConcurrent", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任务一,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    dispatch_sync(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任务二,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    NSLog(@" syncConcurrent  end");
}

输出结果:

  1. 在当前线程中执行任务,没有开启新线程(同步执行不具备开启线程能力),根据线程编号看出,上面讲过
  2. 所有任务都在syncConcurrent startsyncConcurrent end 之间执行(同步执行需要等待队列中的任务执行结束)
  3. 任务按顺序执行,虽然并行队列可以开启多个线程,并且同时执行多个任务,但是因为同步执行不具备开启线程的能力,只有当前这一个线程,而且同步执行需要等待队列中的任务执行结束,再执行下一个任务,所以任务只能一个接一个按照顺序执行。
2018-12-27 10:54:22.992819+0800 Thread[1210:37289]  syncConcurrent  start
2018-12-27 10:54:22.993023+0800 Thread[1210:37289]  任务一,i = 0
2018-12-27 10:54:23.994423+0800 Thread[1210:37289]  任务一,i = 1
2018-12-27 10:54:24.995012+0800 Thread[1210:37289]  任务一,i = 2
2018-12-27 10:54:25.996057+0800 Thread[1210:37289]  任务二,i = 0
2018-12-27 10:54:26.997429+0800 Thread[1210:37289]  任务二,i = 1
2018-12-27 10:54:27.998885+0800 Thread[1210:37289]  任务二,i = 2
2018-12-27 10:54:29.000327+0800 Thread[1210:37289]  syncConcurrent  end

3.2.4.异步执行 + 并发队列

/*
 异步执行 + 并行队列
 开启多个线程,任务交替执行
 */

- (void)asyncConcurrent{
    NSLog(@" asyncConcurrent  start");
    dispatch_queue_t queue = dispatch_queue_create("com.xiuxiu.asyncConcurrent", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任务一,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    dispatch_async(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任务二,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    NSLog(@" asyncConcurrent  end");
}

输出结果:

  1. 开启了2个新线程(异步执行具备开启线程的能力)
  2. 所有任务都在asyncConcurrent startasyncConcurrent end 之后执行(异步执行不会等待,继续执行任务)
  3. 任务交替执行(异步执行具备开启线程的能力,并且并行队列可以开启多个线程,同时执行多个任务)
2018-12-27 11:18:23.174090+0800 Thread[1210:37289]  asyncConcurrent  start
2018-12-27 11:18:23.174253+0800 Thread[1210:37289]  asyncConcurrent  end
2018-12-27 11:18:23.174351+0800 Thread[1210:47038]  任务二,i = 0
2018-12-27 11:18:23.174401+0800 Thread[1210:37362]  任务一,i = 0
2018-12-27 11:18:24.177759+0800 Thread[1210:37362]  任务一,i = 1
2018-12-27 11:18:24.177759+0800 Thread[1210:47038]  任务二,i = 1
2018-12-27 11:18:25.178650+0800 Thread[1210:47038]  任务二,i = 2
2018-12-27 11:18:25.178650+0800 Thread[1210:37362]  任务一,i = 2

3.2.5.同步执行 + 主队列

同步执行 + 主队列 在不同线程中调用,结果不一样。
在主线程中调用,出现死锁
在其他线程中调用,不会开启线程,一个任务执行完毕后,再执行下一个任务

3.2.5.1在主线程调用

/*
 同步执行 + 主队列
 在主线程中调用,出现死锁
 */

- (void)syncMain{
    NSLog(@" syncMain  start");
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任务一,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    dispatch_sync(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任务二,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    NSLog(@" syncMain  end");
}

输出结果:
在Xcode10 上运行崩溃,只输出了syncMain start
为什么?
因为我们在主线程中执行 syncMain方法,相当于把 syncMain任务放到主线程的队列中,而同步执行会等待当前队列中的任务执行完毕后,才会接着执行。我们把任务一追加到主队列中,任务一会等待主线程处理完syncMain方法,而syncMain方法又需要等待任务一执行完毕,才能继续执行,双方都在等待,所以线程死锁,任务无法执行。

2018-12-27 11:26:17.011738+0800 Thread[1443:50531]  syncMain  start

3.2.5.2 在其他线程调用

/*
 同步执行 + 主队列
 在其他线程中调用,不会开启线程,一个任务执行完毕后,再执行下一个任务
 */

- (void)syncMain{
    NSLog(@"主线程");
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@" syncMain  start");
        dispatch_queue_t queue = dispatch_get_main_queue();
        dispatch_sync(queue, ^{
            for (NSInteger  i = 0; i < 3; i ++) {
                NSLog(@" 任务一,i = %ld",(long)i);
                [NSThread sleepForTimeInterval:1.0];
            }
        });
        dispatch_sync(queue, ^{
            for (NSInteger  i = 0; i < 3; i ++) {
                NSLog(@" 任务二,i = %ld",(long)i);
                [NSThread sleepForTimeInterval:1.0];
            }
        });
        NSLog(@" syncMain  end");
    });
}

输出结果:

  1. 没有开启线程,放在主队列的任务都在主线程中执行
  2. 所有任务都在syncMain startsyncMain end 之间执行(同步执行需要等待队列中的任务执行结束)
  3. 任务按顺序执行(串行队列每次只有一个任务被执行,任务一个接着一个执行)
2018-12-27 14:22:03.964047+0800 Thread[2134:84333] 主线程
2018-12-27 14:22:03.964238+0800 Thread[2134:84385]  syncMain  start
2018-12-27 14:22:03.965009+0800 Thread[2134:84333]  任务一,i = 0
2018-12-27 14:22:04.966453+0800 Thread[2134:84333]  任务一,i = 1
2018-12-27 14:22:05.967903+0800 Thread[2134:84333]  任务一,i = 2
2018-12-27 14:22:06.968817+0800 Thread[2134:84333]  任务二,i = 0
2018-12-27 14:22:07.969877+0800 Thread[2134:84333]  任务二,i = 1
2018-12-27 14:22:08.970376+0800 Thread[2134:84333]  任务二,i = 2
2018-12-27 14:22:09.971810+0800 Thread[2134:84385]  syncMain  end

3.2.6.异步执行 + 主队列

/*
 异步执行 + 主队列
 不会开启新线程,在主线程中执行,一个任务执行完毕后,再执行下一个任务
 */

- (void)asyncMain{
    NSLog(@" asyncMain  start");
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任务一,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    dispatch_async(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任务二,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    NSLog(@" asyncMain  end");
}

输出结果:

  1. 没有开启线程,放在主队列的任务都在主线程中执行
  2. 所有任务都在asyncMain startasyncMain end 之后执行(异步执行不会等待,继续执行任务)
  3. 任务按顺序执行(因为主队列是串行队列,每次只有一个任务被执行,任务一个接着一个执行)
2018-12-27 14:33:15.293261+0800 Thread[2134:84333]  asyncMain  start
2018-12-27 14:33:15.293406+0800 Thread[2134:84333]  asyncMain  end
2018-12-27 14:33:15.293646+0800 Thread[2134:84333]  任务一,i = 0
2018-12-27 14:33:16.293875+0800 Thread[2134:84333]  任务一,i = 1
2018-12-27 14:33:17.295251+0800 Thread[2134:84333]  任务一,i = 2
2018-12-27 14:33:18.296751+0800 Thread[2134:84333]  任务二,i = 0
2018-12-27 14:33:19.297602+0800 Thread[2134:84333]  任务二,i = 1
2018-12-27 14:33:20.298579+0800 Thread[2134:84333]  任务二,i = 2

4.线程间通信

在iOS开发工程中,我们一般在主线程中进行UI刷新,如:点击、拖拽、滚动事件,耗时操作放在其他线程中,而当耗时操作结束后,回到主线程,就需要用到线程间的通信。

在全局队列中执行任务,任务完成后,切回主线程

- (void)gcdCommunication{
    NSLog(@"我在主线程");
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任务一,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"回到主线程");
        });
    });
}

输出结果:

2018-12-27 15:05:55.188427+0800 Thread[2519:102865] 我在主线程
2018-12-27 15:05:55.188635+0800 Thread[2519:102899]  任务一,i = 0
2018-12-27 15:05:56.189689+0800 Thread[2519:102899]  任务一,i = 1
2018-12-27 15:05:57.191253+0800 Thread[2519:102899]  任务一,i = 2
2018-12-27 15:05:58.195350+0800 Thread[2519:102865] 回到主线程

5.dispatch_once

创建单例或者整个程序只运行一次的代码,可以使用dispatch_oncedispatch_once函数保证这个程序运行过程中只被执行一次,即时在多线程情况下也是安全的。

/*
   验证dispatch_once
 */
- (void)testGcdOnce{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            [self gcdOnce];
        }
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            [self gcdOnce];
        }
    });
}

- (void)gcdOnce{
    NSLog(@"%s",__func__);
    static TicketManager *manager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        manager = [[TicketManager alloc]init];
        NSLog(@"创建对象");
    });
}

输出结果:dispatch_once里面只被执行了一次

2018-12-27 15:19:31.228794+0800 Thread[2702:110251] -[ViewController gcdOnce]
2018-12-27 15:19:31.228794+0800 Thread[2702:110250] -[ViewController gcdOnce]
2018-12-27 15:19:31.229290+0800 Thread[2702:110251] 创建对象
2018-12-27 15:19:31.229516+0800 Thread[2702:110250] -[ViewController gcdOnce]
2018-12-27 15:19:31.229514+0800 Thread[2702:110251] -[ViewController gcdOnce]
2018-12-27 15:19:31.229630+0800 Thread[2702:110250] -[ViewController gcdOnce]
2018-12-27 15:19:31.229678+0800 Thread[2702:110251] -[ViewController gcdOnce]

6.dispatch_after

dispatch_after并不是在指定时间之后才执行处理,而是在指定时间之后将任务追加到队列中,这个指定时间并不是绝对准确的,想要大致完成延时任务可以使用dispatch_after函数实现

- (void)gcdAfter{
    NSLog(@"%s",__func__);
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%s",__func__);
    });;
}

输出结果:大致为1秒

2018-12-27 15:28:23.416958+0800 Thread[2781:114534] -[ViewController gcdAfter]
2018-12-27 15:28:24.513064+0800 Thread[2781:114534] -[ViewController gcdAfter]_block_invoke

7. dispatch_apply(快速迭代)

dispatch_apply快速迭代方法,按照指定次数将指定的任务添加到队列中,并等待队列中的任务全部执行结束。

如果在串行队列中使用dispatch_apply函数,就和for循环遍历一样,按照顺序执行,体现不出快速迭代的意义。

如果在并行队列中使用dispatch_apply函数,dispatch_apply可以在多个线程中同时遍历多个数字。

7.1 在串行队列使用dispatch_apply

将在主队列dispatch_get_main_queue的遍历任务放在并行队列dispatch_get_global_queue中,为了避免上面讲的死锁问题,关注apply beginapply end之间的代码即可

/*
 dispatch_apply:快速迭代
 */
- (void)gcdApply{
    NSLog(@"主线程");
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"apply begin");
        dispatch_apply(6, dispatch_get_main_queue(), ^(size_t index) {
            NSLog(@"index = %zu",index);
        });
        NSLog(@"apply end");
    });
   
}

输出结果:

  1. 没有开启线程(主队列的任务只在主线程中执行)
  2. apply end在最后输出(dispatch_apply函数会等待队列全部任务执行结束)
2018-12-27 15:58:28.788666+0800 Thread[3090:128936] 主线程
2018-12-27 15:58:28.788880+0800 Thread[3090:128988] apply begin
2018-12-27 15:58:28.819630+0800 Thread[3090:128936] index = 0
2018-12-27 15:58:28.819791+0800 Thread[3090:128936] index = 1
2018-12-27 15:58:28.819897+0800 Thread[3090:128936] index = 2
2018-12-27 15:58:28.820107+0800 Thread[3090:128936] index = 3
2018-12-27 15:58:28.820396+0800 Thread[3090:128936] index = 4
2018-12-27 15:58:28.820503+0800 Thread[3090:128936] index = 5
2018-12-27 15:58:28.820752+0800 Thread[3090:128988] apply end


7.2在并行队列使用dispatch_apply

/*
 dispatch_apply:快速迭代
 */
- (void)gcdApply{
    NSLog(@"apply begin");
    dispatch_apply(6, dispatch_get_global_queue(0, 0), ^(size_t index) {
        NSLog(@"index = %zu",index);
    });
    NSLog(@"apply end");
}

输出结果:

  1. 开启线程(看线程编号得出)
  2. apply end在最后输出(dispatch_apply函数会等待队列全部任务执行结束)
2018-12-27 15:39:06.219402+0800 Thread[2918:120358] apply begin
2018-12-27 15:39:06.219636+0800 Thread[2918:120391] index = 1
2018-12-27 15:39:06.219619+0800 Thread[2918:120358] index = 0
2018-12-27 15:39:06.219746+0800 Thread[2918:120391] index = 3
2018-12-27 15:39:06.219747+0800 Thread[2918:120392] index = 4
2018-12-27 15:39:06.219737+0800 Thread[2918:120358] index = 2
2018-12-27 15:39:06.219832+0800 Thread[2918:120391] index = 5
2018-12-27 15:39:06.219933+0800 Thread[2918:120358] apply end

需求1:我们需要异步执行两个操作(一个操作可以是一个任务,也可以是多个任务,这里是两个任务),而且第一组操作结束后,才能开始第二组操作,如何实现呢?

这里的意思其实保持线程同步,将异步任务转化为同步任务的意思。

方法1:使用栅栏函数
方法2:使用dispatch_group
方式3 :使用dispatch_semaphore

这三个方法都会在下面一一讲解的。

总结:就当前这个需求,使用栅栏函数会比较简单,所有方法讲完具体实现就可以看出来了不用创建group或者semaphore,直接放个栅栏在中间,分隔两个操作。

8.dispatch_barrier(栅栏函数)

栅栏函数存在的意义:先执行栅栏函数之前的任务,再执行栅栏函数中的任务,最后执行栅栏函数之后的任务,增加栅栏函数不影响原有队列的任务执行方式,也就是栅栏函数之前队列的任务是什么执行方式,栅栏函数之后队列的任务还是什么执行方式。

栅栏函数分为:dispatch_barrier_asyncdispatch_barrier_sync
区别就是 :添加栅栏函数后面任务到队列的时间不一样。

dispatch_barrier_sync需要等待栅栏函数中的任务执行结束后,才会添加栅栏函数后的任务到队列中。

dispatch_barrier_async不需要等待栅栏函数中的任务执行结束,就已经将栅栏函数后的任务到队列中。

8.1 dispatch_barrier_sync

实现代码

- (void)gcdBarrier{
    NSLog(@"主线程");
    dispatch_queue_t queue = dispatch_queue_create("com.xiuxiu.gcd", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任务一,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    dispatch_async(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任务二,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    //1.dispatch_barrier_sync
    dispatch_barrier_sync(queue, ^{
        for (NSInteger i = 0; i < 3; i ++) {
            NSLog(@" barrier1,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
        for (NSInteger i = 0; i < 3; i ++) {
            NSLog(@" barrier2,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
 
    NSLog(@"---------barrier代码后面----------------");
    
    dispatch_async(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任务三,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    dispatch_async(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任务四,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
}

输出结果:

  1. 执行任务一、二,执行栅栏函数中的barrier1barrier2最后执行任务三、四;
  2. 任务一、二交替执行(异步执行),任务三、四交替执行(异步执行),印证栅栏函数不影响队列任务的执行方式;
  3. 横线在栅栏函数之后输出(dispatch_barrier_sync:需要等待栅栏函数中的任务执行结束后,才会添加栅栏函数后的任务到队列中)。
2018-12-28 17:07:54.227141+0800 Thread[11766:169507] 主线程
2018-12-28 17:07:54.227455+0800 Thread[11766:169563]  任务二,i = 0
2018-12-28 17:07:54.227482+0800 Thread[11766:169565]  任务一,i = 0
2018-12-28 17:07:55.231426+0800 Thread[11766:169563]  任务二,i = 1
2018-12-28 17:07:55.231426+0800 Thread[11766:169565]  任务一,i = 1
2018-12-28 17:07:56.232952+0800 Thread[11766:169563]  任务二,i = 2
2018-12-28 17:07:56.232985+0800 Thread[11766:169565]  任务一,i = 2
2018-12-28 17:07:57.236980+0800 Thread[11766:169507]  barrier1,i = 0
2018-12-28 17:07:58.238423+0800 Thread[11766:169507]  barrier1,i = 1
2018-12-28 17:07:59.239776+0800 Thread[11766:169507]  barrier1,i = 2
2018-12-28 17:08:00.240315+0800 Thread[11766:169507]  barrier2,i = 0
2018-12-28 17:08:01.240863+0800 Thread[11766:169507]  barrier2,i = 1
2018-12-28 17:08:02.242310+0800 Thread[11766:169507]  barrier2,i = 2
2018-12-28 17:08:03.242826+0800 Thread[11766:169507] ---------barrier代码后面----------------
2018-12-28 17:08:03.243200+0800 Thread[11766:169564]  任务三,i = 0
2018-12-28 17:08:03.243315+0800 Thread[11766:169663]  任务四,i = 0
2018-12-28 17:08:04.247765+0800 Thread[11766:169663]  任务四,i = 1
2018-12-28 17:08:04.247765+0800 Thread[11766:169564]  任务三,i = 1
2018-12-28 17:08:05.252405+0800 Thread[11766:169663]  任务四,i = 2
2018-12-28 17:08:05.252405+0800 Thread[11766:169564]  任务三,i = 2



8.2dispatch_barrier_async

实现代码

 - (void)gcdBarrier{
    NSLog(@"主线程");
    dispatch_queue_t queue = dispatch_queue_create("com.xiuxiu.gcd", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任务一,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    dispatch_async(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任务二,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    //2.dispatch_barrier_async
    dispatch_barrier_async(queue, ^{
        for (NSInteger i = 0; i < 3; i ++) {
            NSLog(@" barrier1,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
        for (NSInteger i = 0; i < 3; i ++) {
            NSLog(@" barrier2,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    NSLog(@"---------barrier代码后面----------------");
    
    dispatch_async(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任务三,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    dispatch_async(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任务四,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
}


输出结果:

  1. 执行任务一、二,执行栅栏函数中的barrier1barrier2最后执行任务三、四;
  2. 任务一、二交替执行(异步执行),任务三、四交替执行(异步执行),印证栅栏函数不影响队列任务的执行方式;
  3. 横线在栅栏函数之前输出(dispatch_barrier_async不需要等待栅栏函数中的任务执行结束,就已经将栅栏函数后的任务到队列中)。
2018-12-28 17:10:58.908322+0800 Thread[11798:171154] 主线程
2018-12-28 17:10:58.908586+0800 Thread[11798:171209]  任务一,i = 0
2018-12-28 17:10:58.908586+0800 Thread[11798:171154] ---------barrier代码后面----------------
2018-12-28 17:10:58.908616+0800 Thread[11798:171207]  任务二,i = 0
2018-12-28 17:10:59.912811+0800 Thread[11798:171207]  任务二,i = 1
2018-12-28 17:10:59.912811+0800 Thread[11798:171209]  任务一,i = 1
2018-12-28 17:11:00.917006+0800 Thread[11798:171207]  任务二,i = 2
2018-12-28 17:11:00.917047+0800 Thread[11798:171209]  任务一,i = 2
2018-12-28 17:11:01.919238+0800 Thread[11798:171209]  barrier1,i = 0
2018-12-28 17:11:02.921574+0800 Thread[11798:171209]  barrier1,i = 1
2018-12-28 17:11:03.924472+0800 Thread[11798:171209]  barrier1,i = 2
2018-12-28 17:11:04.929280+0800 Thread[11798:171209]  barrier2,i = 0
2018-12-28 17:11:05.930060+0800 Thread[11798:171209]  barrier2,i = 1
2018-12-28 17:11:06.931831+0800 Thread[11798:171209]  barrier2,i = 2
2018-12-28 17:11:07.934022+0800 Thread[11798:171209]  任务三,i = 0
2018-12-28 17:11:07.934022+0800 Thread[11798:171207]  任务四,i = 0
2018-12-28 17:11:08.939572+0800 Thread[11798:171207]  任务四,i = 1
2018-12-28 17:11:08.939573+0800 Thread[11798:171209]  任务三,i = 1
2018-12-28 17:11:09.942792+0800 Thread[11798:171209]  任务三,i = 2
2018-12-28 17:11:09.942829+0800 Thread[11798:171207]  任务四,i = 2


9.dispatch_group(队列组)

任务的执行是先创建一个任务,放入队列进行执行。而队列组就是用来存放队列的一个组。

将队列放入队列组中可以使用 dispatch_group_async 或者dispatch_group_enterdispatch_group_leave的组合实现。

队列组的任务结束完成后,调用dispatch_group_notify 可以回到指定线程执行任务,调用dispatch_group_wait可以回到当前线程执行任务(阻塞当前线程)。

就上面的需求1,用dispatch_group方式实现

1. 我们用dispatch_group_async + dispatch_group_notify来进行实现

- (void)requirementGroupAsync{
    NSLog(@"主线程");
    dispatch_queue_t queue = dispatch_queue_create("com.gcd.group", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, queue, ^{
        
            for (NSInteger  i = 0; i < 3; i ++) {
                NSLog(@" 任务一,i = %ld",(long)i);
                [NSThread sleepForTimeInterval:1.0];
            }
        
    });
    dispatch_group_async(group, queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任务二,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    
    dispatch_group_notify(group, queue, ^{
        NSLog(@"------dispatch_group_notify------");
        dispatch_async(queue, ^{
            for (NSInteger  i = 0; i < 3; i ++) {
                NSLog(@" 任务三,i = %ld",(long)i);
                [NSThread sleepForTimeInterval:1.0];
            }
        });
        dispatch_async(queue, ^{
            for (NSInteger  i = 0; i < 3; i ++) {
                NSLog(@" 任务四,i = %ld",(long)i);
                [NSThread sleepForTimeInterval:1.0];
            }
        });
        
    });
}

输出结果:先执行任务一、二,再执行任务三、四;

2018-12-28 17:59:23.695495+0800 Thread[12227:189820] 主线程
2018-12-28 17:59:23.695775+0800 Thread[12227:189867]  任务一,i = 0
2018-12-28 17:59:23.695778+0800 Thread[12227:189868]  任务二,i = 0
2018-12-28 17:59:24.697513+0800 Thread[12227:189867]  任务一,i = 1
2018-12-28 17:59:24.697514+0800 Thread[12227:189868]  任务二,i = 1
2018-12-28 17:59:25.701243+0800 Thread[12227:189868]  任务二,i = 2
2018-12-28 17:59:25.701243+0800 Thread[12227:189867]  任务一,i = 2
2018-12-28 17:59:26.705407+0800 Thread[12227:189868] ------dispatch_group_notify------
2018-12-28 17:59:26.705799+0800 Thread[12227:189868]  任务三,i = 0
2018-12-28 17:59:26.705803+0800 Thread[12227:189866]  任务四,i = 0
2018-12-28 17:59:27.709876+0800 Thread[12227:189868]  任务三,i = 1
2018-12-28 17:59:27.709873+0800 Thread[12227:189866]  任务四,i = 1
2018-12-28 17:59:28.713711+0800 Thread[12227:189866]  任务四,i = 2
2018-12-28 17:59:28.713711+0800 Thread[12227:189868]  任务三,i = 2

2. 我们用dispatch_group_async + dispatch_group_wait来进行实现

- (void)requirementGroupAsync{
    NSLog(@"主线程");
    dispatch_queue_t queue = dispatch_queue_create("com.gcd.group", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, queue, ^{
        
            for (NSInteger  i = 0; i < 3; i ++) {
                NSLog(@" 任务一,i = %ld",(long)i);
                [NSThread sleepForTimeInterval:1.0];
            }
        
    });
    dispatch_group_async(group, queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任务二,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    //2.dispatch_group_wait
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"-----dispatch_group_wait----");
    dispatch_async(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任务三,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    dispatch_async(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任务四,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    
}

输出结果:

  1. 先执行任务一、二,再执行任务三、四;
  2. dispatch_group_wait在主线程输出,就如之前所说 dispatch_group_wait会阻塞当前线程。
2018-12-28 18:07:54.379565+0800 Thread[12300:193286] 主线程
2018-12-28 18:07:54.379782+0800 Thread[12300:193334]  任务二,i = 0
2018-12-28 18:07:54.379783+0800 Thread[12300:193332]  任务一,i = 0
2018-12-28 18:07:55.384688+0800 Thread[12300:193334]  任务二,i = 1
2018-12-28 18:07:55.384720+0800 Thread[12300:193332]  任务一,i = 1
2018-12-28 18:07:56.385807+0800 Thread[12300:193332]  任务一,i = 2
2018-12-28 18:07:56.385808+0800 Thread[12300:193334]  任务二,i = 2
2018-12-28 18:07:57.386847+0800 Thread[12300:193286] -----dispatch_group_wait----
2018-12-28 18:07:57.387161+0800 Thread[12300:193334]  任务三,i = 0
2018-12-28 18:07:57.387174+0800 Thread[12300:193332]  任务四,i = 0
2018-12-28 18:07:58.387806+0800 Thread[12300:193332]  任务四,i = 1
2018-12-28 18:07:58.387808+0800 Thread[12300:193334]  任务三,i = 1
2018-12-28 18:07:59.390173+0800 Thread[12300:193332]  任务四,i = 2
2018-12-28 18:07:59.390209+0800 Thread[12300:193334]  任务三,i = 2

3. 我们用dispatch_group_enter + dispatch_group_leave + dispatch_group_notify来进行实现

- (void)requirementGroupEnter{
    NSLog(@"主线程");
    dispatch_queue_t queue = dispatch_queue_create("com.gcd.group", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任务一,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
        dispatch_group_leave(group);
    });
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任务二,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
        dispatch_group_leave(group);
    });

    //1.dispatch_group_notify
    dispatch_group_notify(group, queue, ^{
        NSLog(@"------dispatch_group_notify------");
        dispatch_async(queue, ^{
            for (NSInteger  i = 0; i < 3; i ++) {
                NSLog(@" 任务三,i = %ld",(long)i);
                [NSThread sleepForTimeInterval:1.0];
            }
        });
        dispatch_async(queue, ^{
            for (NSInteger  i = 0; i < 3; i ++) {
                NSLog(@" 任务四,i = %ld",(long)i);
                [NSThread sleepForTimeInterval:1.0];
            }
        });
        
    });
}

输出结果:先执行任务一、二,再执行任务三、四;

2018-12-28 18:19:52.294127+0800 Thread[12422:198467] 主线程
2018-12-28 18:19:52.294429+0800 Thread[12422:198501]  任务一,i = 0
2018-12-28 18:19:52.294438+0800 Thread[12422:198504]  任务二,i = 0
2018-12-28 18:19:53.297632+0800 Thread[12422:198504]  任务二,i = 1
2018-12-28 18:19:53.297642+0800 Thread[12422:198501]  任务一,i = 1
2018-12-28 18:19:54.302891+0800 Thread[12422:198501]  任务一,i = 2
2018-12-28 18:19:54.302891+0800 Thread[12422:198504]  任务二,i = 2
2018-12-28 18:19:55.307933+0800 Thread[12422:198501] ------dispatch_group_notify------
2018-12-28 18:19:55.308242+0800 Thread[12422:198501]  任务三,i = 0
2018-12-28 18:19:55.308258+0800 Thread[12422:198502]  任务四,i = 0
2018-12-28 18:19:56.312331+0800 Thread[12422:198501]  任务三,i = 1
2018-12-28 18:19:56.312331+0800 Thread[12422:198502]  任务四,i = 1
2018-12-28 18:19:57.313584+0800 Thread[12422:198502]  任务四,i = 2
2018-12-28 18:19:57.313584+0800 Thread[12422:198501]  任务三,i = 2

4. 我们用dispatch_group_enter + dispatch_group_leave + dispatch_group_wait来进行实现

- (void)requirementGroupEnter{
    NSLog(@"主线程");
    dispatch_queue_t queue = dispatch_queue_create("com.gcd.group", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任务一,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
        dispatch_group_leave(group);
    });
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任务二,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
        dispatch_group_leave(group);
    });
//    2.dispatch_group_wait
        dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
        NSLog(@"-----dispatch_group_wait----");
        dispatch_async(queue, ^{
            for (NSInteger  i = 0; i < 3; i ++) {
                NSLog(@" 任务三,i = %ld",(long)i);
                [NSThread sleepForTimeInterval:1.0];
            }
        });
        dispatch_async(queue, ^{
            for (NSInteger  i = 0; i < 3; i ++) {
                NSLog(@" 任务四,i = %ld",(long)i);
                [NSThread sleepForTimeInterval:1.0];
            }
        });
    
}

  1. 先执行任务一、二,再执行任务三、四;
  2. dispatch_group_wait在主线程输出,就如之前所说 dispatch_group_wait会阻塞当前线程
2018-12-28 18:21:29.197243+0800 Thread[12446:199547] 主线程
2018-12-28 18:21:29.197534+0800 Thread[12446:199590]  任务一,i = 0
2018-12-28 18:21:29.197598+0800 Thread[12446:199591]  任务二,i = 0
2018-12-28 18:21:30.200867+0800 Thread[12446:199591]  任务二,i = 1
2018-12-28 18:21:30.200870+0800 Thread[12446:199590]  任务一,i = 1
2018-12-28 18:21:31.204217+0800 Thread[12446:199590]  任务一,i = 2
2018-12-28 18:21:31.204215+0800 Thread[12446:199591]  任务二,i = 2
2018-12-28 18:21:32.205551+0800 Thread[12446:199547] -----dispatch_group_wait----
2018-12-28 18:21:32.205926+0800 Thread[12446:199590]  任务三,i = 0
2018-12-28 18:21:32.205930+0800 Thread[12446:199591]  任务四,i = 0
2018-12-28 18:21:33.210243+0800 Thread[12446:199590]  任务三,i = 1
2018-12-28 18:21:33.210413+0800 Thread[12446:199591]  任务四,i = 1
2018-12-28 18:21:34.215728+0800 Thread[12446:199590]  任务三,i = 2
2018-12-28 18:21:34.215728+0800 Thread[12446:199591]  任务四,i = 2

dispatch_group_asyncdispatch_group_enter + dispatch_group_leave有什么区别呢?

需求2:异步执行两个网络请求,两个网络请求执行结束后,进行一定的操作。

这里sendRequest用来代表网络请求

- (void)sendRequest:(void (^)(void))block{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"start task 1");
        [NSThread sleepForTimeInterval:3];
        NSLog(@"end task 1");
        dispatch_async(dispatch_get_main_queue(), ^{
            if (block) {
                block();
            }
        });
    });
}

- (void)sendRequest2:(void (^)(void))block{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"start task 2");
        [NSThread sleepForTimeInterval:3];
        NSLog(@"end task 2");
        dispatch_async(dispatch_get_main_queue(), ^{
            if (block) {
                block();
            }
        });
    });
}

1. 用dispatch_group_async实现

- (void)gcdGroupAsync{
    NSLog(@"主线程");
//    dispatch_group_async 里面,应该放同步代码,而不是异步代码
        dispatch_queue_t queue = dispatch_queue_create("com.gcd.group", DISPATCH_QUEUE_CONCURRENT);
        dispatch_group_t group = dispatch_group_create();
        dispatch_group_async(group, queue, ^{
    
            [self sendRequest:^{
                NSLog(@"sendRequest done");
            }];
        });
    
        dispatch_group_async(group, queue, ^{
            [self sendRequest2:^{
                NSLog(@"sendRequest2 done");
            }];
        });
        dispatch_group_notify(group, queue, ^{
            NSLog(@"all task over");
            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"回到主线程刷新UI");
            });
        });
}

输出结果:显示不符合需求,task1和task2还没有结束,就输出all task over 咯,为啥子呢?
原因:因为dispatch_group_async里面放入的是异步的任务,dispatch_group_async执行了sendRequest这行代码后,就认为sendRequest已经执行完毕了(其实还没有回调回来),group不再持有这个任务,就会执行下面的dispatch_group_async,而sendRequest2同理,group没有任务时,就会执行dispatch_group_notify 里面的任务,所以造成这样子的输出结果。

由此可见:dispatch_group_async里面适合放入同步代码,而不是异步代码。

2018-12-29 17:13:42.710421+0800 Thread[17395:417245] 主线程
2018-12-29 17:13:42.710700+0800 Thread[17395:417334] start task 2
2018-12-29 17:13:42.710726+0800 Thread[17395:417333] all task over
2018-12-29 17:13:42.710708+0800 Thread[17395:417331] start task 1
2018-12-29 17:13:42.733662+0800 Thread[17395:417245] 回到主线程刷新UI
2018-12-29 17:13:45.714431+0800 Thread[17395:417331] end task 1
2018-12-29 17:13:45.714511+0800 Thread[17395:417334] end task 2
2018-12-29 17:13:45.714752+0800 Thread[17395:417245] sendRequest done
2018-12-29 17:13:45.714854+0800 Thread[17395:417245] sendRequest2 done

2.用dispatch_group_enter + dispatch_group_leave 实现

- (void)gcdGroupEnter{
    NSLog(@"主线程");
  
    dispatch_queue_t queue = dispatch_queue_create("com.gcd.group", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_enter(group);
    [self sendRequest:^{
        NSLog(@"sendRequest done");
        dispatch_group_leave(group);
    }];
    
    dispatch_group_enter(group);
    [self sendRequest2:^{
        NSLog(@"sendRequest2 done");
        dispatch_group_leave(group);
    }];
    
    dispatch_group_notify(group, queue, ^{
        NSLog(@"all task over");
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"回到主线程刷新UI");
        });
    });
}

输出结果:当task1 和task2执行结束后,才输出all task over,符合我们的需求。
当我们调用sendRequest时,先调用dispatch_group_enter时,任务回调后再调用dispatch_group_leave,整个异步操作中,任务是被group持有的,只有回调结束后才离开group,所以不会出现上面的问题。

注意:dispatch_group_enterdispatch_group_leave成对存在

2018-12-29 17:32:12.739361+0800 Thread[17557:425760] 主线程
2018-12-29 17:32:12.739608+0800 Thread[17557:425809] start task 1
2018-12-29 17:32:12.739608+0800 Thread[17557:425810] start task 2
2018-12-29 17:32:15.742987+0800 Thread[17557:425809] end task 1
2018-12-29 17:32:15.742990+0800 Thread[17557:425810] end task 2
2018-12-29 17:32:15.743325+0800 Thread[17557:425760] sendRequest2 done
2018-12-29 17:32:15.743528+0800 Thread[17557:425760] sendRequest done
2018-12-29 17:32:15.743742+0800 Thread[17557:425810] all task over
2018-12-29 17:32:15.744000+0800 Thread[17557:425760] 回到主线程刷新UI


10.dispatch_semaphore(信号量)

dispatch_semaphore 使用计数来完成这个问题,计数为0 ,不可以通过,计数大于等于1,可以通过
其中有三个函数分别为:

  1. dispatch_semaphore_create :创建semaphore并初始化信号量,初始化的值大于等于0;
  2. dispatch_semaphore_signal:信号量+1;
  3. dispatch_semaphore_wait : 判断当前信号量的值,如果当前信号量大于0,信号量-1,往下执行,如果当前信号量等于0,就会阻塞在当前线程,一直等待。

用处:

1、保持线程同步,将异步任务转化为同步任务
2、保证线程安全,为线程加锁

线程安全:在多个线程中同时访问并操作同一对象时,运行结果与预期的值相同就是线程安全。
线程安全问题都是由全局变量及静态变量引起的,若每个线程中对全局变量静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

线程同步:可理解为线程A和B一块配合,A执行到一定程度时要依靠B的某个结果,于是停下来,示意B运行;B依言执行,再将结果给A;A再继续操作。

10.1.保持线程同步,将异步任务转化为同步任务

这个也可以用来实现上面的需求1

- (void)gcdSemaphore{
    NSLog(@"主线程");
    dispatch_queue_t queue = dispatch_queue_create("com.xiuxiu.gcd", DISPATCH_QUEUE_CONCURRENT);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    dispatch_async(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任务一,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
        long x = dispatch_semaphore_signal(semaphore);
        NSLog(@"signal后的信号量 = %ld",x);
    });
    long x = dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"wait后的信号量 = %ld",x);
    NSLog(@"---dispatch_semaphore_wait-----");
    dispatch_async(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任务三,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    dispatch_async(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任务四,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
}

输出结果:

  1. 先输出任务一,再输出任务三、四
  2. dispatch_semaphore_wait在主线程输出(信号量为0 ,阻塞在当前线程)

我们在主线程创建了一个信号量赋值为0,并开辟了一个并行队列异步执行任务一,因为是一个异步操作,此时不会等待任务一执行结束, 直接执行到dispatch_semaphore_wait,此时判断出信号量的值0,不可通行,阻塞当前线程,当运行到dispatch_semaphore_signal时,信号量加1后等于1大于0,可通行,执行dispatch_semaphore_wait后面的任务三、四,通行后信号量减1等于0。

2018-12-29 15:22:24.960944+0800 Thread[16315:368237] 主线程
2018-12-29 15:22:24.961180+0800 Thread[16315:368286]  任务一,i = 0
2018-12-29 15:22:25.965568+0800 Thread[16315:368286]  任务一,i = 1
2018-12-29 15:22:26.970160+0800 Thread[16315:368286]  任务一,i = 2
2018-12-29 15:22:27.975627+0800 Thread[16315:368237] wait后的信号量 = 0
2018-12-29 15:22:27.975627+0800 Thread[16315:368286] signal后的信号量 = 1
2018-12-29 15:22:27.975956+0800 Thread[16315:368237] ---dispatch_semaphore_wait-----
2018-12-29 15:22:27.976232+0800 Thread[16315:368287]  任务四,i = 0
2018-12-29 15:22:27.976285+0800 Thread[16315:368286]  任务三,i = 0
2018-12-29 15:22:28.978431+0800 Thread[16315:368286]  任务三,i = 1
2018-12-29 15:22:28.978445+0800 Thread[16315:368287]  任务四,i = 1
2018-12-29 15:22:29.980405+0800 Thread[16315:368286]  任务三,i = 2
2018-12-29 15:22:29.980405+0800 Thread[16315:368287]  任务四,i = 2

10.2.保证线程安全,为线程加锁

在上面讲NSThread的时候,讲过synchronizedNSCondition加锁方式,这里使用dispatch_semaphore进行加密,运行结果和上面一致。

- (void)sale{
    while (1) {
        //1、synchronized
//        @synchronized (self) {
//            if (self.tickets > 0 ) {
//                [NSThread sleepForTimeInterval:0.1];
//                self.tickets --;
//                self.saleCount = Total - self.tickets;
//                NSLog(@"%@ , 卖出 = %d,剩余= %d",[NSThread currentThread].name,self.saleCount,self.tickets);
//            }else{
//                break;//一定要break,不然就会死循环
//            }
//        }
//        2、NSCondition
//        [self.condition lock];
//        if (self.tickets > 0 ) {
//            [NSThread sleepForTimeInterval:0.1];
//            self.tickets --;
//            self.saleCount = Total - self.tickets;
//            NSLog(@"%@ , 卖出 = %d,剩余= %d",[NSThread currentThread].name,self.saleCount,self.tickets);
//        }else{
//            break;
//        }
//        [self.condition unlock];
//
        //3、dispatch_semaphore方式
        dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
        if (self.tickets > 0 ) {
            [NSThread sleepForTimeInterval:0.1];
            self.tickets --;
            self.saleCount = Total - self.tickets;
            NSLog(@"%@ , 卖出 = %d,剩余= %d",[NSThread currentThread].name,self.saleCount,self.tickets);
        }else{
            dispatch_semaphore_signal(self.semaphore);
            break;
        }
        dispatch_semaphore_signal(self.semaphore);
    }
}

信号量还需要多看点资料,这里就先这样子吧~~

上面就保持线程同步,将异步任务转化为同步任务,保证线程安全,给线程加锁就讲了好多种方式,选择的时候,针对需求而言来选择一个较好的方式就OK啦~

参考博客:

iOS多线程慕课网视频
深入理解iOS开发中的锁

文章链接:
iOS 多线程- pThread和NSThread
iOS 多线程-NSOperation + NSOperationQueue

喜欢就点个赞吧✌️✌️
有错之处,还请指出,感谢🙏🙏

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容