在iOS开发上搬了几年砖了,一直在向各位大神学习,这段时间公司项目完工了,整理一下相关技术点,向后来者做个借鉴,沉淀一下自己。
说起多线程基本上每个开发者都会用到,今天总结一下多线程方面的应用技巧。
总纲
1.概念相关
2.应用场景
3.相关技术和用法
1.概念相关
1.1、什么是多线程?
线程是指程序在执行过程中能够执行代码的一个执行单元,线程主要有新建、就绪、挂起、结束四种状态。
1.2、线程与进程的区别?
程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序过程就称之为进程。进程是程序执行的一次活动。进程中有线程在执行,且一个进程中最少有一条线程,这条线程我们叫它为主线程(主线程中运行着runloop),主线程一般显示、刷新UI界面,处理UI事件(点击、滚动、拖拽事件等),我们也一般叫它UI线程。当然进程也也可以开多条线程,我们称之为子线程。开多条线程的使用情况我们称为多线程。
1.3、多线程的原理
同一时间内一个CPU只能处理一条线程,也只有一条线程在工作,在单核cpu情况下多线程其实就是一个cpu在多条线程之间调度,当然多核CPU才是真正意义上的多线程。CPU在多条线程调度会消耗大量的CPU资源,每条线程被调度频次会降低且消耗内存,一般情况不要同时开5条线程以上
1.4、多线程优缺点
优点:适当提高了程序的执行效率,把那些占用时长的操作放到后台处理,优化应用流畅度,增强用户体验。
缺点:大量的线程需要更多的内存。当多个线程对同一资源出现争夺时候还要注意线程安全问题
2.应用场景
一般情况下我们使用一条线程进程就能跑通了,那么既然有多线程,我们在什么情况下使用多线程呢?我们都知道主线程是UI线程, 它可以渲染UI,负责将视图呈现给我们,当这个时候如果有一个非常耗时的操作,比如说视频的加载或者图片的下载,如果网络不好的情况下可能要等好久,如果在一条线程中执行,那么我们的应用界面将出现卡死现象。这时候如果把图片下载放到子线程中,等图片下载完成再回到主线程渲染UI,那么就不会出现卡死或者卡顿现象。开发中常见的使用多线程地方有图片的下载、版本号的加载、消息红点的校对、计时器、文件的下载等
3.技术方案
目前在iOS开发中有四种技术方案,分别是Pthread、NSThread、GCD、NSOperation
3.1、Pthread
Pthread是一套通用的API,可以跨平台使用,但是使用难度大,语言是C语言,线程生命周期由程序员管理,在开发中几乎不用,当然在平常项目开发中我也没用过,只在闲暇时候demo写过简单的使用方法。
//使用时需要引入#import <pthread.h>
//创建线程对象
pthread_t thread = NULL;
//传递的参数
id str = @"param";
//创建线程
/* 参数一:线程对象 传递线程对象的地址
参数二:线程属性 包括线程的优先级等
参数三:子线程需要执行的方法
参数四:需要传递的参数
*/
int result = pthread_create(&thread, NULL, func, (__bridge void *)(str));
if (result == 0) {
NSLog(@"线程创建成功了^_^");
} else {
NSLog(@"创建线程失败 ^o^。创建结果是:%d", result);
}
//手动把当前线程结束掉
// pthread_detach:设置子线程的状态设置为detached,则该线程运行结束后会自动释放所有资源。
pthread_detach(thread);
//下面是传递的执行方法
void * func(void *param)
{
//打印当前线程
NSLog(@"当前线程是:%@", [NSThread currentThread]);
return NULL;
}
3.2、NSThread
NSThread 是OC 面向对象,简单易用,只负责创建,不用管理线程死亡,偶尔使用
NSThread是一个类,有三种初始化方法:
//创建线程
NSThread * newThread = [[NSThread alloc]initWithTarget:self selector:@selector(demo:) object:@"Thread"];
//或者
NSThread * newThread =[[NSThread alloc]init];
NSThread * newThread = [[NSThread alloc]initWithBlock:^{
NSLog(@"通过block创建");
}];
开启线程:
[newThread start];
暂停线程:
[NSThread sleepForTimeInterval:1.0]; (一秒为例)
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
线程取消:
[newThread cancel];
停止线程:
[NSThread exit];
设置优先级:
iOS8之前:
[NSThread currentThread];
iOS8之后:
//通过qualityOfService枚举的方式设置优先级
NSQualityOfServiceUserInteractive:最高优先级,用于用户交互事件
NSQualityOfServiceUserInitiated:次高优先级,用于用户需要马上执行的事件
NSQualityOfServiceDefault:默认优先级,主线程和没有设置优先级的线程都默认为这个优先级
NSQualityOfServiceUtility:普通优先级,用于普通任务
NSQualityOfServiceBackground:最低优先级,用于不重要的任务
比如设置最低优先级
[newThread setQualityOfService: NSQualityOfServiceBackground];
设置线程名称:
[newThread setName:@"自定义的线程名"];
获取主线程
[NSThread mainThread];
检查是否为主线程:
[thread isMainThread];
线程之间通讯
//指定当前线程操作
[self performSelector:@selector(func)];
[self performSelector:@selector(func) withObject:nil];
[self performSelector:@selector(func) withObject:nil afterDelay:2.0];
//子线程回到主线程操作,比如在主线程刷新UI
[self performSelectorOnMainThread:@selector(func) withObject:nil
waitUntilDone:YES];
//主线程指定其他线程执行操作
[self performSelector:@selector(func) onThread:newThread
withObject:nil waitUntilDone:YES];
//这里指定为后台线程
[self performSelectorInBackground:@selector(func) withObject:nil];
线程同步:
在程序运行中,因为资源在内存中是共享的,如果存在多条线程,那么各个线程读写资源就会存在先后顺序或者同时读写,因此会出现读写混乱或者错误。为了防止这样的事情发生我们要在读写数据时加锁,这样在操作同一个数据时候线程只有一个,一个操作完成后另一个才能操作。但上锁就会加大CPU开销,造成性能降低。
iOS中NSLock / NSConditionLock / NSRecursiveLock / @synchronized都可以实现线程上锁的操作。具体用法很简单,随便搜索都一大把结果这里就不一一说明了。
3.3、 GCD
GCD(Grand Central Dispatch)是iOS4引入的强大的线程处理技术,它是基于XNU内核开发的,性能极为优越。
这里有我以前写的一篇GCD应用可以移步去看一下详细应用。
3.4、NSOperation
NSOperation 是对GCD的封装,NSOperation是一种抽象类,完全面向对象化,使用的时候要使用其子类调取方法或者继承它自定义具体的操作。相比于GCD简单易用,代码可读性高。
NSOperation有两个子类NSBlockOperation和NSInvocationOperation,使用区别是一个是block回调,一个是NSInvocation回调
3.4.1、 NSInvocationOperation子类
//创建操作对象,封装执行的任务
NSInvocationOperation *operation=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test) object:nil];
//执行操作
[operation start];
执行操作就会调用target的test方法,不过操作对象时候默认在主线程中执行,只有添加到队列中才会开启新的线程。默认情况下如果没放到队列Queue中都是同步执行的,只有将NSInvocationOperation 对象放到NSOperationQueue中才会开启线程异步执行。
3.4.2、NSBlockOperation子类
//创建NSBlockOperation操作对象
NSBlockOperation *operation=[NSBlockOperation blockOperationWithBlock:^{
//执行的操作1
}];
//和NSInvocationOperation不同的是NSBlockOperation有一个添加操作的方法
[operation addExecutionBlock:^{
//执行的操作2
}];
[operation addExecutionBlock:^{
//执行的操作3
}];
[operation addExecutionBlock:^{
//执行的操作4
}];
//开启执行操作
[operation start];
需要注意的是只要NSBlockOperation封装的操作数大于1,就会进行异步执行操作
3.4.3、NSOperationQueue
NSOperation可以通过start方法来执行任务,但默认是同步执行,除非NSBlockOperation封装的操作数大于1的时候才会异步执行。但是如果把NSOperation对象添加到NSOperationQueue(队列)中,系统会自动异步执行NSOperation中的操作任务,添加操作任务到NSOperationQueue中自动开启线程执行任务。
//创建NSOperationQueue
NSOperationQueue * queue=[[NSOperationQueue alloc]init];
//把操作添加到队列中
//第一种方式
[queue addOperation:operation1];
[queue addOperation:operation2];
[queue addOperation:operation3];
//第二种方式
[queue addOperationWithBlock:^{
//直接添加操作
}];
// 第三种直接添加NSOperation数组,不过如果Bool类型的wait为yes的话会阻塞当运行前线程直到队列全部添加完成。如果为no就相当于批量添加到队列中。
[queue addOperatios:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait];
网友评论