多线程一

一、线程方式:NSThread,NSQueueOperation, GCD, pthread

二、僵尸线程问题(NSThead操作)

  • 在子线程添加定时器,如果在主线程中移除定时器会造成僵尸线程。
    1、在子线程添加定时器,必须要执行[[NSRunLoop currentRunLoop] run]。因为runloop中的资源没被移除。
    2、在子线程中无法执行到NSLog(@"runloop finished")这段代码。
    3、触摸屏幕时,将定时器从runloop中移除,但是定时器的时间依旧还在执行。
- (void)viewDidLoad {
    [super viewDidLoad];
    NSString * str = @"12211212";
    [NSThread detachNewThreadSelector:@selector(threadEvent:) toTarget:self withObject:str];
}
- (void)threadEvent:(id)thread{
    NSLog(@"%@",[NSThread currentThread]);
    //self.timer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(timeEvent:) userInfo:@"2121" repeats:YES];
    self.timer = [NSTimer timerWithTimeInterval:2 target:self selector:@selector(timeEvent:) userInfo:nil repeats:YES];
    //使用 NSRunLoopCommonModes 模式,这个模式相当于 NSDefaultRunLoopMode 和 NSEventTrackingRunLoopMode 的结合
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run];
    NSLog(@"runloop finished");
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [self.timer invalidate];
}
- (void)timeEvent:(NSTimer *)sender{
    //定时器事件在子线程中执行
    NSLog(@"%@",[NSThread currentThread]);
    NSLog(@"%s",__func__);
}

线程依旧存在,如图一所示。


图一 僵尸线程图.png
  • 如何解决僵尸线程
    在该线程中移除掉定时器。
- (void)viewDidLoad {
    [super viewDidLoad];
    self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadEvent:) object:self];
    [self.thread start];
}
- (void)threadEvent:(id)thread{
    NSLog(@"%@",[NSThread currentThread]);
    //self.timer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(timeEvent:) userInfo:@"2121" repeats:YES];
    self.timer = [NSTimer timerWithTimeInterval:2 target:self selector:@selector(timeEvent:) userInfo:nil repeats:YES];
    //使用 NSRunLoopCommonModes 模式,这个模式相当于 NSDefaultRunLoopMode 和 NSEventTrackingRunLoopMode 的结合
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run];
    NSLog(@"runloop finished");
}
- (void)timeInvalidate:(id)sender{
    [self.timer invalidate];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
/*
  You cannot cancel messages queued using this method. 
  If you want the option of canceling a message on the current thread, 
  you must use either the performSelector:withObject:afterDelay: or performSelector:withObject:afterDelay:inModes: method.
要想自线程消灭掉,必须在其子线程中执行performSelector:withObject:afterDelay:或者performSelector:withObject:afterDelay:inModes: 方法
*/
    [self performSelector:@selector(timeInvalidate:) onThread:self.thread withObject:@"e" waitUntilDone:YES];
}
- (void)timeEvent:(NSTimer *)sender{
    //定时器事件在子线程中执行
    NSLog(@"%@",[NSThread currentThread]);
    NSLog(@"%s",__func__);
}

但是依旧不能解决僵尸线程的问题,因为在移除定时器的同时,给runLoop中添加一个timeInvalidate方法。

  • 彻底解决僵尸线程
    1、方法一:
- (void)viewDidLoad {
    [super viewDidLoad];
    self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadEvent:) object:self];
    [self.thread start];
}
- (void)threadEvent:(id)thread{
    NSLog(@"%@",[NSThread currentThread]);
    //self.timer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(timeEvent:) userInfo:@"2121" repeats:YES];
    self.timer = [NSTimer timerWithTimeInterval:2 target:self selector:@selector(timeEvent:) userInfo:nil repeats:YES];
    //使用 NSRunLoopCommonModes 模式,这个模式相当于 NSDefaultRunLoopMode 和 NSEventTrackingRunLoopMode 的结合
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
    [self performSelector:@selector(timeInvalidate:) withObject:nil afterDelay:5];
    [[NSRunLoop currentRunLoop] run];
    NSLog(@"runloop finished");
}
- (void)timeInvalidate:(id)sender{
    [self.timer invalidate];
}
- (void)timeEvent:(NSTimer *)sender{
    //定时器事件在子线程中执行
    NSLog(@"%@",[NSThread currentThread]);
    NSLog(@"%s",__func__);
}

2、方法二:

