iOS开发多线程--GCD

GCD简介

Grand Central Dispatch (GCD)是Apple开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。它是一个在线程池模式的基础上执行的并行任务。在Mac OS X 10.6雪豹中首次推出,也可在IOS 4及以上版本使用。 -----百度百科

GCD好处

  • GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
  • GCD更接近底层,性能较高

GCD的两个概念

1.任务
任务就是我们实际要执行的代码块。而执行这代码的方式有两种:同步执行(sync)异步执行(async)

  • 同步执行(sync)
    • 不具备开启新线程的能力,只能在当前线程中执行任务
    • 同步任务添加到队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后才会继续执行
  • 异步执行(async)
    • 可以开启新线程,可以在新线程中执行任务
    • 异步任务添加到队列中,会继续执行任务

异步执行async虽然有开启新线程的能力,但是不一定会开启新线程,这根任务所指定的队列类型有关。

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

  • 串行队列(Serial Dispatch Queue)
    • 每次只有一个任务被执行,让任务一个接着一个执行。(只开启一个线程)
  • 并发队列(Concurrent Dispatch Queue)
    • 可以让多个任务并发(同时)执行。(可以开启多个线程,并且同时执行任务)

并发队列的并发功能只有在异步(dispatch_async)函数下才有效

GCD使用步骤

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

dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);

label的参数表示队列的唯一标识符,用于debug,可以传空,推荐使用域名反写,attr用来决定是串行队列,还是并发队列。 DISPATCH_QUEUE_SERIAL表示串行队列,DISPATCH_QUEUE_CONCURRENT表示并发队列。

