码迷,mamicode.com
首页 > 移动开发 > 详细

iOS开发——多线程

时间:2016-03-09 20:45:55      阅读:179      评论:0      收藏:0      [点我收藏+]

标签:

很多朋友都说iOS开发中,最难理解和学习的就是多线程,很多的原理实现都是通过log看到,也比较抽象,本人也是在多线程方面投入过很多脑细胞。。无论这方面的知识掌握和应用起来是否轻松,牢固的基本功、正确的认识理解、再加上充分的实战经验,都能助你将其搞定。这里先介绍一些多线程的知识以及应用,作为讨论,大家共同学习。

一、多线程基本概念

1、线程与进程 

(1)进程:操作系统的每一个应用程序就是一个进程

(2)线程:进程的基本执行单元,一个进程的所有任务都在线程中执行

2、主线程

(1)定义:一个程序运行后,默认会开启1个线程,称为“主线程”或“UI线程”。其他为“子线程”。

(2)作用及注意:线程一般用来 刷新UI界面 ,处理UI事件(比如:点击、滚动、拖拽等事件),避免将耗时的操作放到主线程,以免造成主线程卡顿。

3、多线程原理

(1)是CPU快速的在多个线程之间的切换(自身的随机调度算法)。

(2)同步/异步:

  • 同步:指的就是在当前线程(不一定是主线程)中,从上而下依次执行任务(代码块的阅读顺序),这个就叫做同步。
  • 异步:指不在当前线程中执行了,开辟新的线程执行, 注意:即使在别的线程中执行,也是从上而下依次执行的。

4、iOS多线程实现方案

 技术分享

 

5、线程的占用空间:

(1)子线程:512KB。

(2)主线程:512KB。这里官方文档给出的是1M,实际测试为512,可以打印线程的stackSize属性验证。

6、线程的状态和生命周期:

(1)控制线程的状态(以NSThread管理线程为例)

  a、启动线程:- (void)start;

        线程进入就绪状态,当线程执行完毕后自动进入死亡状态。

  b、暂停(阻塞)线程

    + (void)sleepUntilDate:(NSDate *)date;

    + (void)sleepForTimeInterval:(NSTimeInterval)ti;

    线程进入阻塞状态

  c、停止线程

    + (void)exit;

    线程进入死亡状态

(2)状态图

 技术分享

 

7、线程的属性(以NSThread管理线程为例,一下是NSTread类中的方法或属性)

 (1)stackSize:占内存大小

 (2)name:名字

 (3)threadPriority:优先级(不推荐使用)

  (4)  qualityOfService:服务质量 

 

二、多线程深入理解

1、线程间资源共享/抢夺

 (1)定义:一块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源,当多个线程访问同一块资源时,各个线程提取和修改数据不同步,很容易引发数据错乱和数据安全问题。

 (2)互斥锁(线程同步) :解决上面的问题

  • 代码:@synchronized(锁对象) {  需要锁定的代码  }
  • 每一个对象(NSObject)内部都有一个锁(变量),当有线程要进入synchronized到代码块中会先检查对象的锁是打开还是关闭状态,默认锁是打开状态(1),如果是线程执行到代码块内部 会先上锁(0)。如果锁被关闭,再有线程要执行代码块就先等待,直到锁打开才可以进入。
  • 互斥锁的实现流程

      线程执行到synchronized

    i.   检查锁状态 如果是开锁状态转到ii  ,如果上锁转到v 

    ii.   上锁(0)    

    iii.      执行代码块

    iv.    执行完毕 开锁(1)      

    v.       线程等待(就绪状态)

  • 注意:必须使用全局对象来提供“锁”,否则同样锁不住。

2、原子属性