- (void)viewDidLoad {
    [super viewDidLoad];
    _flag = NO;
    self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadEvent:) object:self];
    [self.thread start];
}
- (void)threadEvent:(id)thread{
    NSLog(@"%@",[NSThread currentThread]);
    self.timer = [NSTimer timerWithTimeInterval:2 target:self selector:@selector(timeEvent:) userInfo:nil repeats:YES];
    //使用 NSRunLoopCommonModes 模式,这个模式相当于 NSDefaultRunLoopMode 和 NSEventTrackingRunLoopMode 的结合
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run];
    NSLog(@"runloop finished");
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    self.flag = YES;
}
- (void)timeEvent:(NSTimer *)sender{
    //定时器事件在子线程中执行
    NSLog(@"%@",[NSThread currentThread]);
    NSLog(@"%s",__func__);
    if(self.flag == YES){
        [self.timer invalidate];
        self.timer = nil;
    }
}
  • 在runloop中添加port,如果在其它线程中移除掉,会产生僵尸线程。
- (void)viewDidLoad {
    [super viewDidLoad];
    self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadEvent:) object:self];
    [self.thread start];
}
- (void)threadEvent:(id)thread{
    NSLog(@"start");
    self.myRunLoop = [NSRunLoop currentRunLoop];
    self.port = [[NSPort alloc] init];
    [self.myRunLoop addPort:self.port forMode:NSRunLoopCommonModes];
    [self.myRunLoop run];
    NSLog(@"finished");
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [self.myRunLoop removePort:self.port forMode:NSRunLoopCommonModes];
}
  • 解决在runloop中添加port产生僵尸线程
- (void)viewDidLoad {
    [super viewDidLoad];
    self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadEvent:) object:self];
    [self.thread start];
}
- (void)threadEvent:(id)thread{
    NSLog(@"start");
    self.myRunLoop = [NSRunLoop currentRunLoop];
    self.port = [[NSPort alloc] init];
    [self.myRunLoop addPort:self.port forMode:NSRunLoopCommonModes];
    [self performSelector:@selector(portCancel) withObject:nil afterDelay:5];
    [self.myRunLoop run];
    NSLog(@"finished");
}
- (void)portCancel{
    [self.myRunLoop removePort:self.port forMode:NSRunLoopCommonModes];
}

结论:
在子线程中,给runloop添加资源,必须在该线程中进行释放,否则会产生僵尸线程。

三、线程队列NSQueueOperation

3.1 Operation对象的并发和非并发

3.2 系统的Operation对象

3.2.1 NSBlockOperation

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSOperationQueue * queue = [[NSOperationQueue alloc] init];
    queue.maxConcurrentOperationCount = 2;
    NSBlockOperation * blockOperation = [[NSBlockOperation alloc] init];
    [blockOperation addExecutionBlock:^{
        NSLog(@"%@",[NSThread currentThread]);
    }];
    [blockOperation addExecutionBlock:^{
        NSLog(@"%@",[NSThread currentThread]);
    }];
    [blockOperation addExecutionBlock:^{
        NSLog(@"%@",[NSThread currentThread]);
    }];
    [blockOperation addExecutionBlock:^{
        NSLog(@"%@",[NSThread currentThread]);
    }];
    [blockOperation addExecutionBlock:^{
        NSLog(@"%@",[NSThread currentThread]);
    }];
    [blockOperation addExecutionBlock:^{
        NSLog(@"%@",[NSThread currentThread]);
    }];
    [blockOperation addExecutionBlock:^{
        NSLog(@"%@",[NSThread currentThread]);
    }];
    [queue addOperation:blockOperation];
}

打印结果:

<NSThread: 0x608000270b40>{number = 5, name = (null)}
<NSThread: 0x608000270940>{number = 4, name = (null)}
<NSThread: 0x608000270b80>{number = 6, name = (null)}
<NSThread: 0x608000270900>{number = 3, name = (null)}
<NSThread: 0x608000270b40>{number = 5, name = (null)}
<NSThread: 0x608000270940>{number = 4, name = (null)}
<NSThread: 0x608000270b80>{number = 6, name = (null)}

3.3.2 NSInvocationOperation(NSInvocation)

  • 使用initWithTarget:selector:object:开启线程。缺点:只能传递一个参数。
- (void)viewDidLoad {
    [super viewDidLoad];
    NSOperationQueue * queue = [[NSOperationQueue alloc] init];
    NSInvocationOperation * invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperationEvent) object:nil];  
    [queue addOperation:invocationOperation];
}
- (void)invocationOperationEvent{
    NSLog(@"%@",[NSThread currentThread]);
}
  • 使用NSInvocation传递多个参数
