
多个任务,执行完一个才能执行另一个,也就是排队式执行任务。
多个线程在单个核心运行,同一时间只能一个线程运行,系统不停的切换线程,看起来就像是同时运行,实际是多个线程不停切换。

每个线程分配给独立的核心,线程同时运行。

多线程编辑,顾名思义就是多个线程可以共同执行,一个进程中至少拥有一个线程,当然我们也可以定义多个线程,每个线程都完成自己对应的工作,并且各个线程之间可以并行执行代码,这样在就实现了一个程序共同执行多段代码的功能,大大的提高了程序的运行效率,这就是多线程编辑。

百度:
Grand Central Dispatch(GCD) 是 Apple 开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。它是一个在线程池模式的基础上执行的并发任务。在 Mac OS X 10.6 雪豹中首次推出,也可在 iOS 4 及以上版本使用。
苹果官方:
Grand Central Dispatch(GCD) 是异步执行任务的技术之一。一般将应用程序中记述的线程管理用的代码在系统级实现。开发者只需要定义想执行的任务并追加到适当的Dispatch Queue中,GCD就能生成必要的线程并计划执行任务。由于线程管理是作为系统的一部分来实现的,因此可统一管理,也可执行任务,这样就比以前的线程更有效率。
GCD用非常简洁的记述方法,实现了极为复杂的多线程编程。
任务: 就是执行操作的意思,也就是要在线程中执行的那段代码。在GCD中就是放在Block中的内容。
具体对任务的执行有两种方式:同步执行和异步执行,主要区别是:是否等待队列的任务执行结束,以及是否具备开启新线程的能力。
执行任务的两种方式:
sync):
dispatch_sync,这个函数会把一个block加入到指定的队列中,而且会一直等到执行完blcok,这个函数才返回。因此在block执行完之前,调用dispatch_sync方法的线程是阻塞的。
async):
使用
dispatch_async,这个函数也会把一个block加入到指定的队列中,但是和同步执行不同的是,这个函数把block加入队列后不等block的执行就立刻返回了。
举个例子:我们要给A和B打电话
这里异步执行只是具备开启新线程的能力,但不一定会开启新线程,是否开启新线程还与队列的类型有关。
队列(Dispatch Queue):这里的队列指执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的线性表,采用先进先出的原则,即新任务总是会被插在队尾,而读取任务则总是从队列的头部开始读取,每读一个任务,则从队列中释放一个任务。

GCD中有两种队列:
注意:并发队列的并发功能只有在异步执行(async)方法下才有效,这个队列中的任务也是按着先进先出的方式开始执行,但结束时间不确定。


我们使用dispatch_queue_create方法创建队列Dispatch Queue。
dispatch_queue_t myQueue = dispatch_queue_create("xiyouedc", DISPATCH_QUEUE_CONCURRENT);
Dispatch Queue的名称,它是一个char *类型,可以为空。
队列的名称推荐使用应用程序 ID 这种逆序全程域名。
NULL或DISPATCH_QUEUE_SERIAL,生成Serial Dispatch Queue(串行队列),指定为DISPATCH_QUEUE_CONCURRENT,生成Concurrent Dispatch Queue(并行队列)。一个串行队列(Serial Dispatch Queue)只是用一个线程。而当生成多个Serial Dispatch Queue时,各串行队列将并行执行。但如果过多使用多线程,就会消耗大量内存,引起大量的上下文切换,大幅度的降低系统的响应性能。而对于并发队列(Concurrent Dispatch Queue)来说,不管生成多少并发队列,由于XNU内核只使用有效管理的线程,因此不会发生上述问题。

另外,当多个线程更新相同资源导致数据竞争时,可使用串行队列Serial Dispatch Queue强制让他们一个一个的更新数据,这样就不会造成数据冲突了。