(1)属性中的修饰符

  • nonatomic :非原子属性
  • atomic  : 原子属性,针对多线程设计的,是默认值。保证同一时间只有一个线程能够写入,但是同一个时间多个线程都可以取值。
  • 自旋锁:atomic  本身就有一把锁(自旋锁),保证“单写多读”:单个线程写入,多个线程可以读取。如果发现有其它线程正在锁定代码,线程会用死循环的方式,一直等待锁定的代码执行完成 。自旋锁更适合执行不耗时的代码。
  • iOS开发的建议:
    • 所有属性都声明为nonatomic,移动设备内存小,atomic虽然相对线程安全,但是消耗资源较多。
    • 尽量避免多线程抢夺同一块资源(多个线程访问和修改同一数据)。
    • 尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减小移动客户端的压力。

3、线程安全

1)多个线程同时操作一个全局变量是不安全的,使用自旋锁并不是绝对的安全(因为单写多读)。

(2)线程安全:在多个线程进行读写操作时,仍然能够保证数据的正确 。使用互斥锁可以实现,但是消耗性能。

(3)关于主线程(UI线程):几乎所有UIKit??提供的类都是线程不安全的,所有更新UI的操作都在主线程上执行。

4、NSRunLoop

(1)功能作用:运行循环,又叫消息循环或事件循环。 

  • 检测、接收  “输入事件”  并执行。
  • 保证程序不退出(主线程)。
  • 如果没有事件发生,会让程序进入休眠状态。

(2)特点:

  • NSRunLoop不能单独存在,必须存在于线程中,不论主线程中还是子线程中都有一个消息循环。
  • 主线程的RunLoop是默认开启的,子线程中的RunLoop默认不开启(使用run方法开启)。
  • 只要线程一启动,内部就会有一个默认的主RunLoop,而每一个App,只要一启动,就会自动有一个主线程。 

(3)(两大核心之一)输入事件

  • 输入源:比如 键盘输入,滚动scrollView,performSelector方法
  • 定时源:NSTimer 定时器

(4)(两大核心之二)运行模式(消息循环模式):

  • 线程的消息循环运行在某一种消息循环模式上。
  • 输入事件必须设置消息循环的运行模式,并且如果想让输入事件可以在消息循环上执行,输入事件的消息循环运行模式必须和当前消息循环的运行模式一致
  • 两种常用的运行模式:NSDefaultRunLoopMode、NSRunLoopCommonModes。
  • 实例:输入事件是定时源
 //定时源 (计时器)
    NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(demo) userInfo:nil repeats:YES];
        /*
        参数1:输入源
        参数2:输入源的模式,要和当前消息循环的模式对应,才可以让消息循环执行输入源
        NSDefaultRunLoopMode默认模式 NSRunLoopCommonModes包含了很多种模式
    */
    [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];

5、自动释放池

(1)主线程自动释放池的创建和销毁:

  • 每一次主线程的消息循环开始的时候会先创建自动释放池。
  • 消息循环结束前,会释放自动释放池。
  • 自动释放池被销毁或耗尽时会向池中所有对象发送 release 消息,释放所有 autorelease 的对象(引用计数-1)。
  • 自动释放池随着消息循环的开始和结束不断的重建和销毁。

(2)子线程的自动释放池:

  • 在子线程开启时手动创建释放池。因为主线程可以自动生成释放池,而子线程不可以。为了保证消息循环结束(线程结束)时,所有的对象可以正常入池和释放,必须手动添加。
  • 其他情况和主线程相同。

(3)什么时候使用自动释放池:(官方文档建议)

  • 开启子线程时。
  • 在一个循环中,生成了大量的临时变量,需要手动在循环内部加入释放池(否则内存会爆。。)。
  •  例如:
for (int i = 0; i < largeNumber; ++i) {
     @autoreleasepool {
        NSString *str = @"Hello World";
            str = [str stringByAppendingFormat:@" - %d", i];
            str = [str uppercaseString];

        }
    }

 

(4)示意图

技术分享

三、线程管理————pthread

1、一套通用的多线程API,纯C语言,操作难度大,在iOS开发中基本不使用。

2、基本使用方式