- (void)viewDidLoad {
    [super viewDidLoad];
    NSOperationQueue * queue = [[NSOperationQueue alloc] init];
    NSMethodSignature * signature = [self methodSignatureForSelector:@selector(name:andAge:andSex:)];
    NSInvocation * invocation = [NSInvocation invocationWithMethodSignature:signature];
    invocation.selector = @selector(name:andAge:andSex:);
    invocation.target = self;
    NSString * name = @"Jerry";
    NSString * age = @"2";
    NSString * sex = @"男";
    //这里的index要从2开始,前两个已经被target和selector占用
    [invocation setArgument:&name atIndex:2];
    [invocation setArgument:&age atIndex:3];
    [invocation setArgument:&sex atIndex:4];
    NSInvocationOperation * invocationOperation = [[NSInvocationOperation alloc] initWithInvocation:invocation];
    [queue addOperation:invocationOperation];
}
- (void)name:(NSString *)name andAge:(NSString *)age andSex:(NSString *)sex{
    NSLog(@"name = %@,age = %@,sex = %@",name,age,sex);
}

3.3 自定义Operation对象

3.3.1 Operation状态(KVO通知)

  • 当isFinished状态为NO时,NSOperation不能执行dealloc。
@interface CustomOperation : NSOperation
@end
@implementation CustomOperation
@synthesize finished = _finished;
- (void)dealloc{
    NSLog(@"%s",__func__);  
}
- (BOOL)isFinished{
    return _finished;
}
@end
- (void)viewDidLoad {
    [super viewDidLoad];
    NSOperationQueue * queue = [NSOperationQueue new];
    CustomOperation * customOperation = [CustomOperation new];
    [queue addOperation:customOperation];
}

CustomOperation不能执行dealloc,用KVO的形式,改变isFinished的值为YES,才能执行CustomOperation才能被释放。

        [self willChangeValueForKey:@"isFinished"];//_finished,finished
        _finished = YES;
        [self didChangeValueForKey:@"isFinished"];

3.3.2 配置Operation执行行为(依赖关系,优先级等)

    NSOperationQueue * operationQueue = [[NSOperationQueue alloc] init];
    NSBlockOperation * blockOperation = [[NSBlockOperation alloc] init];
    [blockOperation addExecutionBlock:^{
        NSLog(@"NSBlockOperation = %@",[NSThread currentThread]);
    }];
    NSInvocationOperation * invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperationEvent) object:nil];
    
    NSBlockOperation * blockOperation2 = [[NSBlockOperation alloc] init];
    [blockOperation addExecutionBlock:^{
        NSLog(@"NSBlockOperation2 = %@",[NSThread currentThread]);
    }];
    //在入队前处理好依赖关系
    [blockOperation addDependency:invocationOperation];
    [blockOperation2 addDependency:blockOperation];
    
    [operationQueue addOperation:blockOperation];
    [operationQueue addOperation:invocationOperation];
    [operationQueue addOperation:blockOperation2];

结果:

invocationOperation = <NSThread: 0x6080000740c0>{number = 3, name = (null)}
NSBlockOperation = <NSThread: 0x600000079580>{number = 4, name = (null)}
NSBlockOperation2 = <NSThread: 0x6080000740c0>{number = 3, name = (null)}

⚠️:A线程依赖B线程,B依赖C,C依赖A,这样会产生死锁。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容

  • 首先了解单线程:一、单线程的应用,整个应用中只有一个顺序执行流,当执行流在执行某个耗时的操作,或者不能立即完成的任...
    蓝白自由阅读 565评论 1 8
  • 由于前段时间,堂弟的询问,再加上自己也想重新整理一下知识结构,就梳理一下知识。一系列的文章,可能会很多,也可能会很...
    摄影师诺风阅读 668评论 5 15
  • 简介 1. 线程分类 主线程(UI线程) : 处理和界面相关的事情. 子线程 : 处理耗时操作. Android中...
    王世军Steven阅读 907评论 0 2
  • 线程概述 线程与进程 进程  每个运行中的任务(通常是程序)就是一个进程。当一个程序进入内存运行时,即变成了一个进...
    闽越布衣阅读 1,003评论 1 7
  • 时光匆匆, 溜过我的指缝。 尽管未来迷茫, 但是每天都有所不同。 你说这个冬天很冷, 冷到冻僵了2016的梦。 我...
    大风起兮云飞扬_阅读 154评论 0 1