dispatch_queue_t queue = dispatch_queue_create("com.easyfly.queue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue = dispatch_queue_create("com.easyfly.queue", DISPATCH_QUEUE_CONCURRENT);
  • 串行队列,GCD提供了一个特殊的串行队列:主队列(Main Dispatch Queue)
    • 所有放在主队列中任务,都会放到主线程中执行
    • 可以使用dispatch_get_main_queue()来获取主队列
// 主队列的获取方法
dispatch_queue_t queue = dispatch_get_main_queue();
  • 并发队列,GCD提供了全局并发队列(Global Dispatch Queue)
    • 可以使用dispatch_get_global_queue来获取。
dispatch_queue_t dispatch_get_global_queue(long identifier, unsigned long flags);

identifier的优先级从高到低:
DISPATCH_QUEUE_PRIORITY_HIGH
DISPATCH_QUEUE_PRIORITY_DEFAULT
DISPATCH_QUEUE_PRIORITY_LOW
DISPATCH_QUEUE_PRIORITY_BACKGROUND

flags:默认传0

2.将任务添加到任务的等待队列中
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先打印了,说明线程并没有等待,开启了新线程,在新线程中执行任务。

3.同步 + 串行

/**
 不会创建新的线程,在当前线程执行,任务串行执行
 */
- (void)syncSerial{
    NSLog(@"currentThread -- %@", [NSThread currentThread]);
    
    NSLog(@"begin");
    
    dispatch_queue_t queueu = dispatch_queue_create("com.easyfly.queue", DISPATCH_QUEUE_SERIAL);
    
    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");
}

2018-05-16 14:59:32.394332+0800 MultiThread[5889:426076] currentThread -- <NSThread: 0x604000074000>{number = 1, name = main}
2018-05-16 14:59:32.394566+0800 MultiThread[5889:426076] begin
2018-05-16 14:59:33.395773+0800 MultiThread[5889:426076] 111----<NSThread: 0x604000074000>{number = 1, name = main}
2018-05-16 14:59:34.397207+0800 MultiThread[5889:426076] 111----<NSThread: 0x604000074000>{number = 1, name = main}
2018-05-16 14:59:35.398690+0800 MultiThread[5889:426076] 222----<NSThread: 0x604000074000>{number = 1, name = main}
2018-05-16 14:59:36.399925+0800 MultiThread[5889:426076] 222----<NSThread: 0x604000074000>{number = 1, name = main}
2018-05-16 14:59:37.400594+0800 MultiThread[5889:426076] 333----<NSThread: 0x604000074000>{number = 1, name = main}
2018-05-16 14:59:38.401101+0800 MultiThread[5889:426076] 333----<NSThread: 0x604000074000>{number = 1, name = main}
2018-05-16 14:59:38.401345+0800 MultiThread[5889:426076] end

从上述例子可以看出:

  • 所有任务都是在当前线程(主线程)中执行的,并没有开启新的线程(同步执行不具备开启新线程的能力)
  • 需要等待队列任务的任务完成
  • 任务按顺序执行

4.异步 + 串行

/**
 创建新线程,但是任务是串行的,会一个一个执行
 */
- (void)asyncSerial{
    NSLog(@"currentThread -- %@", [NSThread currentThread]);
    
    NSLog(@"begin");
    
    dispatch_queue_t queueu = dispatch_queue_create("com.easyfly.queue", DISPATCH_QUEUE_SERIAL);
    
    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 15:08:48.563433+0800 MultiThread[6045:441312] currentThread -- <NSThread: 0x60400006b400>{number = 1, name = main}
2018-05-16 15:08:48.563794+0800 MultiThread[6045:441312] begin
2018-05-16 15:08:48.564524+0800 MultiThread[6045:441312] end
2018-05-16 15:08:49.569620+0800 MultiThread[6045:441364] 111----<NSThread: 0x600000464080>{number = 5, name = (null)}
2018-05-16 15:08:50.570245+0800 MultiThread[6045:441364] 111----<NSThread: 0x600000464080>{number = 5, name = (null)}
2018-05-16 15:08:51.574290+0800 MultiThread[6045:441364] 222----<NSThread: 0x600000464080>{number = 5, name = (null)}
2018-05-16 15:08:52.578832+0800 MultiThread[6045:441364] 222----<NSThread: 0x600000464080>{number = 5, name = (null)}
2018-05-16 15:08:53.580927+0800 MultiThread[6045:441364] 333----<NSThread: 0x600000464080>{number = 5, name = (null)}
2018-05-16 15:08:54.586438+0800 MultiThread[6045:441364] 333----<NSThread: 0x600000464080>{number = 5, name = (null)}

从上述例子可以看出:

  • 开启了一个新的线程
  • 所有任务都是在begin和end打印后执行的
  • 任务是按顺序执行的

5.同步 + 主队列

  1. 在主线程中调用
/**
 卡死
 */
- (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");
}

2018-05-16 15:15:40.866943+0800 MultiThread[6145:451264] currentThread -- <NSThread: 0x604000075c80>{number = 1, name = main}
2018-05-16 15:15:40.867132+0800 MultiThread[6145:451264] begin
(lldb)

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

  1. 在其他线程中调用
[NSThread detachNewThreadSelector:@selector(syncMain) toTarget:self withObject:nil];

2018-05-16 15:21:21.114561+0800 MultiThread[6228:461243] currentThread -- <NSThread: 0x600000470600>{number = 5, name = (null)}
2018-05-16 15:21:21.114724+0800 MultiThread[6228:461243] begin
2018-05-16 15:21:22.153793+0800 MultiThread[6228:461100] 111----<NSThread: 0x60400006f180>{number = 1, name = main}
2018-05-16 15:21:23.154493+0800 MultiThread[6228:461100] 111----<NSThread: 0x60400006f180>{number = 1, name = main}
2018-05-16 15:21:24.156163+0800 MultiThread[6228:461100] 222----<NSThread: 0x60400006f180>{number = 1, name = main}
2018-05-16 15:21:25.157599+0800 MultiThread[6228:461100] 222----<NSThread: 0x60400006f180>{number = 1, name = main}
2018-05-16 15:21:26.159337+0800 MultiThread[6228:461100] 333----<NSThread: 0x60400006f180>{number = 1, name = main}
2018-05-16 15:21:27.160866+0800 MultiThread[6228:461100] 333----<NSThread: 0x60400006f180>{number = 1, name = main}
2018-05-16 15:21:27.161208+0800 MultiThread[6228:461243] end

上述例子可以看出:

  • 所有任务都是在主线程中执行,没有开启新线程
  • 同步执行任务都是需要等待队列的任务执行结束
  • 串行队列,每次只执行一个任务

因为syncMain()任务放在了新开启的线程中,而打印任务都追加在主队列中,此时主队列并没有其他执行的任务,所以可以直接执行打印任务。

6.异步 + 主队列

/**
 只在主线程中执行任务,执行完一个任务,再执行下一个任务
 */
- (void)asyncMain{
    NSLog(@"currentThread -- %@", [NSThread currentThread]);
    
    NSLog(@"begin");
    
    dispatch_queue_t queueu = dispatch_get_main_queue();
    
    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");
}

018-05-16 15:28:29.018407+0800 MultiThread[6308:470456] currentThread -- <NSThread: 0x60400007fa40>{number = 1, name = main}
2018-05-16 15:28:29.018601+0800 MultiThread[6308:470456] begin
2018-05-16 15:28:29.018752+0800 MultiThread[6308:470456] end

2018-05-16 15:28:30.056933+0800 MultiThread[6308:470456] 111----<NSThread: 0x60400007fa40>{number = 1, name = main}
2018-05-16 15:28:31.057944+0800 MultiThread[6308:470456] 111----<NSThread: 0x60400007fa40>{number = 1, name = main}
2018-05-16 15:28:32.059107+0800 MultiThread[6308:470456] 222----<NSThread: 0x60400007fa40>{number = 1, name = main}
2018-05-16 15:28:33.060579+0800 MultiThread[6308:470456] 222----<NSThread: 0x60400007fa40>{number = 1, name = main}
2018-05-16 15:28:34.062021+0800 MultiThread[6308:470456] 333----<NSThread: 0x60400007fa40>{number = 1, name = main}
2018-05-16 15:28:35.063583+0800 MultiThread[6308:470456] 333----<NSThread: 0x60400007fa40>{number = 1, name = main}

从上述例子中可以看出:

  • 所有任务都是在主队列中执行
  • 异步执行不会做人接等待,可以继续执行任务
  • 任务按顺序执行的,主队列是串行队列

GCD线程之间的通信

在其他线程中处理下载图片,请求数据等操作,在主线程中刷新UI,数据,就需要用到线程之间的通讯。

- (void)communication{
    dispatch_queue_t global = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_queue_t main = dispatch_get_main_queue();
    
    dispatch_async(global, ^{
        for (int i = 0; i < 3; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"1--- %@", [NSThread currentThread]);
        }
        
        dispatch_async(main, ^{
            [NSThread sleepForTimeInterval:2];
            NSLog(@"2---%@", [NSThread currentThread]);
        });
    });
}

2018-05-16 15:47:23.444937+0800 MultiThread[6516:498644] 1--- <NSThread: 0x600000277f00>{number = 5, name = (null)}
2018-05-16 15:47:25.450123+0800 MultiThread[6516:498644] 1--- <NSThread: 0x600000277f00>{number = 5, name = (null)}
2018-05-16 15:47:27.455633+0800 MultiThread[6516:498644] 1--- <NSThread: 0x600000277f00>{number = 5, name = (null)}
2018-05-16 15:47:29.456492+0800 MultiThread[6516:498523] 2---<NSThread: 0x6040000647c0>{number = 1, name = main}

从上述例子中可以看出:

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

GCD的其他方法

1.栅栏方法(dispatch_barrier_async)

如果我们需要实现异步请求两个数据,同时B请求必须在A请求完成后才能继续请求,这就需要一种手段分割开A和B请求,这就是栅栏方法dispatch_barrier_asyncdispatch_barrier_async方法会等待前面添加到并发队列中的任务完成后,截止执行dispatch_barrier_async的block任务,最后回复正常的异步并发操作。

- (void)barrier{
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        // 追加任务1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    dispatch_async(queue, ^{
        // 追加任务2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    
    dispatch_barrier_async(queue, ^{
        // 追加任务 barrier
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程
        }
    });
    
    dispatch_async(queue, ^{
        // 追加任务3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    dispatch_async(queue, ^{
        // 追加任务4
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"4---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
}

2018-05-16 16:08:35.729731+0800 MultiThread[6772:531874] 1---<NSThread: 0x604000465b00>{number = 5, name = (null)}
2018-05-16 16:08:35.729844+0800 MultiThread[6772:531870] 2---<NSThread: 0x604000473f80>{number = 6, name = (null)}
2018-05-16 16:08:37.732856+0800 MultiThread[6772:531870] 2---<NSThread: 0x604000473f80>{number = 6, name = (null)}
2018-05-16 16:08:37.732856+0800 MultiThread[6772:531874] 1---<NSThread: 0x604000465b00>{number = 5, name = (null)}
2018-05-16 16:08:39.735677+0800 MultiThread[6772:531870] barrier---<NSThread: 0x604000473f80>{number = 6, name = (null)}
2018-05-16 16:08:41.736375+0800 MultiThread[6772:531870] barrier---<NSThread: 0x604000473f80>{number = 6, name = (null)}
2018-05-16 16:08:43.740706+0800 MultiThread[6772:531870] 3---<NSThread: 0x604000473f80>{number = 6, name = (null)}
2018-05-16 16:08:43.740710+0800 MultiThread[6772:531874] 4---<NSThread: 0x604000465b00>{number = 5, name = (null)}
2018-05-16 16:08:45.741610+0800 MultiThread[6772:531870] 3---<NSThread: 0x604000473f80>{number = 6, name = (null)}
2018-05-16 16:08:45.741610+0800 MultiThread[6772:531874] 4---<NSThread: 0x604000465b00>{number = 5, name = (null)}

上述例子可以看出:

  • 在执行完栅栏前面的操作之后,才执行栅栏操作,最后再执行栅栏后边的操作

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]);
    });
}