#import <pthread.h>
    //线程编号的地址,本质是结构体类型
    pthread_t pthread;
    //方法的返回值:0 成功, 其它失败

     int result =  pthread_create(&pthread, NULL, demo, NULL);

    /*
    pthread_create函数的参数介绍    

    第一个参数:  线程编号的地址;
    第二个参数:  线程的属性;
    第三个参数: 线程要执行的方法,其中void *(*)(void *)具体代表含义:
        //函数的返回值类型: void *,类似于oc中的id;
        //函数的名称:函数指针;
        //函数的参数:void *;
        第四个参数:线程要执行的方法的参数。
    
    */

 

四、线程管理————NSThread

1、创建新线程的三种方式,例如:

//方式一:
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo:) object:nil]; [thread start];
//方式二: [NSThread detachNewThreadSelector:@selector(demo:) toTarget:self withObject:nil];
//方式三:
准确的说此方法是NSObject的

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

2、NSThread在调试中的使用

  • 获得线程的属性:name,stackSize,threadPriority(默认0.5)
  • 管理线程的类方法:start、exit、sleep
  • 获得当前线程和主线程:   [NSThread currentThread]  、[NSThread mainThread];

五、线程管理————GCD

 在之前文章中,已经针对GCD的基本使用做了详细介绍,不在这里复述了。可参阅:http://www.cnblogs.com/cleven/p/5249246.html

本文只对GCD的其他操作进行一些补充。

1、延迟操作

实例:

//延时操作
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  });
        /*
        dispatch_after的参数
    参数1  dispatch_time_t when
    多少秒之后执行
    参数2  dispatch_queue_t queue
    任务添加到那个队列
    参数3  dispatch_block_t block
    要执行的任务
    */

 

2、一次性执行

(1)定义:程序运行中只执行一次。一次性执行是线程安全的,可以使用一次性执行创建单例对象,效率比互斥锁高。

(2)实现:可以用来创建单例对象。

    //原理:当onceToken为0时执行方法,然后将全局变量oneceToken更改为-1,以后就无法再执行。
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    //要执行一次的代码;
    });

3、调度组

(1)定义:有时候需要在多个异步任务都执行完成之后继续做某些事情,比如下载歌曲,等所有的歌曲都下载完毕之后转到主线程提示用户,这样需要一个顺序的统一调度。

(2)实现:

//1 全局队列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    //2 调度组
    dispatch_group_t group = dispatch_group_create();
    //3 添加任务
    //把任务添加到队列,等任务执行完成之后通知调度组,任务是异步执行
    dispatch_group_async(group, queue, ^{
        NSLog(@"歌曲1下载完毕  %@",[NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"歌曲2下载完毕  %@",[NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"歌曲3下载完毕  %@",[NSThread currentThread]);
    });
    //4 所有任务都执行完成后,获得通知 (异步执行)
    //等调度组中队列的任务完成后,把block添加到指定的队列
    dispatch_group_notify(group, queue, ^{
        NSLog(@"所有歌曲都已经下载完毕!   %@",[NSThread currentThread]);
    });
 
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        //在主线程,更新UI控件,提示用户
        NSLog(@"播放器更新完毕!  %@",[NSThread currentThread]);
    });
    NSLog(@"over");

(3)原理:

//1 全局队列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
 
    //2 调度组
    dispatch_group_t group = dispatch_group_create();
 
    //ARC中不用写
//    dispatch_retain(group);
    //3 进入调度组,执行此函数后,再添加的异步执行的block都会被group监听
    dispatch_group_enter(group);
    
    //4 添加任务一
    dispatch_async(queue, ^{
        NSLog(@“下载第一首歌曲!”);
        dispatch_group_leave(group);
        //ARC中此行不用写,也不能写
//        dispatch_release(group);
    });
    //5 添加任务二
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@“下载第二首歌曲”);
        dispatch_group_leave(group);
        //ARC中此行不用写,也不能写
        //dispatch_release(group);
    });
    
    //6  获得调度组的通知
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@“歌曲都已经下载完毕! %@",[NSThread currentThread]);
    });
