前言:
第一次接触多线程还是在写android的时候,咋一看,觉得这玩意好难(面试必问);其实从字眼上看多线程分为“多”跟“线程”,只要搞明白线程是什么东西,那多线程就迎刃而解了(就是多条线程同时存在嘛),哪线程是什么东西? 我门必须了解一个非常基础的的概念 “进程”,正在进行的程序,至于进程更多的概念就自行百度吧!
一个进程要想执行任务必须要有线程(线程是进程的一条执行路径),每一个程序至少有一条线程,进程要执行的任务都是放在线程中执行的,线程的串行,一个线程中的所有任务是串行(一个任务一个任务的执行)的,同一时间一个线程只能执行一个任务;
一个 进程中可以开启多条线程,每条线程可以并行执行不同的任务;提高程序的执行效率;
多线程的原理:
同一时间,CPU 只能处理一条线程,CPU很快的在多条线程中切换(调度),造成了多线程并发执行的原理; 如果CPU在n多个线程来回调度,会消耗大量的CPU资源; 每条线程被调度的频次会降低;
多线程的优点:
能适当的提高程序的执行效率;能适当的提高资源利用率;
多线程的缺点:
创建子线程是有开销的,主要包括内核数据结构,栈空间(子线程512k,主线程1m,也可以使用-setStackSize设置,必须是4k的倍数),创建一个线程大约是90毫秒的时间;程序设计更加复杂;多线程之间的数据共享;多个线程同时占用同一个资源;
多线程在IOS中的应用?
主线程:默认开启的子线程,叫主线程 也叫 UI线程;
作用: 显示 刷新 UI,处理UI事件, 不要将比较耗时的操作放在主线程;
线程的状态:
内存 -》线程对象(新建状态)
调用 Start -》就绪状态(Runable)
CPU 调度当前线程 运行状态(Running)
调用了sleep 进入 阻塞状态(Blocked)
正常执行 或者 异常退出之后,就会进入死亡状态([使用NSThread exit(0)]可以强制杀死线程)
耗时操作的执行:
开启子线程的的方式:
1. pthread :跨平台的线程,使用难度比较大, 几乎不用;(手动创建线程,管理线程);
// 示例代码
pthread_t myRestrict;
pthread_create(&myRestrict, NULL, run, NULL);
void *run(void *data)
{
for (int i=0; i<1000000; i++) {
NSLog(@"%d",i);
}
NSLog(@"run----&@",[NSThread currentThread]);
return NULL;
}
2. NSThread : 面向对象,简单易用,可是直接操作线程对象, 偶尔使用,使用 NSThread管理多个线程非常困难;
(1) [NSThread currentThread] //跟踪任务所在线程,适用于这三种技术.
(2) [NSThread sleepForTimeInterval:] //睡眠多长时间(秒)
/**
* 创建线程的方式1
*/
- (void)createThread1
{
// 1.初始化
NSThread *thread=[[NSThread alloc]initWithTarget:self selector:@selector(download) object:nil];
thread.name=@"线程2号";
//启动线程
[thread start];
//获取主线程
[NSThread mainThread];
}
/**
* 创建线程的方式2, 直接创建
*/
- (void)createThread2
{
// 分离 ,派遣
[NSThread detachNewThreadSelector:@selector(download:) toTarget:self withObject:@"www.baidu.com"];
}
/**
* 创建线程的方式3, 隐身创建线程
*/
- (void)createThread3
{
[self performSelectorInBackground:@selector(download:) withObject:self];
}
/**
* 创建线程的方式4, 隐身创建线程
*/
- (void)createThread4
{
[self performSelector:@selector(download:) onThread:[NSThread mainThread] withObject:self waitUntilDone:YES];}
3. GCD:(Grand Central Dispatch),伟大的中枢调度器,取代NSThread,充分利用设备的多核并行运算;线程的生命周期是自动管理的,经常使用;
GCD的核心概念:任务(执行什么操作) 跟 队列(用来存放任务,相当于线程池)
3.1 任务执行方式: 同步或者异步执行
//同步执行
dispatch_async(<#dispatch_queue_t queue#>, <#^(void)block#>)
//异步执行
dispatch_sync(<#dispatch_queue_t queue#>, <#^(void)block#>)
3.2 队列:并发队列(concurrent),可以让多个任务并发执行,并发功能只有在dispatch_async才有效,苹果提供了DISPATCH_QUEUE_PRIORITY_DEFAULT ,使用dispatch_get_global_queue获取就好了;
异步执行是在执行函数完成之后,在返回去开辟子线程;
串行队列,一个任务一个任务的执行,主队列
同步和异步主要区别:能不能开启新的线程
同步:只能在当前线程中执行任务,不具备开启新线程的能力
异步:可以在新的线程中执行任务,有开启新线程的能力
并发和串行的主要区别:任务的执行方式
并发:允许多个任务同时执行
串行:一个任务执行完毕之后在执行下一个任务
排列组合共有四种方式:
/**
* 异步的+全局队列 最常用
*/
- (void)asyncGlobalQueue
{
dispatch_queue_t queue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSLog(@"1---%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2---%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"3---%@",[NSThread currentThread]);
});
}
/**
* 同步+并行队列
*/
- (void)syncGlobalQueue
{
dispatch_queue_t queue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_sync(queue, ^{
NSLog(@"1---%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"2---%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"3---%@",[NSThread currentThread]);
});
}
/**
* 异步的串行队列, 一个一个按顺序执行
*/
- (void)asyncSerialQueue
{
dispatch_queue_t queue=dispatch_queue_create("com.zhangkun", NULL);
dispatch_async(queue, ^{
NSLog(@"1---%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2---%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"3---%@",[NSThread currentThread]);
});
}
/**
* 同步的串行队列, 一个一个按顺序执行
在主献
*/
- (void)syncSerialQueue
{
dispatch_queue_t queue=dispatch_queue_create("com.zhangkun", NULL);
dispatch_sync(queue, ^{
NSLog(@"1---%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"2---%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"3---%@",[NSThread currentThread]);
});
}
GCD线程之间的通信:
// 返回主线程
dispatch_sync(dispatch_get_main_queue(), <#^(void)block#>)
self performSelectorOnMainThread:<#(nonnull SEL)#> withObject:<#(nullable id)#> waitUntilDone:<#(BOOL)#>
GCD常用的函数:
// 在前面的任务执行结束后 它才执行,而且它后面的任务等它执行完成之后才会执行
dispatch_barrier_async(<#dispatch_queue_t queue#>, <#^(void)block#>)
// 延迟执行
[self performSelector:@selector(run) withObject:param afterDelay:3];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"----2秒之后执行");
});
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:NO];
// 整个程序运行过程中只执行一次, 跟懒加载不同 (一次性代码)
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"只会执行一次,资源加载 线程安全的");
});
// 遍历线程
dispatch_apply(10, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t) {
});
GCD队列小案例:
//1. 队列组
dispatch_group_t group = dispatch_group_create();
//2.
__block UIImage *image = nil;
__block UIImage *image1 = nil;
dispatch_group_async(group, kGlobalQueue, ^{
//下载一张·图片
NSURL *url= [NSURL URLWithString:@"http://img5.iqilu.com/c/u/2015/0811/1439259819581.jpg"];
NSData *data=[NSData dataWithContentsOfURL:url];
image=[UIImage imageWithData:data];
});
dispatch_group_async(group, kGlobalQueue, ^{
//下载第二张图片
NSURL *url1= [NSURL URLWithString:@"https://www.baidu.com/img/bd_logo1.png"];
NSData *data1=[NSData dataWithContentsOfURL:url1 ];
image1=[UIImage imageWithData:data1];
});
//保证组里边的事情都执行完, 才执行block块
dispatch_group_notify(group, kGlobalQueue, ^{
//合并图片 搞一张大的图片, 然后把第一张图片画上去
//开启图片上下文
UIGraphicsBeginImageContextWithOptions(image.size, NO, 0);
[image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)];
[image1 drawInRect:CGRectMake(0, 0, image1.size.width*0.5, image1.size.height*0.5)];
//绘制完毕,得到上文的图片
UIImage *imageC= UIGraphicsGetImageFromCurrentImageContext();
//结束图片上下文
UIGraphicsEndImageContext();
//回到主线程设置图片
dispatch_async(kMainQueue, ^{
NSLog(@"%@",[NSThread currentThread]);
[self.img setImage:imageC];
});
});
使用GCD实现单例模式(设计模式,保证在程序运行的过程中一个类只有一个实例, 在整个应用程序中要共享同一份资源)
实现方式(想办法让对象的内存空间只存在一份, 也可以在load中初始化,也可以使用@synchronized,这里就演示了);
/**
* alloc 内部会调用这个方法
*/
+(instancetype)allocWithZone:(struct _NSZone *)zone
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [super allocWithZone:zone];
});
return _instance;
}
+ (instancetype)shareStudentTool
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[self alloc]init];//防止创建多次
});
return _instance;
}
-(id)copyWithZone:(NSZone *)zone
{
return _instance;
}
在 pch中使用:
//.h
#define kSingH(name) + (instancetype)shareTool##name;
//.m
#define kSingM(name) static id _instance;\
+(instancetype)allocWithZone:(struct _NSZone *)zone\
{\
static dispatch_once_t onceToken;\
dispatch_once(&onceToken, ^{\
_instance=[super allocWithZone:zone];\
});\
return _instance;\
}\
+ (instancetype)shareTool##name\
{\
static dispatch_once_t onceToken;\
dispatch_once(&onceToken, ^{\
_instance=[[self alloc]init];\
});\
return _instance;\
}\
-(id)copyWithZone:(NSZone *)zone\
{\
return _instance;\
\
}
以后想使用单例直接在.h .m 中调用定义好的宏就好了,是不是很简单;
NSOperation:基于GCD的,对GCD的一层封装,自动管理;
多线程的安全隐患:
NSOperation 跟 NSOperationQueue
NSOperation 并不具备封装任务的能力,使用NSOperation子类有三种:
NSInvocationOperation
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
NSInvocationOperation *invo = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download) object:nil];
//operation 调用start,是同步执行
[invo start];
//只有将操作放在队列中,才会异步执行
[queue addOperation:invo];
NSBlockOperation
自定义子类继承NSOperation
NSOperationQueue : 并发队列 / 串行队列
+ (nullable NSOperationQueue *)currentQueue NS_AVAILABLE(10_6, 4_0);
+ (NSOperationQueue *)mainQueue NS_AVAILABLE(10_6, 4_0);
// 主队列
[NSOperationQueue mainQueue];
// 同时包含了:串行 并发 的功能 ,就会自动放到子线程中
NSOperationQueue *queque = [[NSOperationQueue alloc]init];
NSOperationQueue 的挂起和取消 (suspended):
[queue cancelAllOperations];//取消队列中的任务
[queue setSuspended:YES];//暂停队列中的任务
[queue setSuspended:NO];// 恢复队列中的任务
addDependency : 依赖
//假如有ABC三个操作, 要求, 三个操作 异步执行
NSOperationQueue *queue=[[NSOperationQueue alloc]init];
NSBlockOperation *operaA=[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1%@",[NSThread currentThread]);
}];
[operaA setCompletionBlock:^{
NSLog(@"1%@",[NSThread currentThread]);
}];
NSBlockOperation *operaB=[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"2%@",[NSThread currentThread]);
}];
NSBlockOperation *operaC=[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"4%@",[NSThread currentThread]);
}];
[operaB addDependency:operaA];
[operaC addDependency:operaB];
[queue addOperation:operaA];
[queue addOperation:operaB];
[queue addOperation:operaC];
资源共享: 一块资源可能会被多个线程共享,也就是多个线程可能会访问同一块儿资源;
比如多个线程同时访问 同一个对象 同一个变量 同一个文件;
老生常谈的demo:
1.存取钱, 多个任务在多条线程同时执行存取钱的任务;
2.卖票, 多个窗口同时在卖票,先查询票数够不够,出现一张票被多个人买走的问题;
解决方案: 加互斥锁 ,线程同步
优点:能有效的防治因多线程抢夺资源造成的数据安全隐患 (互斥锁的应用场景)
缺点:需要消耗大量的CPU资源
使用@synchronized将要锁的代码{}起来
while (1) {
@synchronized(self){//开始加锁
int count = self.lastSale;
if (count > 0) {
self.lastSale = count-1;
[NSThread sleepForTimeInterval:0.1];
NSLog(@"%@---%d",[NSThread currentThread].name,count);
}else{return;}
补充: 原子跟非原子属性
atomic: 原子属性,为setter方法加锁 (默认是atomic), 消耗大量的资源
nonatomic: 非原子属性,不会加锁, iOS开发建议 都声明为nonatomic
线程之间的通信:
1.图片下载
主线程添加UIImageView,子线程下载图片,在回到主线程渲染图片;
2.NSPort NSMessagePort NSMachPort 可以在线程之间进行通信
主线程(port:8080) 传子线程(port:8181)
事件处理
Runtime
Runloop : 既能保住线程的命, 就能让线程继续工作;