2018-05-16 16:16:26.406706+0800 MultiThread[6866:543950] currentThread---<NSThread: 0x60000007f6c0>{number = 1, name = main}
2018-05-16 16:16:28.600856+0800 MultiThread[6866:543950] after---<NSThread: 0x60000007f6c0>{number = 1, name = main}

可以看出来,实际还是存在误差的,所以要求不严格可以说还是用这个方法。

3.GCD一次性代码(dispatch_once)
创建单例、或者需要整个程序运行期间只执行一次的代码,我们就用到了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");
}

2018-05-16 16:34:57.901523+0800 MultiThread[7186:573598] apply---begin
2018-05-16 16:34:57.901834+0800 MultiThread[7186:573598] 0---<NSThread: 0x604000061f40>{number = 1, name = main}
2018-05-16 16:34:57.901899+0800 MultiThread[7186:573672] 1---<NSThread: 0x6000002683c0>{number = 5, name = (null)}
2018-05-16 16:34:57.901937+0800 MultiThread[7186:573662] 2---<NSThread: 0x600000267400>{number = 6, name = (null)}
2018-05-16 16:34:57.901974+0800 MultiThread[7186:573665] 3---<NSThread: 0x600000268280>{number = 7, name = (null)}
2018-05-16 16:34:57.902303+0800 MultiThread[7186:573598] 4---<NSThread: 0x604000061f40>{number = 1, name = main}
2018-05-16 16:34:57.902560+0800 MultiThread[7186:573598] apply---end