//7 等待调度组 监听的队列中的所有任务全部执行完毕,才会执行后续代码,会阻塞线程(很少使用)
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

 

六、线程管理————NSOperation

1、NSOperation的作用以及特点

(1)NSOperation是OC语言中基于GCD的面向对象的封装,NSOperation是iOS2.0推出的,iOS4之后(GCD出现)重写了NSOperation。

(2)使用起来比GCD更加简单(面向对象)。同时,苹果推荐使用,使用NSOperation不用关心线程以及线程的生命周期。

(3)提供了一些用GCD不好实现的功能,比如暂停,取消,最大并发数、依赖关系。当然GCD也有自己的特有,比如延迟、一次性执行、调度组。

(4)NSOperation是抽象类,约束子类都具有共同的属性和方法,不能直接使用,而是使用其子类。

(5)任务是并发执行的,除非遇到主队列(start方法除外)。

2、NSOperationQueue 队列 

(1)两种队列

  • 并发队列:程序员自己创建
  • 主队列:系统创建

(2)NSOperationQueue的作用:

  • NSOperation可以调用start方法来执行任务,但默认是主线程执行的。如果将NSOperation添加到NSOperationQueue(操作队列)中,系统会自动异步执行NSOperation中的操作。 

(3)无论是使用start还是加入队列的方式来执行操作,系统都会调用NSOperation中的main方法,所以如果自定义NSOperation,就要重写此方法。

(4)添加操作到队列(主队列也一样)

    - (void)addOperation:(NSOperation *)op;
    - (void)addOperationWithBlock:(void (^)(void))block;    

 (5) 其他一些常用方法和属性

  • (BOOL)suspended                                        暂停
  • (NSUInteger)operationCount                         队列中的操作数
  • (NSUInteger)maxConcurrentOperationCount  最大并发数
  • +(NSOperation*)mainQueue                         获得主队列
  • +(NSOperation*)currentQueue                      获得当前队列
  • -(void)cancelAllOperations                             取消所有操作

 

3、NSOperation子类——NSInvocationOperation

(1)实例

//建NSInvocationOperation对象
    - (id)initWithTarget:(id)target selector:(SEL)sel object:(id)arg;
//调用start方法开始执行操作,一旦执行操作,就会调用target的sel方法
    - (void)start;

(2)注意:默认情况下,调用了start方法后并不会开一条新线程去执行操作,而是在当前线程同步执行操作,只有将NSOperation放到一个NSOperationQueue中,才会异步执行操作。

 

4、NSOperation子类——NSBlockOperation

(1)实例:

//创建NSBlockOperation对象
    + (id)blockOperationWithBlock:(void (^)(void))block;
//通过addExecutionBlock:方法添加更多的操作
    - (void)addExecutionBlock:(void (^)(void))block;

(2)注意:只要NSBlockOperation封装的操作数 > 1,就会异步执行操作。

    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@",[NSThread currentThread]);
    }];
    //操作添加额外的任务
    [op addExecutionBlock:^{
        NSLog(@"Execution %@",[NSThread currentThread]);
    }];
    [op start];
    
    //如果NSBlockOperation的操作数>1 开启新的线程
    //这时有两个任务并发执行,一个在主线程,一个在新开启的线程

 

5、并发数

(1)定义:同时执行的任务数,比如,同时执行3个任务放到3个线程,并发数就是3。

(2)最大并发数及相关方法:最大并发数是系统同一时间并发执行任务的最大数。系统可以开辟多个线程、队列可以拥有多个任务,但是同时执行的任务数只能是设定好的最大并发数,直到队列中任务执行完毕。

  • - (NSInteger)maxConcurrentOperationCount;
  • - (void)setMaxConcurrentOperationCount:(NSInteger)cnt

(3)执行的过程

  a、把操作添加到队列

  b、去线程池去取空闲的线程,如果没有就创建线程

  c、把操作交给从线程池中取出的线程执行

  d、执行完成后,把线程再放回线程池中

  e、重复b,c,d直到所有的操作都执行完

