iOS 多线程开发

作为一个开发人员, 有两个词无论是工作中还是面试中, 都会经常听见, 被问及:"进程""线程"。 在开始了解多线程之前, 先来了解一下这二者的关系和区别: 简单点说, 进程是一个有独立地址空间的, 而线程只是一个进程中执行任务的一个路径, 这是二者有本质的区别, 可以说他们是不同的操作系统资源的管理方式。

做iOS开发,很少会涉及到进程。我们一般会做的就是进程间通信,从当前App跳转到第三方App,例如“QQ”、“微信”或指定App。我们可能会在跳转过程中传递一些参数,在第三方App上做出相应的操作,这就是我目前涉及到的跟进程有关的开发。这里我主要还是想讲的是线程,因为,相比之下,线程在我们开发过程中还是尤为重要的。

线程

可以说,一个进程是由一个或多个线程组成的,进程负责调度多条线程, 真正执行任务,负责代码执行的是线程。

在程序被启动的时候,系统的主线程就被创建,直到程序完全退出,这个线程都会存在。他用来执行main函数。这时的主线程要完成所有的操作,网络请求、UI展示、动画等等。在只有主线程的情况下这些任务都要按顺序一个一个执行, 无法并发执行。所以,我们需要多线程来协助主线程完成这个并发执行的任务。

iOS多线程

多线程很好理解,有多条线程在执行任务。

它其实是针对单核的CPU设计的, 因为单核的同一时间只能执行一个任务, 但是我们可以利用多线程,让CPU快速的在多个线程之间切换,从而给我们一种多个任务在同时进行的错觉。在多核的CPU中, 是真实的达到了, 多个线程同时执行任务。

1. 多线程优缺点

优点:① 适当的提高代码执行效率

           ② 适当提高资源利用率

缺点:① 开启线程过多,会占用大量内存空间,所以要适度

           ② 程序设计更复杂,需要考虑诸如线程安全的问题

2. iOS中的多线程

iOS中多线程主要有四种:NSThread、NSObject、NSOperationQueue、GCD

2.1  NSTread 线程类

优点:1. 轻量级

            2.可以快速创建子线程,并对子线程有控制权

缺点:1. 需要手动管理线程的生命周期

            2.要手动开启子线程、基本信息要手动设置

2.1.1 NSTread的创建

NSTread的创建有三种方式, 后两者相当于对第一种进行了封装,我们不再可以设置线程的基本信息

基本的创建方式:

NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadTask) object:nil];

thread.name = @"线程类";

[thread start];

快速创建,并自动开启:

[NSThread detachNewThreadSelector:@selector(threadTask) toTarget:self withObject:nil];

隐式创建,并自动开启:

[self performSelectorInBackground:@selector(threadTask) withObject:nil];

这里面线程我让他执行threadTask里面的任务, 里面模拟了线程卡死代码,并看看当前是在哪执任务

- (void)threadTask {

if ([NSThread isMainThread]) {

NSLog(@"当前线程 是 主线程,是%@", [NSThread currentThread]);

} else {

NSLog(@"当前线程 非 主线程,是%@", [NSThread currentThread]);

}

NSInteger num = 0;

for (NSInteger i = 0; i < 1000000000; i++) {

num++;

}

NSLog(@"%ld", num);

[self performSelector:@selector(reloadUIView) onThread:[NSThread mainThread] withObject:nil waitUntilDone:YES];

}

这里面用到了几个线程相关的方法:

isMainThread : 查看当前线程是否是主线程(0:非  /  1:是)

currentThread: 查单当前线程

mainThread:     获得主线程

线程间通信:

在某个线程或主线程执行某个任务

-(void)performSelector:(SEL)aSelectoron Thread:(NSThread*)thread withObject:(id)arg waitUntilDone:(BOOL)wait;

-(void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;



2.2 GCD

GCD是iOS开发中应用最为广泛的多线程开发技术,使用得最简单。GCD是苹果公司自己开发的,基于C语言写的,提供了非常强大的函数。

想学懂GCD,不得不先了解一下任务和队列两个核心概念

2.2.1 任务和队列

一、概念:

        任务:执行的操作,通常是我们代码中的方法

        队列:可以理解为用来存放任务的列表

二、分类:

        任务的种类(是否开辟新的线程):

                     同步执行(不开辟新线程),异步执行(开辟新线程)

        队列的种类(任务的执行方式):

                    并行队列(多个任务可同时执行),串行队列(任务按顺序一个一个执行)

三、组合:

        同步执行+并行队列

        同步执行+串行队列

        异步执行+并行队列

        异步执行+串行队列

    还有一种队列叫主队列,是串行。加上这种队列,又有两种新组合方式

        同步执行+主队列

        异步执行+主队列

2.2.2 GCD的使用步骤

1->创建队列:

    并行队列:

dispatch_queue_t queue = dispatch_queue_create("name", DISPATCH_QUEUE_CONCURRENT);

    串行队列:

dispatch_queue_t queue = dispatch_queue_create("name", DISPATCH_QUEUE_SERIAL);

    主队列:

dispatch_queue_t queue = dispatch_get_main_queue();

   全局队列:

dispatch_queue_t globa = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);