我们一般在需要更新数据的时候使用Serial Dispatch Queue(串行队列),而在获取数据的时候一般使用Concurrent Dispatch Queue(并发队列),因为更新数据的时候可能会出现数据冲突,使用串行队列来避免,在获取数据的时候没有这个冲突,所以我们怎么快怎么来。
队列的获取系统有给我们提供相应的方法:
// 获取主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
在主线程中执行的Dispatch Queue,因为主线程只有一个,所以Main Dispatch Queue是串行队列(Serial Dispatch Queue)。
追加到Main Dispatch Queue的处理会在主线程的RunLoop中执行。
它是所有应用程序都能够使用的并发队列(Concurrent Dispatch Queue)。
有四个优先级:高优先级、默认优先级、低优先级、后台优先级。
通过XNU内核管理的用于全局并发队列Global Dispatch Queue的线程,将各自使用的Global Dispatch Queue的执行优先级作为线程的执行优先级使用,所以在向Global Dispatch Queue追加任务时,应选择与处理内容对应的执行优先级的Global Dispatch Queue。
// 获取全局并发队列
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
这两种方法同时也可以作为另一种获取串行队列和并发队列的获取方法,不过主队列可能会有点特殊,要特别注意使用。
GCD提供了同步执行任务的创建方法dispatch_sync和异步执行任务的创建方法dispatch_async:
// 同步执行任务创建方法
dispatch_sync(queue, ^{
// 这里放同步执行任务代码
});
// 异步执行任务创建方法
dispatch_async(queue, ^{
// 这里放异步执行任务代码
});
我们上面说到了两种任务执行的方法(同步执行、异步执行)和两种队列(串行队列、并发队列),那么现在就出现了4种情况:
假设当前线程为主线程的情况下:不同执行方式 + 不同队列的区别如下:

因为主队列中追加的任务与主线程本身的任务两者相互等待, 阻塞了主线程, 最终导致主线程死锁。
在其他线程中调用 同步执行 + 主队列, 就不会阻塞主队列,不会造成死锁问题,最终结果就是不会开启新线程,串行执行任务。

总结下来就是:主队列 + 同步执行 会导致死锁问题!
没有开启新线程,串行执行任务:
NSLog(@"当前线程:%@", [NSThread currentThread]);
NSLog(@"begin");
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"任务一执行线程:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"任务二执行线程:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"任务三执行线程:%@", [NSThread currentThread]);
});
NSLog(@"end");
输出结果:

没有开启新线程,串行执行任务:
NSLog(@"当前线程:%@", [NSThread currentThread]);
NSLog(@"begin");
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"任务一执行线程:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"任务二执行线程:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"任务三执行线程:%@", [NSThread currentThread]);
});
NSLog(@"end");
输出结果:

有开启新线程(1条),串行执行任务:
NSLog(@"当前线程:%@", [NSThread currentThread]);
NSLog(@"begin");
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"任务一执行线程:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"任务二执行线程:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"任务三执行线程:%@", [NSThread currentThread]);
});
NSLog(@"end");
输出结果:

有开启新线程,并发执行任务:
NSLog(@"当前线程:%@", [NSThread currentThread]);
NSLog(@"begin");
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"任务一执行线程:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"任务二执行线程:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"任务三执行线程:%@", [NSThread currentThread]);
});
NSLog(@"end");
输出结果:

所有放在主队列的任务,都会放在主线程中执行。
同步执行 + 主队列 在不同线程中调用的结果是不一样的,在主线程中调用会发生死锁,在其他线程中调用就不会。
出现死锁情况,程序崩溃:
NSLog(@"当前线程:%@", [NSThread currentThread]);
NSLog(@"begin");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"任务一执行线程:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"任务二执行线程:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"任务三执行线程:%@", [NSThread currentThread]);
});
NSLog(@"end");
输出结果:

dispatch_get_main_queue换成其他串行或任意并行队列因为将任务添加到主线程的操作是在新线程中完成的,所以不会造成死锁:
NSLog(@"当前线程:%@", [NSThread currentThread]);
NSLog(@"begin");
// 创建新线程执行syncMain函数
[NSThread detachNewThreadSelector:@selector(syncMain) toTarget:self withObject:nil];
[NSThread sleepForTimeInterval:3];
NSLog(@"end");
- (void)syncMain {
NSLog(@"当前执行syncMain函数的线程:%@", [NSThread currentThread]);
NSLog(@"begin---syncMain");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"任务一执行线程:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"任务二执行线程:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"任务三执行线程:%@", [NSThread currentThread]);
});
NSLog(@"end---syncMain");
}
输出结果:

只在主线程中执行任务,执行完一个任务,再执行下一个任务:
NSLog(@"当前线程:%@", [NSThread currentThread]);
NSLog(@"begin");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"任务一执行线程:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"任务二执行线程:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"任务三执行线程:%@", [NSThread currentThread]);
});
NSLog(@"end");
输出结果:

当我们需要异步执行两组操作,并且在第一组操作进行完了之后才能开始第二组操作,这时候就得用到GCD的栅栏方法了,将我们要执行的各组操作分隔开。

