什么是多线程
- 由于一条线程
同一时间
只能处理一个任务
,所以线程里的任务必须按顺序
执行。 - 如果遇到耗时操作(网络通信、耗时计算、音乐播放等),等上一个操作完成再执行下一个任务,在此段时间内,用户得不到任何响应,这个体验无疑是极糟的。
- 因此,在iOS编程中,通常
将耗时操作
单独放在一个线程中
,而把用户交互
的操作放在主线程中
。
进程与线程
- 进程:
进程是指系统中正在运行的应用程序。这个'运行中的程序'就是一个进程。
每个进程都拥有着自己的地址空间。
进程有3个主要特征:
独立性:
进程是一个能够独立运行的基本单位,它既拥有自己独立的资源,又拥有着自己私有的地址空间。
在没有经过进程本身允许的情况下,一个用户的进程是不可以直接访问其他进程的地址空间的。
动态性:
进程的实质是程序在系统中的一次执行过程。
程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合。
在进程中加入了时间的概念,它就具有了自己的生命周期和各自不同的状态,进程是动态消亡的。
并发性:
多个进程可以在单个处理器中同时进行,而不会相互影响。
- 线程:
多线程扩展了多进程的概念,使得同一进程可以同时并发处理多个任务。
一个程序的运行至少需要一个线程,一个进程想要执行任务,也必须要有至少一个线程,而这个线程就被称作主线程。
通常来说,只有一个主线程。
当进程被初始化后,主线程就被创建了,主线程是其他线程最终的父线程,所有界面的显示操作必须在主线程进行。
多线程的优势
- 进程间不能共享内存,但线程之间的共享内存是很容易的。
- 当硬件处理器的数量有所增加时,程序运行的速度更快,无需做其他调整。
- 充分发挥了多核处理器的优势,将不同的任务分配给不同的处理器,真正进入“并行运算”的状态。
- 将耗时、并发需求高的任务分配到其他线程执行,而主线程则负责统一更新界面,这样使得应用程序更加流畅,用户体验更好。
多线程的劣势
- 开启线程需要占用一定的内存空间[默认情况下,主线程最大占用1M的栈区空间、子线程最大占用512K的栈区空间],如果要开启大量的线程,势必会占用大量的内存空间,从而降低程序的性能。
- 开启的线程越多,CPU在调度线程上的开销就越大,一般最好不要同时开启超过5个线程。
- 程序的设计会变得更加复杂,如线程之间的通信、多线程间的数据共享等。
线程的串行和并行
串行:
如果在一个进程中只有一个线程,而这个进程要执行多个任务,那么这个线程只能一个一个的按照顺序执行这些任务,也就是说,在同一时间内,一个线程只能执行一个任务,这样的线程执行方式称为线程的串行。并行:
如果一个进程中包含的线程不止一条,每条线程之间可以并行执行不同的任务,这叫做线程的并行,也叫多线程。补充:
假如一个进程有3个线程,每个线程执行1个任务,3个下载任务没有先后顺序。可以同时执行。
同一时间,CPU只能处理一个线程,也就是只有一个线程在工作。由于CPU快速的在多个线程之间调度,人眼无法察觉到,就造成了多线程并发执行的假象。
线程的状态
当线程被创建并启动之后,既不是一启动就进入执行状态,也不是一直处于执行状态,即便程序启动运行之后,它也不可能一直占用CPU独自运行。
由于CPU需要在多个线程之间进行切换,造成了线程的状态会在多次运行、就绪之间进行切换。
线程的状态主要有5个:
- 新建New
当程序新建一个线程之后,该线程就处于新建状态。
和其他对象一样,只是由系统分配了内存,并初始化了内部成员变量的值。
此时的线程没有任何动态特征
- 就绪Runable
当线程被start之后,该线程就处于就绪状态。
系统会为其创建 方法调用的栈和 程序计数器。
- 运行Running
当CPU调度当前线程的时候,将其他线程挂起,当前线程变为运行状态。
当CPU调度其他线程时,当前线程回到就绪状态。
测试线程是否在运行,调用isExecuting方法,若返回YES,则处于运行状态。
- 终止Exit
* 线程执行方法执行完成,线程正常结束
* 线程执行的过程出现异常,线程崩溃结束
* 直接调用NSThread类的exit方法,终止当前正在运行的线程
* 测试线程是否结束,调用isFinished方法判断,若返回YES,则已终止。
- 阻塞Blocked
如果当前正在执行的线程需要暂停一段时间,并进入阻塞状态,通过NSThread类的两个方法:
//让当前执行的线程暂停到date参数代表的时间,并且进入阻塞状态
+ (void)sleepUntilDate:(NSDate *)date;
//让正在执行的线程暂停ti秒,并进入阻塞状态
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
线程间的安全隐患
进程中的一块资源可能会被多个线程共享,也就是多个线程可能访问同一块资源,这里的资源包括对象
、变量
、文件
等。
当多个线程同时访问同一块资源时,会造成资源抢夺,很容易引发数据错乱和数据安全的问题。
为了解决这个问题,实现数据的安全访问,可以使用线程间的加锁
。
- 同步锁
@synchronized (obj)
{
//插入被修饰的代码块
}
使用同步锁的时候,要尽量让同步代码块包围的代码范围最小,而且要锁定共享资源的全部读写部分的代码。