5.GCD队列组(dispatch_group)

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

5.1 dispatch_group_create
用于创建任务组

dispatch_group_t dispatch_group_create(void);

5.2 dispatch_group_async
把异步任务提交到指定的任务组和指定的队列执行

void dispatch_group_async(dispatch_group_t group,
                          dispatch_queue_t queue,
                          dispatch_block_t block);
  • group ——对应的任务组,之后可以通过dispatch_group_wait或者dispatch_group_notify监听任务组内任务的执行情况
  • queue ——block任务执行的线程队列,任务组内不同任务的队列可以不同
  • block —— 执行任务的block

5.3 dispatch_group_enter
用于添加对应任务组中的未执行完毕的任务数,执行一次,未执行完毕的任务数加1,当未执行完毕任务数为0的时候,才会使dispatch_group_wait解除阻塞和dispatch_group_notify的block执行

void dispatch_group_enter(dispatch_group_t group);

dispatch_group_leave
用于减少任务组中的未执行完毕的任务数,执行一次,未执行完毕的任务数减1,dispatch_group_enterdispatch_group_leave要匹配,不然系统会认为group任务没有执行完毕

void dispatch_group_leave(dispatch_group_t group);

5.4 dispatch_group_wait
等待组任务完成,会阻塞当前线程,当任务组执行完毕时,才会解除阻塞当前线程

long dispatch_group_wait(dispatch_group_t group, 
                         dispatch_time_t timeout);
  • group ——需要等待的任务组
  • timeout ——等待的超时时间(即等多久),单位为dispatch_time_t。如果设置为DISPATCH_TIME_FOREVER,则会一直等待(阻塞当前线程),直到任务组执行完毕

5.5 dispatch_group_notify
待任务组执行完毕时调用,不会阻塞当前线程

