说到多线程呢,就需要先说下进程,进程是系统正在运行的一个应用程序,一个进程想要执行任务,就至少需要开启一个线程,为了提高资源的利用率,我们可以开启多个线程。但是一个cpu只能运行一个线程,多线程是cpu在多个线程来回调度切换的造成并发的假象,所以如果太多线程也会降低线程调用的频率,所以应当开启适当的线程。
ios程序一般会默认开启一条主线程,也称ui线程,主线程主要是用来刷新和显示界面,响应点击、滚动、拖拽等ui事件,一些耗时操作会放在子线程来完成,因为假如把耗时操作放到主线程,会严重影响ui的流畅度,给用户造成一种卡顿的现象。
比如,我下载一个小电影用了10秒钟,我把这个线程放到主线程来执行,那么用户在此刻点击按钮,滚动、拖拽界面都不会响应的,要等到下载小电影这个线程执行完才有反应,这样就造成了一种卡顿的现象了。
所以,一般我们开发中会把耗时操作放在子线程,刷新更新界面等ui操作则放在主线程。
分为pthread、NSThread、GCD(其实是并发编程,不属于多线程)、NSOperation(其实是并发编程,不属于多线程)4种多线程方法。
pthread是c语言,不常使用。
NSThread: [NSThread alloc] initwithTarget:self selector:@selector(demo:) object:nulll、[NSThread detachNewThreadSelector:@selector() toTarget:self withObject:null]、[self performSelectorInBackground:@selector() withOject:null]
NSThread的属性name可以让程序在崩溃的时候,能够获取到程序准确的所在线程。属性threadPriority可以控制优先级的高低,优先级从0.0—1.0,从高到低。isMainThread判断是否主线程。
多线程状态(生命周期):
Sleep方法是类方法,会直接休眠(阻塞)当前线程。[NSThread sleepForTimeInterval:2.0];
Exit类方法,会终止当前线程(死亡),但是app不会挂掉。[NSThread exit];
多线程的安全隐患:
当多个线程访问同一块资源(同一个对象、同一个变量、同一个文件),很容易引发数据错乱和数据安全问题。比如取钱和卖票问题。
安全隐患的解决方法:互斥锁(lock-read-write-unlock ),保证锁内的代码,同一时间,只有一条线程执行,和上厕所很像,弊端是串行操作。synchronized(self){}; 原则:加锁范围尽量小,范围大了,效率就差。
互斥锁容易犯错的地方:synchronized(任意oc对象都ok){};但是局部变量不行,因为局部变量是每个线程单独拥有的,因此没法加锁。上锁一定要上共有的对象,一般用全局对象,比如self。
atomic(原子属性):相当于互斥锁,它的目的是当多个线程写入这个对象的时候,保证同一时间只有一个线程能够执行。实际上原子属性内部有一个锁叫自旋锁,它与互斥锁的不同点在于,互斥锁如果线程被锁在外面,就会进入休眠状态,等待锁打开,然后被唤醒,自旋锁如果线程被锁在外面,就会用死循环的方式,一直等待锁打开。无论什么锁,都很消耗性能,效率都不高。
atomic的缺点:出现单写多读的现象,有可能出现脏数据,比如卖票,判断数据时候,可能会读入2个线程,导致写数据时候有可能出现-1的不正常状态。所以线程安全指的是在多个线程进行读写操作时,仍然保证数据正确。
nonatomic和atomic对比:atomic线程安全,需要消耗大量的资源,nonatomic非线程安全,适合内存小的移动设备。所以开发时候应当注意,所有属性都声明为nonatomic,尽量避免多线程抢夺同一块资源,尽量将加锁、资源抢夺的业务逻辑交给服务端处理,减少移动客户端的压力。
ui线程共同约定:所有更新ui的操作都放在主线程上执行!原因是UIKit框架都是线程不安全的(因为线程安全,效率就会下降)。
线程间通讯:
回调主线程[self performSelectorOnMainThread: withObject: waitUntilDone:];调用子线程[self performSelectorInBackground: withObject:];
GCD
纯c语言,全称Grand Central Dispatch,可译为“牛逼的中枢调度器”,提供了非常多强大的函数。
优势:1、GCD是苹果公司为多核的并行运算提出的解决方案,会自动利用更多的cup内核。2、GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)。3、程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码。
流程:1、创建队列 ——— dispatch_queue_t q = dispatch_get_global_queue(0,0);定义任务(block)——— void (^task)() = ^{};添加任务到队列,并且会执行 ——— dispatch_sync(q,task);上述流程可合成一句代码 —— dispatch_sync(q, ^{}); 同步执行方法。dispatch_async(q,task);异步执行方法:如果任务没有执行完毕,可以不用等待,异步执行下一个任务。具备开启线程的能力,异步优势多线程的代名词。
dispatch_async(dispatch_get_global_queue(0, 0),^{
//耗时操作
NSLog(@“%@”,[NSThread currentThread]);
//更新ui 主队列,就是专门负责在主线程上调度任务的队列。
//主队列用同步还是异步都是同一个概念
dispatch_async(dispatch_get_main_queue(),^{
NSLog(@“更新ui%@”,[NSTread currentThread]);
});
});
核心概念:将任务添加到队列,指定任务执行的方法。
-任务:使用block封装,就是一个提前准备好的代码块,在需要的时候执行。
-队列:负责调度任务。A、串行队列:一个接一个的调度任务。dispatch_queue_t q = dispatch_queue_create(“队列名称”,NULL);B、并发队列:可以同时调度多个任务。dispatch_queue_t q = dipatch_queue_create(“队列名称”,DISPATCH_QUEUE_CONCURRENT);
-任务执行函数:A、同步执行、不会到线程池里面获取子线程。B、异步执行、只要有任务,就会从线程池里取子线程(主队列getMainQueue除外)。
所以,会不会开线程,由任务执行函数决定,会不会顺序执行,由队列属性(串行/并发)和任务执行函数(同步/异步)共同决定。
GCD同步任务加强:利用同步任务,能够做到任务依赖关系,前一个任务是同步任务,不执行完,队列没法调度后面的任务。
1、用户登录dispatch_sync(loginQueue,^{});
2、支付dispatch_async(loginQueue,^{});
3、下载dispatch_async(loginQueue,^{});
全局队列(本质上并发队列):dispatch_queue_t q = dispatch_get_global_queue(0,0);
参数1:涉及到系统适配,iOS 8 服务质量
QOS_CLASS_USER_INTERACTIVE 用户交互(希望线程快速被执行,不要用好使的操作)
QOS_CLASS_USER_INITIATED 用户需要的(同样不要使用耗时操作)
QOS_CLASS_DEFAULT 默认的(给系统来重置队列的)
QOS_CLASS_UTILITY 使用工具(用来做耗时操作)
QOS_CLASS_BACKGROUND 后台
QOS_CLASS_UNSPECIFIED 没有指定优先级
iOS 7 调度的优先级
- DISPATCH_QUEUE_PRIORITY_HIGH 2 高优先级
- DISPATCH_QUEUE_PRIORITY_DEFAULT 0 默认优先级
- DISPATCH_QUEUE_PRIORITY_LOW (-2) 低优先级
- DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN 后台优先级
提示:尤其不要选择BACKGROUND 优先级,服务质量,线程执行会慢到令人发指!!!
参数2: 为未来使用的一个保留,现在始终给0。老项目中,一般还是没有淘汰ios7,没法使用服务质量。
主队列:NSLog(@”这里”);dispatch_queue_t q = dispatch_get_main_queue(); dispatch_async(q,^{});以FIFO调度任务,如果主线程上有任务在执行,主队列就不会调度任务,这种情况会造成死锁。Void(^task)() = ^{ NSLog(@”这里”);dispatch_queue_t q = dispatch_get_main_queue(); dispatch_async(q,^{});};dispatch_async(dispatch_get_global_queue(0,0),task);把任务丢到子线程去执行,就不会造成死锁了。
延时执行:dispatch_after
一次执行:static dispatch_once_t onceToken;
dispatch_once(&onceToken,^{});
调度组:1、队列dispatch_queue_t q = dispatch_get_global_queue(0,0);
2、调度组dispatch_group_t g = dispatch_group_create();
3、添加任务,让队列调度,任务执行情况,最后通知群组
dispatch_group_async(g,q,^{});
4、所有任务执行完毕后通知(本身也是异步的)
dispatch_group_notify(g,q,^{});用法:通知ui更新dispatch_group_notify(g,dispatch_get_main_queue(),^{
NSLog(@”下载完成%@”,[NSThread currentThread]);
});
NSOperation:将“操作”添加到“队列”。是一个抽象类。特点是不能直接使用。目的是定义子类共有的属性和方法。
子类有2个:NSInvocationOperation和NSBlockOperation。
本质上是对GCD的面向对象的封装,就是GCD的并发队列,开启多个线程,不会顺序执行。
1、队列:NSOperationQueue *q = [[NSOperationQueue alloc] init];
[q addOperation:op];
线程间的通讯:主线程更新ui—[[NSOperationQueue mainQueue] addOperationWithBlock:^{}];
GCD和NSOperation对比:
GCD在ios4.0推出,主要针对多核处理器做了优化的并发技术,是c语言的.
--将“任务”[block]添加到队列[串行/并发/主队列/全局队列],并且指定执行任务的函数[同步/异步]。
--提供了一些NSOperation不具备的功能:a、一次执行。b、延迟执行。c、调度组(在op中也可以做到,有点麻烦)。
-mainQueue。
--提供了一些GCD实现起来比较困难的功能:
a、最大并发线程。
b、队列的暂停/继续。
c、取消所有操作。
d、指定操作之间的依赖关系(GCD用同步来实现)。
最大并发线程:-(NSOperationQueue *)opQueue{
if(!_opQueue) {
_opQueue = [[NSOperationQueue alloc] init];
}
return _opQueue;
}
-(void)viewDidLoad{
//设置同时最大的并发操作数量。
self.opQueue.maxConcurrentOperationCount = 2;
for(int I = 0; I < 20; i++){
[self.opQueue addOperationWithBlock:^{}];
}
}
从ios8.0开始,无论使用GCD还是NSOperation,都会开启很多线程,在ios7.0以前,GCD通常只会开启5,6条线程。目前线程多了说明:1、底层的线程池更大了,能够拿到的线程资源多了。2、多控制同时并发的线程数,要求就更高了。
Wifi:设置同时最大的并发操作数量是5至6,流量则是2至3条。
队列挂起(暂停):注意,当挂起队列的时候,正在执行的操作不受影响。
//isSuspended:判断我们队列是否挂起,suspend:决定队列的 暂停和继续,operationCount:队列中的操作数。
if(self.opQueue.isSuspended){
NSLog(@“继续%tu”,self.opQueue.operationCount);
self.opQueue.suspended = NO;
}else{
NSLog(@“暂停%tu”, self.opQueue.operationCount);
Self.opQueue.suspended = YES;
}
取消所有操作:[self.opQueue cancelAllOperatitons]1、;队列挂起的时候,不会清空内部的操作,只有在队列继续的时候才会清空。2、正在执行的操作也不会被取消。
依赖关系:
//MARK: 依赖关系
-(void)dependecy{
/*
例子: 下载\解压\通知用户
*/
//1.下载
NSBlockOperation * op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@”下载—%@”,[NSThread currentThread]);
[NSThread sleepForTimeInterval:.5];
}];
//2.解压
NSBlockOperation * op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@”解压—%@”,[NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0];
}];
//3.通知用户
NSBlockOperation * op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@”通知用户—%@”,[NSThread currentThread]);
}];
//NSOperation 提供了依赖关系
//!!!! 注意,不要指定循环依赖,队列就不工作了!!
[op2 addDependency:op1];
[op3 addDependency:op2];
//添加到队列中 waitUntilFinished:是否等待! //会卡住当前线程!!
[self.opQueue addOperations:@[op1,op2] waitUntilFinished:NO];
//主线程通知用户
[[NSOperationQueue mainQueue] addOperation:op3];
NSLog(@"come here %@",[NSThread currentThread]);
}