2->创建任务:

    同步执行:

dispatch_sync(queue, ^{

});

    异步执行:

dispatch_async(queue, ^{

});

2.2.3 GCD死锁

GCD死锁的问题我也是绕了好久才绕明白。这里来列举几个会造成死锁的案例:

案例1. 主队列+同步执行

// 同步主队列

- (void)syncMainQueue {

NSLog(@"~~~1~~~");//任务1

dispatch_sync(dispatch_get_main_queue(), ^{

NSLog(@"~~~2~~~");//任务2

});

NSLog(@"~~~3~~~");//任务3

}

执行结果:

分析:进入到方法中,首先执行任务1,接下来遇到同步线程的任务2,队列是先进先出的原则,由于三个任务全都是在主队列上,同步的任务2实际上是插在了任务3的后面,但是任务3却要等任务2执行完毕之后才会执行,这样就形成了任务2和任务3互等的状态,无法往下执行,卡死在这里。

解决:自定义串行队列,配合同步线程

// 同步串行

- (void)syncSerial {

NSLog(@"~~~1~~~");

dispatch_sync(dispatch_queue_create("同步串行", DISPATCH_QUEUE_SERIAL), ^{

NSLog(@"~~~2~~~");

});

NSLog(@"~~~3~~~");

}

执行结果:

分析:

主队列也是串行队列,同样是同步线程,但结果却是不一样的。造成执行结果不同的唯一原因就是任务2是执行在一个不同于任务1和任务3的队列中。

来看一下执行顺序,首先执行任务1,接着遇到同步线程的任务2,在任务2执行完毕之后,去执行任务3。由于不是在一个队列中,任务2是排在了自定义的队列的第一个位置, 不用等任务3执行完就可以去执行。、

案例2:在自定义队列中同步执行

- (void)asyncAndSync {

NSLog(@"~~~1~~~");

dispatch_queue_t queue = dispatch_queue_create("串行", DISPATCH_QUEUE_SERIAL);

dispatch_async(queue, ^{

NSLog(@"~~~2~~~");

dispatch_sync(queue, ^{

NSLog(@"~~~3~~~");

});

NSLog(@"~~~4~~~");

});

NSLog(@"~~~5~~~");

}

执行结果:

任务3,任务4未执行

分析:

  首先看看5个任务都是在哪个队列执行的 任务1-主队列、任务2-自定义队列、任务3-自定义队列、任务4-自定义队列、任务5-主队列。

再来看看任务的执行顺序:首先执行任务1;接着遇到异步任务,暂时跳过,执行任务5;接着进入到异步任务内,执行任务2;接下来遇到同步任务3,但是任务3,任务2,任务4都是执行在自定义的队列里,所以他排在了任务4后,但是任务4还在等同步队列执行完才执行, 所以两者进入了互等状态,卡死在这里。

2.2.4 GCD对比

2.3 NSOperation 操作类

单独的NSOperation对象是不能进行多线程编辑的,它需要配合NSOperationQueue(操作队列)来实现多线程

首先我们应该知道,NSOperation是一个抽象类, 它本事并不具备封装操作的能力, 所以我们必须创建NSOperation的子类

2.3.1 创建NSOperation

NSOperation的子类有三种:NSInvocationOperationNSBlockOperation和自定义的继承于NSOperation的类。

2.3.2 NSOperation对象的创建与使用

1-> NSInvocationOperation

- (void)invocationOperation {

NSInvocationOperation *operation_1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(logNum) object:nil];

[operation_1 start];

NSInvocationOperation *operation_2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(logNum) object:nil];

[operation_2 start];

}

2-> NSBlockOperation

       这里创建NSBlockOperation对象的方法:

              +(id)blockOperationWithBlock:(void(^)(void))block;

       添加操作:

             -(void)addExecutionBlock:(void(^)(void))block;

- (void)blockOperation {

NSBlockOperation *operation_1 = [NSBlockOperation blockOperationWithBlock:^{

NSLog(@"~~~1.%@~~~", [NSThread currentThread]);

}];

[operation_1 addExecutionBlock:^{

NSLog(@"~~~2.%@~~~", [NSThread currentThread]);

}];

[operation_1 addExecutionBlock:^{

NSLog(@"~~~3.%@~~~", [NSThread currentThread]);

}];

[operation_1 addExecutionBlock:^{

NSLog(@"~~~4.%@~~~", [NSThread currentThread]);

}];

[operation_1 addExecutionBlock:^{

NSLog(@"~~~5.%@~~~", [NSThread currentThread]);

}];

[operation_1 start];

}

执行结果:

多打印几次会发现,执行操作的线程是不确定的;

3->自定义类

自定义的继承于NSOperation的类需要重写main方法

2.3.3 NSOperationQueue

// 创建队列

NSOperationQueue *queue = [[[NSOperationQueue alloc] init] autorelease];

// 设置最大并发数(同时可处理的线程的个数)

[queue setMaxConcurrentOperationCount:2];

// 创建任务

MyOperation *op1 = [[MyOperation alloc] init];

MyOperation *op2 = [[MyOperation alloc] init];

// 添加到队列

[queue addOperation:op1];

[queue addOperation:op2];

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

推荐阅读更多精彩内容