void dispatch_group_notify(dispatch_group_t group,
                           dispatch_queue_t queue, 
                           dispatch_block_t block);
  • group ——需要监听的任务组
  • queue ——block任务执行的线程队列,和之前group执行的线程队列无关
  • block ——任务组执行完毕时需要执行的任务block

示例

- (void)group{
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    NSLog(@"group one start");
    NSLog(@"CurrentThread --- %@", [NSThread currentThread]);
    dispatch_group_async(group, queue, ^{
        dispatch_async(queue, ^{
            NSLog(@"dispatch_async --- %@", [NSThread currentThread]);
            sleep(3);  //模拟异步请求
            NSLog(@"group one finished");
        });
    });
    
    dispatch_group_notify(group, queue, ^{
        NSLog(@"dispatch_group_notify --- %@", [NSThread currentThread]);
        NSLog(@"group finished");
    });
}

2018-05-17 09:32:09.284063+0800 MultiThread[10977:949950] group one start
2018-05-17 09:32:09.284450+0800 MultiThread[10977:949950] CurrentThread --- <NSThread: 0x604000077880>{number = 1, name = main}
2018-05-17 09:32:09.284987+0800 MultiThread[10977:950236] dispatch_async --- <NSThread: 0x6000004794c0>{number = 5, name = (null)}
2018-05-17 09:32:09.285092+0800 MultiThread[10977:950242] dispatch_group_notify --- <NSThread: 0x600000479a00>{number = 6, name = (null)}
2018-05-17 09:32:09.286039+0800 MultiThread[10977:950242] group finished
2018-05-17 09:32:12.291271+0800 MultiThread[10977:950236] group one finished

可以看出来,在group中嵌套了一个异步任务是,开启了一个新线程,新线程是直接返回,group就认为任务完成了。解决上述问题,就需要用了另外两个方法。

- (void)group{
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    NSLog(@"group one start");
    dispatch_group_enter(group);
    NSLog(@"CurrentThread --- %@", [NSThread currentThread]);
    dispatch_group_async(group, queue, ^{
        dispatch_async(queue, ^{
            NSLog(@"dispatch_async --- %@", [NSThread currentThread]);
            sleep(3);  //模拟异步请求
            NSLog(@"group one finished");
            dispatch_group_leave(group);
        });
    });
    
    dispatch_group_notify(group, queue, ^{
        NSLog(@"dispatch_group_notify --- %@", [NSThread currentThread]);
        NSLog(@"group finished");
    });
}

2018-05-17 09:36:48.794270+0800 MultiThread[11054:959258] group one start
2018-05-17 09:36:48.794480+0800 MultiThread[11054:959258] CurrentThread --- <NSThread: 0x604000075080>{number = 1, name = main}
2018-05-17 09:36:48.794824+0800 MultiThread[11054:959669] dispatch_async --- <NSThread: 0x60000046d400>{number = 5, name = (null)}
2018-05-17 09:36:51.798630+0800 MultiThread[11054:959669] group one finished
2018-05-17 09:36:51.798986+0800 MultiThread[11054:959669] dispatch_group_notify --- <NSThread: 0x60000046d400>{number = 5, name = (null)}
2018-05-17 09:36:51.799302+0800 MultiThread[11054:959669] group finished

刚开始,未完成任务数量+1,等到异步任务完成后,数量-1,然后通知group,任务完成了,就会回调dispatch_group_notify的block内容。

6. GCD信号量(dispatch_semaphore)

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

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

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

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

dispatch_semaphore_signal(semaphore);

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

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

注意:dispatch_semaphore_wait
当信号量的总数<=0时候,该函数所在的线程就会等待,而信号量的总数>0的时候,该函数就会继续往下执行,同时信号量的总数-1
这里有个等待时间的参数,如果在等待的时间内获得了信号量,那么函数继续往下执行,如果等待时间内信号量一直为0,那么函数也会继续往下执行了

/**
 线程同步
 */
- (void)semaphoreDemoOne{
    NSLog(@"currentThread --- %@", [NSThread currentThread]);
    NSLog(@"semaphore --- begin");
    
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    __block int number = 0;
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"1---%@", [NSThread currentThread]);
        
        number = 100;
        
        dispatch_semaphore_signal(semaphore);
    });
    
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"samaphore --- end, number = %d", number);
}

