多线程的原理
1、CPU只能处理一条线程,只能在一条线程中工作。
2、多线程并发,其实是CPU快速的在多条线程之间的调度。
3、如果开启的线程过多会消耗大量的CPU资源,降低执行效率。
4、一般开启 3 ~ 5 个线程。
创建线程对象
pthread_t thread;
创建线程
/*
第一个参数 线程对象
第二个参数 线程属性
第三个参数 要执行的任务
第四个参数 函数的参数
*/
pthread_create(&thread, NULL, run ,NULL);
设置子线程的状态设置为detach,该线程运行结束后会自动释放所有资源。
pthread_detach()
执行方法
void *run(void *param)
{
for (NSInteger i = 0; i < 10000; i++) {
NSLog(@"%zd", i);
}
return NULL;
}
创建线程
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run) object:nil];
设置线程名字
thread.name = @"running";
qualityOfService 设置优先级
/*
NSQualityOfServiceUserInteractive: 最高:主要用于与UI交互的操作,各种事件处理以及绘制图像等。
NSQualityOfServiceUserInitiated: 次高:执行一些明确需要立即返回结果的任务。
NSQualityOfServiceDefault: 默认:默认的优先级,介于次高级和普通级之间。
NSQualityOfServiceUtility: 普通:用于执行不许要立即返回结果、耗时的操作,下载或者一些媒体操作等。
NSQualityOfServiceBackground: 后台:后台执行一些用户不需要知道的操作,它将以最有效的方式运行。例如一些预处理的操作,备份或者同步数据等等。
*/
thread.qualityOfService = NSQualityOfServiceUserInitiated;
开启线程
[thread start];
其它常用方法
//判断是否是多线程
+ (BOOL)isMultiThreaded;
//线程休眠
+ (void)sleepUntilDate:(NSDate *)date;
//线程休眠时间
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
//退出当前线程
+ (void)exit;
//获取当前线程优先级
+ (double)threadPriority;
// 设置线程优先级 默认为0.5 取值范围为0.0 - 1.0,1.0优先级最高
+ (BOOL)setThreadPriority:(double)p;
线程通信
1、在1个进程中,多个线程之间需要经常进行通信。
2、例如我们在子线程获取数据后,要回到主线程刷新界面。
3、1个线程传递数据给另1个线程。
4、在1个线程中执行完特定任务后,转到另1个线程继续执行任务。
回到主线程或者转到另一个线程的方法
// 返回主线程
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
// 返回指定线程
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
线程状态描述
1、创建线程对象后,线程处于新建New的状态。
2、调用start 开启线程后,会将线程加入到可调度线程池中,此时进入就绪Runnable状态。
3、当CPU调度到当前线程的时候,这时就会处于运行Running的状态。
4、当CPU调度其他线程的时候,当前线程又回到就绪状态。
5、当我们对当前线程调用了sleep或者等待同步锁的时候,此时又会进入线程阻塞Blocked状态。
加入到可调度线程池中。
6、当任务执行完毕或强制退出后,进入到死亡Dead状态,此时线程对象被释放。
借一张图表示一下:
线程安全
1、问题:多条线程抢夺同一块资源,可能会导致数据错乱和数据安全的问题。
2、解决:互斥锁,加锁可以使线程同步(多条线程在同一条线上按顺序的执行任务)。
3、优点:有效防止因多线程抢夺资源造成的数据安全问题。
4、缺点:需要消耗大量的CPU资源。
//self : 锁对象,必须全局唯一
@synchronized (self) {
//代码片段,需要做的操作
}
GCD优点
核心概念:任务和队列
队列:存放任务。
1、将任务添加到队列中,GCD会自动将队列中的任务取出,放到对应的线程中执行。
2、任务的取出遵循队列的FIFO原则:先进先出,后进后出。
任务
(1)、同步任务:只能在当前线程执行任务,不具备开启新线程的能力.
dispatch_sync(<#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)
(2)、异步任务:在新的线程中执行任务,具备开启线程的能力.
dispatch_sync(<#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)
队列
(1)、并发队列:可以让多个任务同时执行,并发功能只有在异步函数下才有效.
//1.创建并发队列
/**
第一个参数:c语言字符串,标签
第二个参数:队列的类型
DISPATCH_QUEUE_CONCURRENT 并发
DISPATCH_QUEUE_SERIAL 串行
*/
dispatch_queue_t creatCurrrentQueue = dispatch_queue_create("creatCurrrentQueue", DISPATCH_QUEUE_CONCURRENT);
//2.获取全局的并发队列
/**
第一个参数:优先级 选择默认的优先级
第二个参数:留给以后用的 暂时传0
*/
dispatch_queue_t getGlobalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
(2)、串行队列:让任务一个接一个执行.
//1.创建串行队列
dispatch_queue_t creatQueue = dispatch_queue_create("creatQueue", DISPATCH_QUEUE_SERIAL);
//2.获取全局的串行队列主队列 主队列是GCD自带的一种特殊的串行队列,放在主队列中的任务,都会放到主线程中执行
dispatch_queue_t getQueue = dispatch_get_main_queue();
常用组合方式
// 异步函数 + 获取全局并发队列 --- 例如获取数据
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 异步函数 + 获取全局串行队列(主队列)--- 例如 回到主线程 刷新UI界面
dispatch_async(dispatch_get_main_queue(), ^{
});
});
同步函数 + 主队列:死锁
//1.获得主队列
dispatch_queue_t queue = dispatch_get_main_queue();
//2.同步函数
dispatch_sync(queue, ^{
NSLog(@"---download1---%@",[NSThread currentThread]);
});
1、给主线程中添加任务,同步函数要求立刻马上执行任务,而主队列安排主线程来执行任务,
但当前主线程在等待方法执行完毕, 因此就会相互等待而发生死锁。
2、如果此方法在子线程中调用,则不会形成死锁。
其它GCD使用方式
(1)、延迟执行
/*
第一个参数:延迟时间
第二个参数:要执行的代码
如果想让延迟的代码在子线程中执行,也可以更改在哪个队列中执行 dispatch_get_main_queue() -> dispatch_get_global_queue(0, 0)
*/
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), queue, ^{
//执行内容
});
(2)、只执行一次,例如单例
//整个程序运行过程中只会执行一次
//onceToken用来记录该部分的代码是否被执行过
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"-----");
});
(3)、队列组
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 执行1个耗时的异步操作
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 执行1个耗时的异步操作
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的异步操作都执行完毕后,回到主线程...
});
(4)、快速迭代:开启多条线程,并发执行,相比于for循环在耗时操作中极大的提高效率和速度
/*
第一个参数:迭代的次数
第二个参数:在哪个队列中执行
第三个参数:block要执行的任务
*/
dispatch_apply(10, queue, ^(size_t index) {
});
NSOperation是苹果对GCD的封装,完全面向对象,不用考虑线程的生命周期、同步、加锁等问题。
NSOperation和NSOperationQueue分别对应GCD的任务和队列。
NSOperation和NSOperationQueue实现多线程的具体步骤:
1、将需要执行的操作封装到一个NSOperation对象中。
2、将NSOperation对象添加到NSOperationQueue中。
3、系统会自动将NSOperationQueue中的NSOperation取出来,将其封装的操作放到一条新线程中执行。
NSOperation
NSOperation是个抽象类,并不具备封装操作的能力,必须使用它的子类,它的子类有三种:
(1)、NSInvocationOperation
/*
第一个参数:目标对象
第二个参数:选择器,要调用的方法
第三个参数:方法要传递的参数
*/
NSInvocationOperation *op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(run) object:nil];
//启动操作
[op start];
(2)、NSBlockOperation(常用)
//1.封装操作
NSBlockOperation *blockOperation= [NSBlockOperation blockOperationWithBlock:^{
//要执行的操作,在主线程中执行
}];
//2.追加操作,追加的操作在子线程中执行,可以追加多条操作
[blockOperation addExecutionBlock:^{
//追加的操作
}];
//启动操作
[blockOperation start];
(3)、自定义子类继承NSOperation,实现内部相应的方法
-(void)main
{
// 要执行的操作
}
NSOperationQueue
队列:
(1)、主队列:通过mainQueue获得,凡是放到主队列中的任务都将在主线程执行
(2)、非主队列:直接alloc init出来的队列。非主队列同时具备了并发和串行的功能,通过设置最大并发数属性来控制任务是并发执行还是串行执行
(3)、NSOperation可以调用start方法来执行任务,但默认是同步执行的,如果将NSOperation添加到NSOperationQueue(操作队列)中,系统会自动异步执行NSOperation中的操作
添加操作到NSOperationQueue中
- (void)addOperation:(NSOperation *)op;
- (void)addOperationWithBlock:(void (^)(void))block;
NSOperation和NSOperationQueue结合使用创建多线程
// 1. 创建非主队列 同时具备并发和串行的功能,默认是并发队列
NSOperationQueue *queue =[[NSOperationQueue alloc]init];
// 2. 封装操作
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
//操作
}];
// 3. 将封装操作加入主队列
// 也可以不获取封装操作对象 直接添加操作到队列中
//[queue addOperationWithBlock:^{
// 操作
//}];
[queue addOperation:op1];
NSOperation和NSOperationQueue的重要属性和方法
NSOperation的依赖
// 操作one依赖two,即one必须等two执行完毕之后才会执行
[one addDependency:two];
// 移除依赖
[one removeDependency:two];
NSOperation操作监听
op1.completionBlock = ^{
NSLog(@"op1已经完成了---%@",[NSThread currentThread]);
};
NSOperation线程之间的通信方法
// 回到主线程刷新UI
[[NSOperationQueue mainQueue]addOperationWithBlock:^{
//刷新操作
}];