多线程概念:https://blog.csdn.net/u011146511/article/details/51348641
NSOperation多线程:https://blog.csdn.net/u011146511/article/details/51348834
GCD多线程:https://blog.csdn.net/u011146511/article/details/77869913
iPhone中的线程应用并不是无节制的,官方给出的资料显示,iPhone OS下的主线程的堆栈大小是1M,第二个线程开始就是512KB,并且该值不能通过编译器开关或线程API函数来更改,只有主线程有直接修改UI的能力。
一个运行着的程序就是一个进程或者叫做一个任务,一个进程至少包含一个线程,线程就是程序的执行流。
Mac和iOS中的程序启动,创建好一个进程的同时,一个线程便开始运作,这个线程叫做主线程。主线成在程序中的位置和其他线程不同,它是其他线程最终的父线程,且所有的界面的显示操作即AppKit或UIKit的操作必须在主线程进行。
系统中每一个进程都有自己独立的虚拟内存空间,而同一个进程中的多个线程则公用进程的内存空间。
每创建一个新的进成,都需要一些内存(如每个线程有自己的stack空间)和消耗一定的CPU时间。
---------------------
Grand Central Dispatch 简称(GCD)是苹果公司开发的技术,以优化的应用程序支持多核心处理器和其他的对称多处理系统的系统。这建立在任务并行执行的线程池模式的基础上的。让程序平行排队的特定任务,根据可用的处理资源,安排他们在任何可用的处理器核心上执行任务。
参考:全局队列,并发队列,主队列,串行队列,https://www.jianshu.com/p/5840523fb3ea
1、主队列(是串行队列)
dispatch_queue_t mainQueue = dispatch_get_main_queue();
2、全局并行队列
dispatch_queue_t concu = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);//两个参数,前者是优先级,后者目前用不到
3、创建串行队列
dispatch_queue_t queueSerial = dispatch_queue_create("je", DISPATCH_QUEUE_SERIAL);//两个参数,前者是名字(注意是c字符串),后者是队列类型。
4、创建并行队列(一般使用系统带的全局并行队列即可)
dispatch_queue_t queueConcu = dispatch_queue_create("jr2", DISPATCH_QUEUE_CONCURRENT);
GCD实际上就是队列调度中心,GCD调度队列来实现操队列中的任务放到线程中执行.
GCD有一个底层线程池,这个池中存放的是一个个的线程。之所以称为“池”,很容易理解出这个“池”中的线程是可以重用的,当一段时间后这个线程没有被调用的话,这个线程就会被销毁。注意:开多少条线程是由底层线程池决定的(线程建议控制再3~5条),池是系统自动来维护,不需要我们程序员来维护。
而我们程序员需要关心的是什么呢?我们只关心的是向队列中添加任务,队列调度即可。
• 如果队列中存放的是同步任务,则任务出队后,底层线程池中会提供一条线程供这个任务执行,任务执行完毕后这条线程再回到线程池。这样队列中的任务反复调度,因为是同步的,所以当我们用currentThread打印的时候,就是同一条线程。
• 如果队列中存放的是异步的任务,(注意异步可以开线程),当任务出队后,底层线程池会提供一个线程供任务执行,因为是异步执行,队列中的任务不需等待当前任务执行完毕就可以调度下一个任务,这时底层线程池中会再次提供一个线程供第二个任务执行,执行完毕后再回到底层线程池中。
• 这样就对线程完成一个复用,而不需要每一个任务执行都开启新的线程,也就从而节约的系统的开销,提高了效率。在iOS7.0的时候,使用GCD系统通常只能开5~8条线程,iOS8.0以后,系统可以开启很多条线程,但是实在开发应用中,建议开启线程条数:3~5条最为合理。
Synchronous vs. Asynchronous 同步 vs. 异步
在 GCD 中,这些术语描述当一个函数相对于另一个任务完成,此任务是该函数要求 GCD 执行的。一个同步函数只在完成了它预定的任务后才返回。
一个异步函数,刚好相反,会立即返回,预定的任务会完成但不会等它完成。因此,一个异步函数不会阻塞当前线程去执行下一个函数。
注意——当你读到同步函数“阻塞(Block)”当前线程,或函数是一个“阻塞”函数或阻塞操作时,不要被搞糊涂了!动词“阻塞”描述了函数如何影响它所在的线程而与名词“代码块(Block)”没有关系。代码块描述了用 Objective-C 编写的一个匿名函数,它能定义一个任务并被提交到 GCD 。
译者注:中文不会有这个问题,“阻塞”和“代码块”是两个词。
Critical Section 临界区
就是一段代码不能被并发执行,也就是,两个线程不能同时执行这段代码。这很常见,因为代码去操作一个共享资源,例如一个变量若能被并发进程访问,那么它很可能会变质(译者注:它的值不再可信)。
Race Condition 竞态条件
这种状况是指基于特定序列或时机的事件的软件系统以不受控制的方式运行的行为,例如程序的并发任务执行的确切顺序。竞态条件可导致无法预测的行为,而不能通过代码检查立即发现。
Deadlock 死锁
两个(有时更多)东西——在大多数情况下,是线程——所谓的死锁是指它们都卡住了,并等待对方完成或执行其它操作。第一个不能完成是因为它在等待第二个的完成。但第二个也不能完成,因为它在等待第一个的完成。
Thread Safe 线程安全
线程安全的代码能在多线程或并发任务中被安全的调用,而不会导致任何问题(数据损坏,崩溃,等)。线程不安全的代码在某个时刻只能在一个上下文中运行。一个线程安全代码的例子是 NSDictionary
。你可以在同一时间在多个线程中使用它而不会有问题。另一方面,NSMutableDictionary
就不是线程安全的,应该保证一次只能有一个线程访问它。
Context Switch 上下文切换
一个上下文切换指当你在单个进程里切换执行不同的线程时存储与恢复执行状态的过程。这个过程在编写多任务应用时很普遍,但会带来一些额外的开销。
Concurrency vs Parallelism 并发与并行
并发和并行通常被一起提到,所以值得花些时间解释它们之间的区别。
并发代码的不同部分可以“同步”执行。然而,该怎样发生或是否发生都取决于系统。多核设备通过并行来同时执行多个线程;然而,为了使单核设备也能实现这一点,它们必须先运行一个线程,执行一个上下文切换,然后运行另一个线程或进程。这通常发生地足够快以致给我们并发执行地错觉,
Queues 队列
GCD 提供有 dispatch queues
来处理代码块,这些队列管理你提供给 GCD 的任务并用 FIFO 顺序执行这些任务。这就保证了第一个被添加到队列里的任务会是队列中第一个开始的任务,而第二个被添加的任务将第二个开始,如此直到队列的终点。
所有的调度队列(dispatch queues)自身都是线程安全的,你能从多个线程并行的访问它们。 GCD 的优点是显而易见的,即当你了解了调度队列如何为你自己代码的不同部分提供线程安全。关于这一点的关键是选择正确类型的调度队列和正确的调度函数来提交你的工作。
在本节你会看到两种调度队列,都是由 GCD 提供的,然后看一些描述如何用调度函数添加工作到队列的列子。
Serial Queues 串行队列
串行队列中的任务一次执行一个,每个任务只在前一个任务完成是才开始。而且,你不知道在一个 Block 结束和下一个开始之间的时间长度,如下图所示:
这些任务的执行时机受到 GCD 的控制;唯一能确保的事情是 GCD 一次只执行一个任务,并且按照我们添加到队列的顺序来执行。
由于在串行队列中不会有两个任务并发运行,因此不会出现同时访问临界区的风险;相对于这些任务来说,这就从竞态条件下保护了临界区。所以如果访问临界区的唯一方式是通过提交到调度队列的任务,那么你就不需要担心临界区的安全问题了。
Concurrent Queues 并发队列
在并发队列中的任务能得到的保证是它们会按照被添加的顺序开始执行,但这就是全部的保证了。任务可能以任意顺序完成,你不会知道何时开始运行下一个任务,或者任意时刻有多少 Block 在运行。再说一遍,这完全取决于 GCD 。
下图展示了一个示例任务执行计划,GCD 管理着四个并发任务:
注意 Block 1,2 和 3 都立马开始运行,一个接一个。在 Block 0 开始后,Block 1等待了好一会儿才开始。同样, Block 3 在 Block 2 之后才开始,但它先于 Block 2 完成。
何时开始一个 Block 完全取决于 GCD 。如果一个 Block 的执行时间与另一个重叠,也是由 GCD 来决定是否将其运行在另一个不同的核心上,如果那个核心可用,否则就用上下文切换的方式来执行不同的 Block 。
有趣的是, GCD 提供给你至少五个特定的队列,可根据队列类型选择使用。
Queue Types 队列类型
首先,系统提供给你一个叫做 主队列(main queue)
的特殊队列。和其它串行队列一样,这个队列中的任务一次只能执行一个。然而,它能保证所有的任务都在主线程执行,而主线程是唯一可用于更新 UI 的线程。这个队列就是用于发生消息给 UIView
或发送通知的。
系统同时提供给你好几个并发队列。它们叫做 全局调度队列(Global Dispatch Queues)
。目前的四个全局队列有着不同的优先级:background
、low
、default
以及 high
。要知道,Apple 的 API 也会使用这些队列,所以你添加的任何任务都不会是这些队列中唯一的任务。
最后,你也可以创建自己的串行队列或并发队列。这就是说,至少有五个队列任你处置:主队列、四个全局调度队列,再加上任何你自己创建的队列。
1.GCD的核心是:将任务添加到队列;
2.GCD中的任务是使用block封装的;
3.任务的取出遵循先进先出,后进后出的原则;GCD会自动的将队列中的任务取出,放到对应的线程中执行;
4.GCD会自动的管理线程的生命周期(创建线程,调度任务,线程销毁);线程在一定的时间内可以复用;
5.GCD的队列分为两大类型:
串行队列(serial dispatch queue):任务一个接一个执行;
并发队列(concurrent):多个任务同时执行(自动开启多个线程),只有在异步的情况下才有效果.
6.主队列(全局串行队列):主队列的任务,只有主线程空闲的时候才会被调度,主队列中的任务要在主线程执行;
程序启动时就创建了主队列,使用的时候不需要创建,直接获取;
7.全局队列(并发队列):程序启动就创建好,直接get获取,不需要我们内存管理;
=====================
//自定义串行队列
//参数1 :队列的标识符
// 参数2 :队列的属性.决定了队列的类型,即是串行还是并行(并发)
dispatch_queue_t queue =dispatch_queue_create("CZ",DISPATCH_QUEUE_SERIAL);
//自定义并发队列
dispatch_queue_t queue = dispatch_queue_create("CZ", DISPATCH_QUEUE_CONCURRENT);
//获取全局队列(并发):
dispatch_queue_t global_queue =dispatch_get_global_queue(0,0);
//获取主队列(串行):
dispatch_queue_t queue =dispatch_get_main_queue();
=======================
异步执行:新开线程,在新线程中执行
dispatch_async(queue, ^{
}
同步执行:在当前线程中执行,有可能是在子线程,也有可能是在主线程;
dispatch_sync(queue, ^{
// 查看当前的线程
NSLog(@"登陆 %@",[NSThreadcurrentThread]);
});
下面两个都是在主线程执行:
dispatch_sync(dispatch_get_main_queue(), ^{
// 查看当前的线程
NSLog(@"通知用户 %@",[NSThreadcurrentThread]);
});
dispatch_async(queue, ^{
// 查看当前的线程
NSLog(@"%d %@",i,[NSThreadcurrentThread]);
});
=====================
队列和执行方法的总结:
并发队列--同步执行:不开线程,顺序执行;
串行队列--同步执行:不开线程,顺序执行;
串行队列--异步执行:新开1个线程,顺序执行;
并发队列--异步执行:每次开多个线程,线程数有GCD决定.
主队列 ---异步执行:不开线程,顺序执行;
朱队列----同步执行:不允许,会死锁;
=================================
一次性执行,设计单利
// onceToken 是有一个初始值
// 第一次执行的时候,会判断是否是初始值,如果是初始值,就执行block内部的代码
// 第一次执行完成之前,会重置初始值为非0;
+ (instancetype)sharedAccountManager
{
// 定义静态的空对象
static AccountManager *manager;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [[AccountManager alloc] init];
});
return manager;
}
================================
延时操作:
void dispatch_after(
dispatch_time_t when,
dispatch_queue_t queue,
dispatch_block_t block);
/*
参数1 : 操作延迟的时间
参数2 : 操作添加的队列
参数3 : 执行那个操作
*/
// 参数1 : 精确到纳秒
dispatch_time_t when =dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 *NSEC_PER_SEC));
// 参数2
dispatch_queue_t queue =dispatch_get_main_queue();
// 参数3
void (^task)() = ^{
// 查看当前的线程
NSLog(@"%@",[NSThreadcurrentThread]);
};
// 延迟多少纳秒,在哪个队列中执行那个操作
dispatch_after(when, queue, task);
=================================================
调度组的原理:
每一个任务都有一个标记,任务完成标记移除,如果标记全部移除,会执行监听;
// 参数1 : 调度组
dispatch_group_t group =dispatch_group_create();
// 参数2 : 队列
dispatch_queue_t queue =dispatch_get_global_queue(0,0);
dispatch_group_async(group, queue, ^{
// 模拟网络延迟
[NSThreadsleepForTimeInterval:1.0];
// 查看当前的线程
NSLog(@"下载图片1 %@",[NSThreadcurrentThread]);
});
dispatch_group_async(group, queue, ^{
// 模拟网络延迟
[NSThreadsleepForTimeInterval:0.5];
// 查看当前的线程
NSLog(@"下载图片2 %@",[NSThreadcurrentThread]);
});
dispatch_group_async(group, queue, ^{
// 查看当前的线程
NSLog(@"下载图片3 %@",[NSThreadcurrentThread]);
});
// 监听图片是否下载完成
dispatch_group_notify(group, queue, ^{
// 查看当前的线程
NSLog(@"监听是否全部下载完成 %@",[NSThreadcurrentThread]);
});
------------
// 队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 调度组
dispatch_group_t group = dispatch_group_create();
// 1. 进入群组,给 group 打一个标记,在后续紧接着的 block 归 group 监听
// dispatch_group_enter 和 dispatch_group_leave 必须成对出现!
dispatch_group_enter(group);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:10];
NSLog(@"download A - %@", [NSThread currentThread]);
// 耗时操作代码
// 2. 离开群组
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"download B - %@", [NSThread currentThread]);
// 耗时操作代码
// 2. 离开群组
dispatch_group_leave(group);
});
// 等待群组空,一直到永远,群组不空,这句代码就死等,同步
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"OK");