2018-05-17 10:35:31.468369+0800 MultiThread[11654:1048990] currentThread --- <NSThread: 0x60000006f280>{number = 1, name = main}
2018-05-17 10:35:31.468523+0800 MultiThread[11654:1048990] semaphore --- begin
2018-05-17 10:35:33.472110+0800 MultiThread[11654:1049057] 1---<NSThread: 0x600000460940>{number = 5, name = (null)}
2018-05-17 10:35:33.472321+0800 MultiThread[11654:1048990] samaphore --- end, number = 100

如果注释dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);这句代码,number 就等于0了。block块异步执行添加到了全局并发队列里,所以程序在主线程会跳过block块(同时开辟子线程异步执行block块),执行块外的代码dispatch_semaphore_wait,因为semaphore信号量为0,且时间为DISPATCH_TIME_FOREVER,所以会阻塞当前线程(主线程),进而只执行子线程的block块,直到执行块内部的dispatch_semaphore_signal使得信号量+1。正在被阻塞的线程(主线程)会恢复继续执行。这样保证了线程之间的同步。

售票加锁示例

/**
 模拟两个窗口售票
 */
- (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] 所有票均已售完

如果注释semaphore相关代码,就会出现一张票卖了两次的情况。打开注释后

2018-05-17 11:00:22.375800+0800 MultiThread[12051:1089200] 剩余票 --- 9 窗口 --- <NSThread: 0x600000469d00>{number = 5, name = (null)}
2018-05-17 11:00:22.376091+0800 MultiThread[12051:1089202] 剩余票 --- 8 窗口 --- <NSThread: 0x604000463e80>{number = 6, name = (null)}
2018-05-17 11:00:22.376561+0800 MultiThread[12051:1089200] 剩余票 --- 7 窗口 --- <NSThread: 0x600000469d00>{number = 5, name = (null)}
2018-05-17 11:00:22.376950+0800 MultiThread[12051:1089202] 剩余票 --- 6 窗口 --- <NSThread: 0x604000463e80>{number = 6, name = (null)}
2018-05-17 11:00:22.377360+0800 MultiThread[12051:1089200] 剩余票 --- 5 窗口 --- <NSThread: 0x600000469d00>{number = 5, name = (null)}
2018-05-17 11:00:22.377834+0800 MultiThread[12051:1089202] 剩余票 --- 4 窗口 --- <NSThread: 0x604000463e80>{number = 6, name = (null)}
2018-05-17 11:00:22.378057+0800 MultiThread[12051:1089200] 剩余票 --- 3 窗口 --- <NSThread: 0x600000469d00>{number = 5, name = (null)}
2018-05-17 11:00:22.378413+0800 MultiThread[12051:1089202] 剩余票 --- 2 窗口 --- <NSThread: 0x604000463e80>{number = 6, name = (null)}
2018-05-17 11:00:22.378687+0800 MultiThread[12051:1089200] 剩余票 --- 1 窗口 --- <NSThread: 0x600000469d00>{number = 5, name = (null)}
2018-05-17 11:00:22.378899+0800 MultiThread[12051:1089202] 剩余票 --- 0 窗口 --- <NSThread: 0x604000463e80>{number = 6, name = (null)}
2018-05-17 11:00:22.378996+0800 MultiThread[12051:1089200] 所有票均已售完
2018-05-17 11:00:22.379244+0800 MultiThread[12051:1089202] 所有票均已售完

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

参考资料

iOS多线程:『GCD』详尽总结
iOS GCD之dispatch_semaphore(信号量)
关于iOS多线程,你看我就够了

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

推荐阅读更多精彩内容

  • 本文首发于我的个人博客:「程序员充电站」[https://itcharge.cn]文章链接:「传送门」[https...
    ITCharge阅读 347,129评论 308 1,924
  • iOS多线程实践中,常用的就是子线程执行耗时操作,然后回到主线程刷新UI。在iOS中每个进程启动后都会建立一个主线...
    jackyshan阅读 1,435评论 2 12
  • 简介:为什么要用 GCD 呢?因为 GCD 有很多好处啊,具体如下:GCD 可用于多核的并行运算GCD 会自动利用...
    WorldPeace_hp阅读 374评论 0 4
  • 1、GCD简介 全名:Grand Central Dispatch,它是苹果为多核的并行运算提出的解决方案,会合理...
    i_belive阅读 3,095评论 0 25
  • 呵呵,算法一次主要由程序员使用于当他们不想解释他们在做什么的时候。
    孙亖阅读 481评论 0 1