6、优先级

(1)方法一设置NSOperation在queue中的优先级,可以改变操作的执行优先级,注意这是NSOperation中的属性,已经不推荐使用了。

  • - (NSOperationQueuePriority)queuePriority;
  • - (void)setQueuePriority:(NSOperationQueuePriority)p;

(2)方法二:iOS8以后推荐使用服务质量 qualityOfService属性,这是个枚举值。

(3)注意:优先级只是告诉系统:在CUP随机调度的情况下,请尽量优先调用优先级高的任务去执行,并不能绝对保证CPU全部执行优先级高的任务。

7、依赖关系

(1)定义:类似于GCD的串行队列,NSOperation之间可以设置依赖来保证执行顺序。

(2)实例 

 NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"op1 验证账号");
    }];
    
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"op2 扣费");
    }];
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"op3 下载应用");
    }];
    //操作依赖 不能设置相互依赖
    [op2 addDependency:op1];
    [op3 addDependency:op2];
    //把操作添加到队列中 waitUntilFinished是否等待这句代码执行完毕再来执行下面的代码
    [self.queue addOperations:@[op2,op3] waitUntilFinished:NO];
    
    //不同的队列之间也可以设置依赖关系
    [[NSOperationQueue mainQueue]addOperation:op1];

8、监听操作的完成

 (1)类似于GCD的操作组,NSOperation也可以监听操作的完成,这是NSOperation中的方法:

  • - (void (^)(void))completionBlock;
  • - (void)setCompletionBlock:(void (^)(void))block;
   //创建操作op,任务就是循环打印输出
        NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
            for (int i = 0; i < 20; i++) {
                 NSLog(@"我是op===》 %d",i);
             }
        }];
    //设置操作优先级 最高的服务质量
    op.qualityOfService = NSQualityOfServiceUserInteractive;
    //把操作添加到队列中
        [self.queue addOperation:op];    
    //监听op的完成
    [op setCompletionBlock:^{
        NSLog(@"op 执行完毕了! %@",[NSThread currentThread]);
    }];
    
    //创建操作op2
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i <20; i++) {
            NSLog(@"我是op2+++》 %d",i);
        }
    }];
    //设置操作优先级 最低的服务质量
    op2.qualityOfService = NSQualityOfServiceBackground;
    //把操作添加到队列中
    [self.queue addOperation:op2];
   
    //本例中加入了优先级的使用:op和op2两个任务,op要优先于op2被CPU调度,理论上也是会优先于op2执行完毕,当op执行完毕时,op任务的结束被监听到,输出“执行完毕”,此时op2还在执行中。
    

 

七、线程间通信的几种方式

 线程间的通讯,关键在于获得不同的线程或队列,然后在不同线程中执行任务,比如从子线程到主线程,再从主线程到子线程。

 1、获得当前线程(队列)的方式

 (1) NSThread管理线程:           [NSThread currentThread];

 (2) NSOperation管理线程:       [NSOperationQueue currentQueue];

 2、获得主线程(主队列)的方式

 (1) NSThread管理线程:           [NSThread mainThread];  主线程

 (2) NSOperation管理线程:       [NSOperationQueue mainQueue]; 主队列

(3)GCD管理线程:                  dispatch_get_main_queue(); 主队列

 3、在某线程中执行

(1)此方法是NSObject的扩展(NSThread.h中):  

  • - (void)performSelectorOnMainThread

(2)开启新线程执行:

  • - (void)performSelectorInBackground

(3)在某个线程中执行:

  • -(void)performSelector:onThread:

 

到此处,本文只是简单的将iOS中多线程的基本概念和使用做了简单介绍和整理,如有问题,还望多包含并指教,随后将更多讨论些在实际开发中的应用。

 

iOS开发——多线程

标签:

原文地址:http://www.cnblogs.com/cleven/p/5259416.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!