dispatch_barrier_async函数会等待前面追加到并发队列上的任务全部执行完之后,再将指定的任务追加到该并发队列中。然后在dispatch_barrier_async函数追加的任务执行完毕之后,并发队列才恢复为一般动作,追加到该并发队列的任务又开始执行。
NSLog(@"当前线程:%@", [NSThread currentThread]);
NSLog(@"begin");
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任务一执行线程:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任务二执行线程:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任务三执行线程:%@", [NSThread currentThread]);
});
dispatch_barrier_async(queue, ^{
NSLog(@"barrier线程:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任务四执行线程:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任务五执行线程:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任务六执行线程:%@", [NSThread currentThread]);
});
NSLog(@"end");
输出结果:

NSLog(@"当前线程:%@", [NSThread currentThread]);
NSLog(@"begin");
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任务一执行线程:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任务二执行线程:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任务三执行线程:%@", [NSThread currentThread]);
});
dispatch_barrier_sync(queue, ^{
NSLog(@"barrier线程:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任务四执行线程:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任务五执行线程:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任务六执行线程:%@", [NSThread currentThread]);
});
NSLog(@"end");
输出结果:

注意:异步栅栏
The queue you specify should be a concurrent queue that you create yourself using the dispatch_queue_create function. If the queue you pass to this function is a serial queue or one of the global concurrent queues, this function behaves like the dispatch_async function.
您指定的队列应该是您自己使用dispatch_queue_create函数创建的并发队列。如果你传递给这个函数的队列是一个串行队列或一个全局并发队列,该异步栅栏函数的行为类似于dispatch_async函数,就没有什么特殊的了。
下面验证一下这个说法:
NSLog(@"当前线程:%@", [NSThread currentThread]);
NSLog(@"begin");
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任务一执行线程:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任务二执行线程:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任务三执行线程:%@", [NSThread currentThread]);
});
dispatch_barrier_async(queue, ^{
NSLog(@"barrier线程:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任务四执行线程:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任务五执行线程:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任务六执行线程:%@", [NSThread currentThread]);
});
NSLog(@"end");
输出结果:

NSLog(@"当前线程:%@", [NSThread currentThread]);
NSLog(@"begin");
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任务一执行线程:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任务二执行线程:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任务三执行线程:%@", [NSThread currentThread]);
});
dispatch_barrier_async(queue, ^{
NSLog(@"barrier线程:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任务四执行线程:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任务五执行线程:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任务六执行线程:%@", [NSThread currentThread]);
});
NSLog(@"end");
输出结果:

他就是一个线程延时处理操作,如果我们想要在指定时间后执行某个处理,就可以使用它。
但dispatch_after并不是在指定时间后执行处理,而是在指定时间后追加处理到队列中。严格来说,这个时间并不是绝对准确的,但想要大致延迟执行时间,该方法是非常有效的。
dispatch_time_t类型的值。
该值可以使用dispatch_time函数或dispatch_walltime函数获得,
dispatch_time函数用于计算相对时间,dispatch_walltime用于计算绝对时间。
dispatch_time函数能够获取从第一个参数dispatch_time_t类型值中指定的时间开始,到第二个参数指定的毫微秒单位时间时间后的时间。第一个参数经常使用DISPATCH_TIME_NOW,表示现在的时间。
“ull”是C语言的数值字面量,是显示表明类型时使用的字符串(unsign long long)。
SEC 秒
PER 每
NSEC 纳秒
MSEC 毫秒
USEC 微秒
NSLog(@"当前线程:%@", [NSThread currentThread]);
NSLog(@"begin");
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 2ull * NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"dispatch_after任务线程:%@", [NSThread currentThread]);
});
NSLog(@"end");
输出结果:

dispatch_once这个函数我们经常在书写单例的时候用到,它保证其中的代码只执行一次,通过这个函数,即使在多线程的环境下,也可以保证线程安全。
当onceToken为0时,线程执行dispatch_once的block中的代码
当onceToken为-1时,线程跳过dispatch_once的block中的代码
当onceToken为其他值时,线程被阻塞,等待onceToken值改变
NSLog(@"当前线程:%@", [NSThread currentThread]);
NSLog(@"begin");
dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"dispatch_once方法执行的线程:%@", [NSThread currentThread]);
});
dispatch_once(&onceToken, ^{
NSLog(@"dispatch_once方法执行的线程:%@", [NSThread currentThread]);
});
dispatch_once(&onceToken, ^{
NSLog(@"dispatch_once方法执行的线程:%@", [NSThread currentThread]);
});
NSLog(@"end");
输出结果:

类似于for循环遍历方法,GCD给我们提供了快速迭代的方法dispatch_apply。
如果在串行队列中使用dispatch_apply方法,那么就和for循环一样,按照顺序同步进行,那么使用这个方法就没有意义了,但是我们如果在并发队列中使用dispatch_apply方法,dispatch_apply就可以在多个线程同时遍历数据。
注意:无论是串行队列还是并发队列,dispatch_apply都会等待其中的任务执行完才会返回。
NSLog(@"当前线程:%@", [NSThread currentThread]);
NSLog(@"begin");
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(10, queue, ^(size_t iteration) {
NSLog(@"第%zu次任务", iteration);
});
NSLog(@"end");
输出结果:

在追加到Dispatch Queue中的多个处理全部结束后想执行结束处理任务,就可以使用该方法。
如果使用一个串行队列,因为它是逐一执行的,所以只要将结束处理最后追加到队列中即可。但是如果使用并发队列Concurrent Dispatch Queue时,想要做一个全部线程都完成的任务的最终处理任务就需要使用Dispatch Group了。
dispatch_group_async先把任务放在队列中,然后再将队列放入队列组group中,或者使用队列组的dispatch_group_enter、dispatch_group_leave组合来实现dispatch_group_async。dispatch_group_notify回到指定线程执行任务,或者使用dispatch_group_wait回到当前线程继续向下执行,但是dispatch_group_wait会阻塞线程。监听group中任务的完成状态,当全部的任务都执行完成后,追加任务到DIspatch Queue中:
dispatch_group_create函数生成dispatch_group_t类型的Dispatch Groupdispatch_group_async函数:
dispatch_group_notify函数:
NSLog(@"当前线程:%@", [NSThread currentThread]);
NSLog(@"begin");
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"1, %@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"2, %@", [NSThread currentThread]);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"end group线程:%@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"3, %@", [NSThread currentThread]);
});
NSLog(@"end");
输出结果:

该函数如字面意思,wait(等待),一旦调用该函数,该函数就处于调用状态不返回,即执行dispatch_group_wait函数的线程(当前线程)阻塞,在经过该函数中指定的时间 或 指定的group中的任务全部执行完之后,才会往下继续执行,也就是才会执行dispatch_group_wait之后的操作。
NSLog(@"当前线程:%@", [NSThread currentThread]);
NSLog(@"begin");
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"1, %@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"2, %@", [NSThread currentThread]);
});
// 等待上面的任务全部完成后,会往下继续执行(会阻塞当前线程)
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"3, %@", [NSThread currentThread]);
});
NSLog(@"end");
输出结果:

dispatch_group_enter标志着要有一个任务追加到group中,执行一次,相当于group中未执行完毕的任务数+1dispatch_group_leave标志着有一个任务离开了group,执行一次,相当于group中未执行完毕的任务数-1NSLog(@"当前线程:%@", [NSThread currentThread]);
NSLog(@"begin");
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"1, %@", [NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"2, %@", [NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//等前面的异步操作都执行完毕,将该任务添加到主队列,回到主线程执行
NSLog(@"end group执行线程:%@", [NSThread currentThread]);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"3, %@", [NSThread currentThread]);
dispatch_group_leave(group);
});
NSLog(@"end");
输出结果:

dispatch_group_enter和dispatch_group_leave组合,其实等同于dispatch_group _async,其实还有点像MRC中自己实现对象的引用计数,执行前先加入,执行完了不需要了,再移除。当group中所有的任务执行完成之后,才会执行dispatch_group_notify中的任务,跟代码的位置无关,只要你在第一个任务加入到该group之后定义的notify,都是group中的任务全部执行完了,才会调用notify中的任务。
GCD中的信号量是指Dispatch Semaphore,是持有计数的信号,该计数是多线程编程中的计数类型信号。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);:创建一个Semaphore并初始化信号的总量dispatch_semaphore_signal:发送一个信号,信号量+1dispatch_semaphore_wait:信号量减1,信号总量小于0时会一直等待(阻塞所在线程),否则就可以正常运行Dispatch Semaphore在实际开发中主要用于:
在开发中可能会遇到:异步执行耗时操作,需要使用异步执行的结果进行一些额外的操作。换句话说,就是将异步执行任务转换成同步任务来执行。
NSLog(@"当前线程:%@", [NSThread currentThread]);
NSLog(@"begin");
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block int num = 0;
dispatch_async(queue, ^{
NSLog(@"%@", [NSThread currentThread]);
num = 10;
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"end");
NSLog(@"num = %d, 当前线程:%@", num, [NSThread currentThread]);
输出结果:

线程安全:如果代码所在的进程中有多个线程同时运行,而这些线程可能会同时运行这段代码,如果每次运行的结果和单线程运行的结果是一样的,变量的值也与预期的是一样的,就是线程安全的。
若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量就是线程安全的。若有多个线程同时执行写操作(更改变量),一般都需要考虑线程同步,否则就可能影响线程安全,运行出的结果可能并不是预期的结果。
线程同步:可理解为线程A和线程B一起配合,线程A执行到一定程度时要依靠线程B的某个结果/数据,于是停下来,示意B运行。B执行完,再将结果/数据给A,A再继续执行。
线程的同步关系源于他们之间的相互合作,下面通过模拟售卖门票,来实现线程安全和解决线程同步问题:
总共20张门票,有两个售卖门票的窗口,窗口A、窗口B
- (void)initializeTicketInformation {
NSLog(@"begin 当前线程:%@", [NSThread currentThread]);
self.count = 20;
dispatch_queue_t queueA = dispatch_queue_create("queueA", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queueB = dispatch_queue_create("queueB", DISPATCH_QUEUE_SERIAL);
__weak id weakSelf = self;
dispatch_async(queueA, ^{
[weakSelf saleTicket];
});
dispatch_async(queueB, ^{
[weakSelf saleTicket];
});
}
- (void)saleTicket {
while (1) {
if (_count > 0) { // 还有门票,可以继续卖
self.count--;
NSLog(@"剩余门票:%ld,窗口:%@", _count, [NSThread currentThread]);
[NSThread sleepForTimeInterval:0.2];
} else { // 没有门票了,收摊
NSLog(@"门票卖完了");
break;
}
}
}
输出结果:

其中有很多重复的数据,这是不应该出现的,但是由于我们同时有两个线程对count进行修改,并且没有做到数据同步,导致修改后的值并不是我们预期的值,得到的剩余票数是错乱的,那么此时的线程就是不安全的。
线程安全(使用semaphore加锁):
- (void)initializeTicketInformation {
NSLog(@"begin 当前线程:%@", [NSThread currentThread]);
self.count = 20;
self.semaphore = dispatch_semaphore_create(1);
dispatch_queue_t queueA = dispatch_queue_create("queueA", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queueB = dispatch_queue_create("queueB", DISPATCH_QUEUE_SERIAL);
__weak id weakSelf = self;
dispatch_async(queueA, ^{
[weakSelf saleTicket];
});
dispatch_async(queueB, ^{
[weakSelf saleTicket];
});
}
- (void)saleTicket {
while (1) {
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
if (_count > 0) { // 还有门票,可以继续卖
self.count--;
NSLog(@"剩余门票:%ld,窗口:%@", _count, [NSThread currentThread]);
[NSThread sleepForTimeInterval:0.2];
dispatch_semaphore_signal(self.semaphore);
} else { // 没有门票了,收摊
NSLog(@"门票卖完了");
dispatch_semaphore_signal(self.semaphore);
break;
}
}
}
输出结果:

是一种用于保护多线程共享资源的锁,与一般互斥锁不同之处在于:当自旋锁尝试获取锁时以忙等的形式不断的循环检查锁是否可用。当上一个线程的任务没有执行完毕的时候(被锁住),那么下一个线程就会一直等待(不会睡眠),当上一个线程的任务执行完毕,下一个线程会立即执行。
在多CPU环境中,对持有锁较短的程序来说,使用自旋锁代替一般的互斥锁往往能够提高串程序的性能。
举例:
atomic、OSSpinLock、dispatch_semaphore_t
当上一个线程的任务没有执行完毕的时候(被锁住),那么下一个线程会进入睡眠状态等待任务执行完毕。当上一个线程的任务执行完毕,下一个线程会自动会醒然后执行任务。
举例:
pthread_mutex、@synchronized、NSLock、NSConditionLock 、NSCondition、NSRecursiveLock
自旋锁会忙等:所谓忙等,即在访问被锁资源时,调用者线程不会休眠,而是不停循环访问看是否可以占用线程来执行任务,直到被锁资源释放锁。
互斥锁会休眠:所谓休眠,即在访问被锁资源时,调用者线程会休眠,此时CPU可以调度其他线程工作。直到被锁资源释放锁,此时会唤醒休眠线程。
参考房学姐的iOS——GCD详解