在同一时刻,一个CPU只能处理1条线程,但CPU可以在多条线程之间快速的切换,只要切换的足够快,就造成了多线程一同执行的假象。
多线程是通过提高资源使用率来提高系统总体的效率。
主线程:处理UI,所有更新UI的操作都必须在主线程上执行。不要把耗时操作放在主线程,会卡界面。
运用多线程的目的是:将耗时的操作放在后台执行!
一.线程的生命周期:
新建- 就绪- 运行- 阻塞- 死亡
1.新建:实例化线程对象
2.就绪:详线程对象发送start消息,线程对象被加入可调度线程池等待CPU调度。
3.运行:CPU负责调度可调度线程池中线程的执行。线程执行完成之前,状态可能会在就绪和运行之间来回切换。就绪和运行之间的状态变化由CPU负责,程序员不能干预。
4.阻塞:当满足某个预定条件时,可以使用休眠或锁,阻塞线程执行。sleepForInterval(休眠指定时长),sleepUntilData(休眠到指定日期),@synchronized(self)(互斥锁)
5.死亡:正常死亡,线程执行完毕。非正常死亡,当满足某个条件后,在线程内部中止执行/在主线程中止线程对象。
二.多线程的几种方式:
PThread:
一套C语言的通用的多线程API,适用于Unix/Linux/Windows等系统,跨平台,可移植。使用难度比较大,内存自己管理。
NSThread:
OC语言的,使用更加面向对象,简单易用,可直接操作线程对象。不太常用,自己管理内存。
三种创建方式:
init方式,
detachNewThreadSelector创建好之后自动启动,
performSelectorInBackground创建好之后也是直接启动
类方法:
[NSThread currentThread]; //当前线程
[NSThread sleepForTimeInterval:2]; //休眠多久
[NSThread sleepUntilDate:[NSDate date]]; //休眠到指定时间
[NSThread exit]; //退出线程
[NSThread isMainThread]; //判断当前线程是否为主线程
[NSThread isMultiThreaded]; //判断当前线程是否是多线程
属性:
thread.isExecuting; //线程是否在执行
thread.isCancelled; //线程是否被取消
thread.isFinished; //线程是否完成
thread.isMainThread; //是否是主线程
thread.threadPriority; //线程的优先级,取值范围0.0到1.0,默认优先级0.5,1.0表示最高优先级,优先级高,CPU调度的频率高
GCD:
C语言的,GCD会自动利用更多的CPU内核,GCD自动管理线程的生命周期,例如创建线程,调度任务,销毁线程等,只要告诉GCD想要如何执行什么任务,不需要编写任何线程管理代码,易用。
队列创建方法:
dispatch_queue_create(“队列标示”,“DISPATCH_QUEUE_SERIAL
串行或DISPATCH_QUEUE_CONCURRENT并行”)
dispatch_get_main_queue() // 回到主线程进行UI操作
dispatch_get_global_queue(0, 0) //全局并发队列,第一个参数是优先级。
任务执行方式:
同步:dispatch_sync
异步:dispatch_async
应用:
1.串行同步:在主线程上,不开启新线程。
2.串行异步:开启一条新线程,顺序执行。
3.并发同步:主线程上,不开启新线程。
4.并发异步:开启多条线程,无顺序执行。
5.主队列同步:发生死锁,程序崩溃。
原因:如果在主线程中运用主队列同步,也就是把任务放到了主线程的队列中。而同步对于任务是立刻执行的,那么当把第一个任务放进主队列时,它就会立马执行。可是主线程现在正在处理syncMain方法,任务需要等syncMain执行完才能执行。syncMain执行到第一个任务的时候,又要等第一个任务执行完才能往下执行第二个和第三个任务。这样syncMain方法和第一个任务就开始了互相等待,形成了死锁。
6. 主线程异步:主线程上顺序执行
其他用法:
GCD栅栏:dispatch_barrier_async,可以分组执行。
GCD延时执行:dispatch_after。
GCD一次执行:dispatch_once能保证某段代码在程序运行过程中只被执行1次。常用于单例。
GCD快速迭代:dispatch_apply。常用于遍历
GCD队列组:异步,并发。dispatch_group_create创建一个队列组,dispatch_group_notify监听任务是否完成。
NSOPeration:
基于底层的GCD,比GCD多了一些更简单使用的功能,使用更加面向对象,OC语言的,自动管理内存。经常使用。
NSOperation实现多线程步骤:创建任务:先将需要执行的操作封装到NSOperation对象中。创建队列:创建NSOperationQueue。将任务加入到队列中:将NSOperation对象添加到NSOperationQueue中。
创建方式:
1.NSInvocationOperation对象并关联方法,之后start。(在主线程下执行,没有开启新线程。如果加入队列,就会开启新的线程,在子线程下执行)
2.NSBlockOperation创建对象,创建block中的任务是在主线程执行(加入队列,会开启新的线程,在自线程下执行)。
addExecutionBlock加入的任务是在子线程中执行。
3.继承自NSOperation的子类。然后重写它的main方法,
队列NSOperationQueue:
主队列、其他队列(并发和串行)。
最大并发数。maxConcurrentOperationCount(-1,代表多线程)
NSOperation的其他操作:
1. - (void)cancelAllOperations //取消队列NSOperationQueue的所有操作。
2. - (void)cancel //取消NSOperation的某个操作,NSOperation对象方法
3.[queue setSuspended:YES]; //使队列暂停或继续
4. - (BOOL)isSuspended //判断队列是否暂停
5. [operation2 addDependency:operation1]; //操作依赖,2依赖1
三.线程安全问题:
1.互斥锁:当新线程访问时,如果发现其他线程正在执行锁定的代码,新线程就会进入休眠。
2.自旋锁:加了自旋锁,当新线程访问代码时,如果发现有其他线程正在锁定代码,新线程会用死循环的方式,一直等待锁定的代码执行完成。相当于不停尝试执行代码,比较消耗性能。
nonatomic 非原子属性,同一时间可以有很多线程读和写。非线程安全,不过效率更高。
atomic 原子属性(线程安全),保证同一时间只有一个线程能够写入(但是同一个时间多个线程都可以取值),atomic 本身就有一把锁(自旋锁),线程安全,需要消耗大量的资源。
四.Block的一些问题:
1.IOS项目中使用的好处和缺点?
好处:
使用线程可以把占据时间长的程序中的任务放到后台去处理
用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度
程序的运行效率可能提高
在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。
缺点:
如果有大量的线程,会影响性能,因为操作系统需要在它们之间切换。
更多的线程需要更多的内存空间。
线程的中止需要考虑其对程序运行的影响。
2.一次执行是怎么做到的?如果让你来实现dispatch_once,你会怎么做?
1.线程A执行Block时,任何其它线程都需要等待。
2.线程A执行完Block应该立即标记任务完成状态,然后遍历信号量链来唤醒所有等待线程。
3.线程A遍历信号量链来signal时,任何其他新进入函数的线程都应该直接返回而无需等待。
4.线程A遍历信号量链来signal时,若有其它等待线程B仍在更新或试图更新信号量链,应该保证此线程B能正确完成其任务:a.直接返回b.等待在信号量上并很快又被唤醒。
5.线程B构造信号量时,应该考虑线程A随时可能改变状态(“等待”、“完成”、“遍历信号量链”)。
6.线程B构造信号量时,应该考虑到另一个线程C也可能正在更新或试图更新信号量链,应该保证B、C都能正常完成其任务:a.增加链节并等待在信号量上b.发现线程A已经标记“完成”然后直接销毁信号量并退出函数。
3. NSOperation与GCD的区别?
GCD是将任务(block)添加到队列(串行、并行、全局、主队列),并且以同步/异步的方式执行任务的函数。GCD提供了一些NSOperation不具备的功能:一次性执行,延迟执行,调度组,GCD 是严格的队列,先进先出FIFO;
NSOperation是将操作(异步的任务)添加到队列(并发队列),就会执行指定的函数。NSOperation提供的方便操作:1.最大并发数。2.队列的暂停和继续。3.取消所有的操作。4.指定操作之间的依赖关系依赖关系,可以让异步任务同步执行。5.将KVO用于NSOperation中,监听一个operation是否完成。6.能够设置NSOperation的优先级,能够使同一个并行队列中的任务区分先后地执行。7.对NSOperation进行继承,在这之上添加成员变量与成员方法,提高整个代码的复用度。