多线程基本概念
单核CPU,同一时间cpu只能处理1个线程,只有1个线程在执行 。多线程同时执行:是CPU快速的在多个线程之间的切换。如果线程数非常多,线程切换会消耗大量的cpu资源。相同时间内,每个线程被调度的次数会降低,线程的执行效率降低 。
优点:
能适当提高程序的执行效率
能适当提高资源的利用率(CPU&内存)
线程上得任务执行完后自动销毁
缺点:
开启线程需要占用一定的内存空间(默认情况下,每一个线程都占512KB)
如果开启大量的线程,会占用大量的内存空间,降低程序的性能
线程越多,cpu在调用线程上的开销就越大
程序设计更加复杂,比如线程间的通信、多线程的数据共享
主线程:
一个程序运行后,默认会开启1个线程,称为“主线程”或“UI线程”
主线程一般用来 刷新UI界面 ,处理UI事件(比如:点击、滚动、拖拽等事件)
主线程使用注意 �别将耗时的操作放到主线程中�耗时操作会卡住主线程,严重影响UI的流畅度,给用户一种卡的坏体验
iOS中目前有四种实现多线程的方案:
1.pthread
2.NSThread
3.GCD
4.NSOperation
pthread
一套在多操作系统上通用的多线程API,跨平台、可移植性强,基于C语言。线程的生命周期需要程序员自己管理,基本不怎么使用。
简单使用:
创建一个子线程并在子线程中执行一个函数。
#import <pthread.h>
#pragma mark --pthread
- (void)pthreadDemo1{
pthread_t PID;
//参数1:pthread_t 线程的标示
//参数2:pthread_attr_t 线程的属性
//参数3:void* (*) (void *) 函数签名, void *大约可以理解为oc中的id
// 返回值 函数名 参数
//参数4:给函数(参数3)的参数
//返回值:0 成功,非0 失败
int result = pthread_create(&PID, NULL, task, NULL);
if (result == 0) {
NSLog(@"ok");
}else{
NSLog(@"fail");
}
}
void * task(void *param){
NSLog(@"task is running %@",[NSThread currentThread]);
return NULL;
}
- (IBAction)clicked:(id)sender {
NSLog(@"main thread:%@",[NSThread currentThread]);
[self pthreadDemo1];
}
- (void)pthreadDemo2{
pthread_t PID;
NSString *str = @"str";
// char * str2 = "str2";
// int result = pthread_create(&PID, NULL, task, str2);
int result = pthread_create(&PID, NULL, task, (__bridge void *)(str));
if (result == 0) {
NSLog(@"ok");
}else{
NSLog(@"fail");
}
}
void * task(void *param){
// printf("%s\n",param);
NSString *str = (__bridge NSString *)(param);
NSLog(@"正在运行的线程: %@, ->%@",[NSThread currentThread],param);
NSLog(@"正在运行的线程:%@, ->%@",[NSThread currentThread],str);
return NULL;
}
注意:ARC默认下对OC对象进行内存管理,不对C变量管理,桥接的作用是让C变量在合适的时候进行释放。在ARC中使用到和C语言对应的数据类型,就应该使用__bridge进行桥接,在MRC中则不需要。
使用pthread需要自己管理线程的生命周期,上面的代码中创建了线程但是没有销毁。
NSThread
面向对象,可以直接操作线程对象,基于OC语言。线程的生命周期仍然需要程序员自己进行管理,使用频率不高。可以通过[NSThread currentThread]获取当前线程,以此获取线程的各种属性,便于调试。
创建和启动线程
- 需要手动启动
//参数1:对象
//参数2:方法
//参数3:参数2方法需要的参数
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(task2) object:nil];
[thread start];//线程启动后在thread中执行task2方法
- 自动启动
[NSThread detachNewThreadSelector:@selector(task2) toTarget:self withObject:nil];
或者:
//隐式创建并自动启动线程
[self performSelectorInBackground:@selector(task2) withObject:nil];
后面这两种创建线程并自动启动线程的方法有一个缺点:没有办法对线程的一些属性进行设置。
线程的状态
-
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(task3) object:nil];
线程新建状态。在内存中创建出一个线程对象。
-
[thread start];
线程就绪状态。线程对象被放入可调度线程池,池中还有别的其他线程对象。等待CPU调度。
CPU调度当前线程,线程进入运行状态。当CPU调度其他线程,线程回到就绪状态。
-
[NSThread sleepForTimeInterval:1];
线程阻塞状态。当调用了sleep方法,或者在等待同步锁。线程对象移出可调度线程池
当sleep时间到时或得到了同步锁,回到就绪状态。
-
线程死亡。线程任务执行完毕,自然死亡;强制退出,手动杀死。线程一旦死亡了,就不能再次开启任务。
- (void)stateDemo{
//新建状态
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(task3) object:nil];
//就绪状态
[thread start];
}
- (void)task3{
//运行
NSLog(@"正在运行的线程:%@",[NSThread currentThread]);
//阻塞
NSLog(@"线程即将进入阻塞状态");
[NSThread sleepForTimeInterval:1];
// [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]];//线程休眠时间
//从阻塞->就绪->运行。被cpu调用,进入运行状态
NSLog(@"线程唤醒");
//线程死亡。可以自然死亡也可以被手动杀死,当线程任务执行完毕,自动进入死亡状态
//手动杀死
[NSThread exit];
NSLog(@"dead");//这句话将不会被打印出来
//一旦线程死亡,就不能再次开启任务
}
常用属性、方法
更多的属性、方法请参阅文档
名字属性。
@property (nullable, copy) NSString *name;
设置线程名:
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(task4) object:nil];
thread.name = @"thread";
获得当前线程
+ (NSThread *)currentThread;
获得主线程
+ (NSThread *)mainThread;
是否为主线程
- (BOOL)isMainThread;
线程优先级
+ (double)threadPriority;
设置优先级:
- (void)attrDemo2{
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(task4) object:nil];
thread.name = @"thread";
thread.threadPriority = 0;
[thread start];
NSThread *thread2 = [[NSThread alloc]initWithTarget:self selector:@selector(task4) object:nil];
thread2.name = @"thread2";
[thread2 start];
}
- (void)task4{
for (int i = 0; i<20; i++) {
NSLog(@"当前线程:%@,%d",[NSThread currentThread],i);
}
}
线程优先级取值范围0.0-1.0,默认为0.5,值越大优先级越高。设置优先级并不意味着优先级高的线程要比优先级低的线程先运行(运行反映的结果就是,打印结果里面显示的第一条是哪个thread),只是更可能被CPU执行到。先执行哪个线程是由cpu调度决定的。
线程间通信
常用方法:NSThread类
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
GCD
旨在替代NSThread等的线程技术,充分利用多核,基于C语言。线程的生命周期是自动管理的,不需要程序员手动管理线程的创建/销毁/复用过程。经常使用。
任务、队列
任务和队列是GCD中的两个核心概念。
任务就是要执行什么操作,在GCD中通过block来指定要执行的代码。队列用来存放任务。
GCD使用的两个步骤:
1.定制任务:确定想做的事情
2.将任务添加到队列中,指定运行方式。
GCD会自动将队列中的任务取出,放到对应的线程中执行。任务的取出遵循队列的FIFO原则,先进先出。
//创建任务
//dispatch_block_t的定义typedef void (^dispatch_block_t)(void);任务实际上是一个Block
dispatch_block_t task = ^{
NSLog(@"task %@",[NSThread currentThread]);
};
//获取队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//把任务放到队列中
//参数1:队列 参数2:任务
dispatch_async(queue, task);
//简化
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"task %@",[NSThread currentThread]);
});
不需要管理线程的生命周期;线程能够复用
for (int i = 0; i < 20; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"task %@ %d",[NSThread currentThread] , i);
});
}
- 队列
队列的类型:并发队列、串行队列。
并发队列:可以让多个任务并发(同时)执行(自动开启多个线程“同时”执行任务)。并发功能只有在异步时才有效。
串行队列:无论是同步异步,任务都是一个接一个地执行。
无论是串行队列还是并发队列,队列里面的任务取出都遵循FIFO原则。并发队列取出任务就分发到可用的线程里,取出的动作很快,就相当于是所有任务都是一起执行的。
全局并发队列:供整个应用使用,GCD有函数可以获得,不需要手动创建
主队列:它是特殊的串行队列,又叫全局串行队列,代表着主线程。
队列的创建:
dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
第一个参数是唯一标识符,也可以当做是队列的名称,用于debug。第二个参数是队列的属性,表示创建的是并行队列还是串行队列,DISPATCH_QUEUE_SERIAL或者NULL为串行,DISPATCH_QUEUE_CONCURRENT为并行。
dispatch_get_main_queue()
获得主队列
dispatch_get_global_queue(long identifier, unsigned long flags);
获得全局并发队列.参数1为队列的优先级。参数2暂时没有用,填0即可。
全局队列 、 并发队列的区别:
全局队列没有名称,无论 MRC & ARC 都不需要考虑释放.
并发队列有名字,和 NSThread 的 name 属性作用类似;如果在 MRC 开发时,需要使用 dispatch_release(q); 释放相应的对象;dispatch_barrier 必须使用自定义的并发队列;开发第三方框架时,建议使用并发队列
- 任务
任务的执行方式:同步、异步。同步异步的区别是,是否会阻塞当前线程。
同步执行会阻塞当前线程,等到block任务执行完毕,然后当前线程再继续往下运行。异步执行不会阻塞当前线程。通常的表现是:同步是在当前线程中执行,异步是在另一条线程中执行,但也有例外。
通过打断点来看同步异步的运行过程来理解会更直观。
看下面的例子:
串行队列,同步执行
- (void)demo1{
//创建串行队列
//参数1:队列的名字 参数2:队列的属性
dispatch_queue_t serialQueue = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);
for (int i = 0; i < 10 ;i++ ){
dispatch_sync(serialQueue, ^{
NSLog(@"serialQueue %@ %d",[NSThread currentThread],i);
});
}
//用途:在多个线程时,要确保在一个线程执行完任务再去执行另一个线程上的任务
}
运行结果显示:不开线程,在当前线程下执行(阻塞当前线程);任务是有序执行的。
串行队列,异步执行
- (void)demo2{
dispatch_queue_t serialQueue = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);
for (int i = 0; i < 10 ;i++ ){
dispatch_async(serialQueue, ^{
NSLog(@"serialQueue %@ %d",[NSThread currentThread],i);
});
}
}
运行结果显示:另外开一个线程(不会阻塞当前线程);任务是有序执行的。
修改demo1 demo2不进行循环创建,通过打断点来看同步异步的区别:异步,当代码块执行时demo2:方法已经退出了;同步,demo1:一直等到代码块执行完才退出
并行队列,异步执行
- (void)demo3{
//创建并行队列
dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);
for (int i =0; i<10; i++) {
dispatch_async(concurrentQueue, ^{
NSLog(@"concurrent %@ %d",[NSThread currentThread],i);
});
}
}
运行结果显示:开多个线程(不会阻塞当前线程),任务无序执行。效率最大。每次开启多少个线程是不固定的(线程数由GCD来决定)
并行队列,同步执行
- (void)demo4{
dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);
for (int i =0; i<10; i++) {
dispatch_sync(concurrentQueue, ^{
NSLog(@"concurrent %@ %d",[NSThread currentThread],i);
});
}
}
运行结果显示:不开线程,在当前线程下执行(当前线程不一定是主线程);任务是有序执行的。这种情况等同于,串行队列同步执行
主队列,异步执行
- (void)demo5{
//得到主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
for (int i =0; i<10; i++) {
dispatch_async(mainQueue, ^{
NSLog(@"mainQueue %@",[NSThread currentThread]);
});
}
}
主队列是特殊的串行队列,永远在主线程执行。运行结果显示:任务顺序执行,由于是异步,因此并不会阻塞当前线程(主线程)。
主队列,同步执行
- (void)demo6{
NSLog(@"开始");
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_sync(mainQueue, ^{
NSLog(@"mainQueue %@",[NSThread currentThread]);//这个任务要等待-demo6执行完成才能继续执行
});
NSLog(@"结束");
}
运行的结果显示,只会打印出“开始”这一句,然后后面主线程就卡死了。程序死锁。
原因:同步执行会阻塞当前线程。当前-demo6方法在主线程中执行,它把block任务放到主队列中执行,即执行到dispatch_sync()的时候,主线程就被阻塞了。主线程要等到任务执行完成之后才会继续往下执行,而block任务要等到主线程中的-demo6方法执行完之后才能执行。由此造成死锁,主线程卡死。
总结:
线程间通信
dispatch_async(dispatch_get_global_queue(0, 0), ^{ //也叫后台执行
// 执行耗时的异步操作...
dispatch_async(dispatch_get_main_queue(), ^{
// 回到主线程,执行UI刷新操作
});
});
队列组
队列组可以将很多队列添加到一个组里,当这个组里所有的任务都执行完了,队列组会通过一个方法通知我们。
假设现在有这样一个需求:分别异步执行两个耗时操作;等两个异步操作都执行完毕,再回到主线程执行操作。这时可以使用队列组来高效实现。
-(void)demo{
NSLog(@"begin");
//创建组
dispatch_group_t group = dispatch_group_create();
//开启异步任务,参数1:队列组;参数2:队列;参数3:任务
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
[NSThread sleepForTimeInterval:arc4random_uniform(5)];
NSLog(@"下载 文件1.zip %@",[NSThread currentThread]);
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
[NSThread sleepForTimeInterval:arc4random_uniform(5)];
NSLog(@"下载 文件2.zip %@",[NSThread currentThread]);
});
//完成队列组的任务后进行通知,参数1:队列组;参数2:队列;参数3:任务
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"下载完成 %@",[NSThread currentThread]);
});
}
一次性执行
被调用多次,但只会执行一次。它是线程安全的
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
// for (int i = 0; i < 20; i++) {
// [self demo];
// }
//多线程测试
for (int i = 0; i < 20; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self demo];
});
}
}
-(void)demo{
NSLog(@"这句话执行多次");
static dispatch_once_t onceToken;
NSLog(@"%ld",onceToken);
dispatch_once(&onceToken, ^{
NSLog(@"once");
});
}
dispatch_once_t 实际上被定义为long类型,demo方法中打印出onceToken的值中可以看出,在代码第一次被调用的时候,onceToken的值为0(即使在多线程下可能会有多个0值打印出来),此时block里面的代码会被执行而且只会执行一次。当代码再次被调用,onceToken的值为非0,block代码不会被执行。onceToken值相当于是一个flag,用来标记block里面的代码是否已经被执行过。
单例模式
OC里面实现单例在此不进行详述,用到了 GCD 的 dispatch_once方法。
实现单例还可以用互斥锁,但相比GCD性能要差得多(没有得到锁的线程一直在等待。)
@implementation Singleton
+(instancetype)configSync{
static Singleton *instance;
@synchronized(self) {
if(instance == nil){
instance = [[Singleton alloc] init];
}
}
return instance;
}
@end
NSOperation
面向对象,基于GCD封装,比GCD多一些简单的功能,基于OC语言。线程的生命周期是自动管理的,经常使用。
NSOperation
和NSOperationQueue
就相当于是GCD 的任务和队列。
NSOperation实现多线程的具体步骤
1.将需要执行的任务封装到一个NSOperation对象中
2.将NSOperation对象添加到NSOperationQueue中
3.系统会自动将NSOperationQueue中的NSOperation取出来
4.NSOperation封装的任务被放到一条新线程中执行
任务NSOperation
NSOperation是一个抽象类,不具备封装任务的能力,不可以直接使用,它只是约束子类都具有共同的属性和方法。因此必须使用它的子类:
1.NSInvocationOperation
2.NSBlockOperation
3.自定义子类,继承NSOperation
,实现内部相应的方法
子类创建operation
使用的方法虽不尽相同,但最后都需要调用start方法来启动执行任务。默认情况下,调用start
方法后不会新开一个线程执行任务,而是在当前线程同步执行,只有将NSOperation
放入一个NSOperationQueue
中,才会异步执行操作。
- NSInvocationOperation比较少用
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
//创建operation
NSInvocationOperation *op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(demo1) object:nil];//任务在主线程(当前线程)运行
//启动任务
[op start];
}
- (void)demo1{
NSLog(@"任务1当前线程----%@",[NSThread currentThread]);//在主线程运行
}
- NSBlockOperation
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
//创建operation
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"当前线程----%@",[NSThread currentThread]);
}];//还是主线程
//额外的任务在子线程执行
[op addExecutionBlock:^{
NSLog(@"当前线程----%@",[NSThread currentThread]);
}];
[op start];
}
addExecutionBlock
方法给operation添加额外的任务,这时operation中的所有任务并发执行(当前线程和其他子线程)只要NSBlockOperation封装的任务数大于1,就会异步执行?
- 自定义子类
当以上两个子类无法满足需求时,又或者需要封装任务等等,就需要自定义operation。
自定义operation需要继承NSOperation
类,并实现main
方法,因为在调用start
方法的时候,内部会调用main
方法完成相关逻辑。重写main方法的注意点:
1.自己创建自动释放池(因为如果是异步操作,无法访问主线程的自动释放池)【这个不太理解】
2.经常通过-(BOOL)isCancelled
方法检测操作是否被取消,对取消作出响应
具体举例下面用到再细说。
队列NSOperationQueue
NSOperation可以调用start方法来执行任务,但因为是同步执行,会占用当前线程。但如果把NSOperation添加到NSOperationQueue中,就可以异步执行任务。
NSOperationQueue队列类型:
1.主队列[NSOperationQueue mainQueue]
添加到主队列中的任务都会放到主线程中执行
2.其他队列(包括串行、并发),使用alloc init
方式创建。任务只要添加到队列,系统就会自动异步执行任务(自动调用start方法)。
用maxConcurrentOperationCount(最大并发数)属性控制是串行还是并发队列添加任务的方法:
- (void)addOperation:(NSOperation *)op;
- (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait;
- (void)operationQueueDemo1{
//将NSOperation添加到NSOperationQueue中,系统会自动**异步执行**任务
//创建队列
NSOperationQueue *opqueue = [[NSOperationQueue alloc]init];
//创建operation
NSInvocationOperation *op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(demo1) object:nil];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任务2当前线程----%@",[NSThread currentThread]);
}];
[op2 addExecutionBlock:^{
NSLog(@"任务3当前线程----%@",[NSThread currentThread]);
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任务4当前线程----%@", [NSThread currentThread]);
}];
CustomOperation *op4 = [[CustomOperation alloc] init];//自定义operation
// 添加任务到队列中
[opqueue addOperation:op];//一旦添加,就会执行(自动start),并发,且每添加一个operation开一条子线程
[opqueue addOperation:op2];
[opqueue addOperation:op3];
[opqueue addOperation:op4];//添加自定义operation,自动调用start,start调用main
//添加任务的另一种更简洁的方式,和上面是等价的
[opqueue addOperationWithBlock:^{
NSLog(@"block添加任务,block当前线程----%@",[NSThread currentThread]);
}];
}
ps:在这里使用了自定义operation
#import "CustomOperation.h"
@implementation CustomOperation
//封装自定义任务,自动调用这个方法
- (void)main
{
NSLog(@"自定义operation");
}
@end
-
队列最大并发数
@property NSInteger maxConcurrentOperationCount;
这个属性用来设置最多可以让多少个任务同时执行。所以,把maxConcurrentOperationCount
的值设为1,那么队列就是串行的了!
- (void)operationQueueDemo2{
NSOperationQueue *opqueue = [[NSOperationQueue alloc]init];
// opqueue.maxConcurrentOperationCount = 2;
//串行队列
opqueue.maxConcurrentOperationCount = 1;
[opqueue addOperationWithBlock:^{
NSLog(@"block添加任务,任务1当前线程----%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0];
}];
[opqueue addOperationWithBlock:^{
NSLog(@"block添加任务,任务2当前线程----%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0];
}];
[opqueue addOperationWithBlock:^{
NSLog(@"block添加任务,任务3当前线程----%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0];
}];
[opqueue addOperationWithBlock:^{
NSLog(@"block添加任务,任务4当前线程----%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0];
}];
[opqueue addOperationWithBlock:^{
NSLog(@"block添加任务,任务5当前线程----%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0];
}];
}
maxConcurrentOperationCount=2时运行结果:
maxConcurrentOperationCount=1时运行结果:
任务执行在哪个线程不是固定的。比如当前最大并发数为2,在同一时间并发执行任务的线程的确有2个,但并不是固定一直都是这两个线程去处理任务。当一个线程执行完任务后,由系统来决定销毁它另开线程还是继续使用这个线程来执行任务
-
队列的暂停、恢复、取消
暂停、恢复:suspended
属性
要执行suspended=yes时候,队列挂起,但如果有任务还没执行完,那么这个任务将会继续执行到完成。而队列中余下的任务就会被挂起不执行。
当suspended属性设为No的时候,再继续执行余下的任务。
队列取消:- (void)cancelAllOperations;
cancelAllOperations
取消队列所有的任务,取消了就不会再恢复。This method calls the cancel method on all operations currently in the queue.
同样,如果有任务还没执行完,那么这个任务将会继续执行到完成。
如果自定义operation中有多个耗时的操作,建议在main方法中在每个耗时操作后判断任务是否已经被取消了,如果取消了,余下的耗时操作将不再执行
#import "CustomOperation.h"
@implementation CustomOperation
- (void)main
{
for (NSInteger i = 0; i<1000; i++) {
NSLog(@"download1 -%zd-- %@", i, [NSThread currentThread]);
}
if (self.isCancelled) return;
for (NSInteger i = 0; i<1000; i++) {
NSLog(@"download2 -%zd-- %@", i, [NSThread currentThread]);
}
if (self.isCancelled) return;
for (NSInteger i = 0; i<1000; i++) {
NSLog(@"download3 -%zd-- %@", i, [NSThread currentThread]);
}
if (self.isCancelled) return;
}
@end
- 依赖
NSOperation之间可以设置依赖来保证执行顺序。[op1 addDependency:op2]
可以让任务op1在任务op2之后执行。
这种任务依赖是可以跨队列的。不能添加相互依赖,会死锁,比如 A依赖B,B依赖A。
- (void)operationQueueDemo4{
NSOperationQueue *opqueue = [[NSOperationQueue alloc] init];
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任务1----%@", [NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任务2----%@", [NSThread currentThread]);
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任务3----%@", [NSThread currentThread]);
}];
NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任务4----%@", [NSThread currentThread]);
}];
//监听任务是否执行完毕,这个block在任务完成之后执行,而且它不在主线程上,也与它监听的任务不一定在同一个线程上
op4.completionBlock = ^{
NSLog(@"op4执行完毕---%@", [NSThread currentThread]);
};
// 设置依赖,而且这种依赖是可以跨队列的
[op2 addDependency:op3];
//添加完依赖再把任务加到队列上
[opqueue addOperation:op];
[opqueue addOperation:op2];
[opqueue addOperation:op3];
[opqueue addOperation:op4];
}
线程间通信
[[[NSOperationQueue alloc] init] addOperationWithBlock:^{
.......
// 回到主线程
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
......
}];
}];
其他
线程同步:
为了防止多个线程可能会访问同一块资源,引发数据错乱和数据安全问题,所采取的措施。
- 互斥锁:给需要同步的代码块加一个互斥锁,就可以保证每次只有一个线程访问此代码块。
售票问题:
@property (nonatomic , assign)int tickets;
- (void)viewDidLoad {
[super viewDidLoad];
self.tickets = 5;
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(sellTicket) object:nil];
thread.name = @"窗口1";
[thread start];
NSThread *thread2 = [[NSThread alloc]initWithTarget:self selector:@selector(sellTicket) object:nil];
thread2.name = @"窗口2";
[thread2 start];
}
- (void)sellTicket{
while (YES) {
//模拟网络延时
[NSThread sleepForTimeInterval:1];
if (self.tickets > 0) {
self.tickets = self.tickets - 1;
NSLog(@"%@ 剩余票数%d",[NSThread currentThread],self.tickets);
continue;
}
NSLog(@"卖完了");
break;
}
}
运行结果:产生数据错乱问题
解决方法:使用互斥锁(同步锁)
格式:@synchronized(锁对象){需要锁定的代码}
优缺点:能有效防止因多线程抢夺资源造成的数据安全问题。但是需要消耗大量的CPU资源。没有得到锁的线程一直在等待。
锁可以是任意对象,默认锁是开着的。
@property (nonatomic , strong) NSObject *obj;
- (void)viewDidLoad {
[super viewDidLoad];
self.tickets = 5;
self.obj = [[NSObject alloc]init];//obj要初始化,锁才有效
......(创建线程)
}
- (void)sellTicket{
while (YES) {
//模拟网络延时
[NSThread sleepForTimeInterval:1];
//锁是一个任意对象(任意对象都有一把锁),默认锁是开着的
@synchronized(self.obj) {
if (self.tickets > 0) {
self.tickets = self.tickets - 1;
NSLog(@"%@ 剩余票数%d",[NSThread currentThread],self.tickets);
continue;
}
NSLog(@"卖完了");
break;
}
}
}
运行结果:是线程安全的
注意:锁定一份代码只能用一把锁,使用多把锁无效。如果把上面的代码改成下面这样,同样会产生数据错乱问题。因为当前while循环中每次循环都创建一把新的锁,锁默认都是开着的。
while (YES) {
[NSThread sleepForTimeInterval:1];
NSObject *obj = [[NSObject alloc]init];
@synchronized(obj) {
......
}
NSLog(@"卖完了");
break;
}
}
atomic 和nonatomic:
nonatomic非原子属性:多个线程可以同时赋值同时读取。非线程安全的,适合内存小的移动设备。开发中建议把属性声明为nonatomic
atomic原子属性:多个线程中只有一个线程能够对变量赋值(为setter加锁),但多个线程可以同时读取。线程安全,但需要消耗大量资源。原子属性有自旋锁
互斥锁:如果发现其他线程正在执行锁定的代码,线程休眠(就绪状态),等其他线程开锁后,线程被唤醒。
自旋锁:如果发现其他线程正在执行锁定代码,线程会用死循环的方式一致等待锁定代码完成。自旋锁适合执行不耗时的操作。
不使用互斥锁,如果把tickets声明为atomic,再执行第一段“售票问题”代码的话会怎么样呢?@property (nonatomic , assign)int tickets;
运行结果显示,自旋锁仍然会有线程安全问题。因为它只对setter加锁,对Getter不加锁。
延时执行:
方式1:NSTimer
NSTimer *timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(task) userInfo:nil repeats:NO];
方式2:
[self performSelector:@selector(task) withObject:nil afterDelay:1];
方法3:GCD
//参数1:延时的时间 dispatch_time生成时间 纳秒为计时单位 精度高
//参数2:队列
//参数3:任务
//异步执行
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"task");
});