IOS——多线程

##1、基本概念

  • 什么是进程: 进程是在系统运行的一个程序,每个进程之间是独立的,每个进程均运行在其专有且受保护的内存空间内。

  • 什么是线程: 一个进程想要执行任务,必须得有线程(至少一个线程),线程是进程的基本执行单位,一个进程的所有任务都必须在线程中执行。

  • 线程的串行: 一个线程中任务的执行是串行的,如果要在一个线程中执行多个任务,只能一个一个的按顺序执行

##2、多线程

  • 什么是多线程:

    一个进程中可以开启多个线程,每个线程可以并发/并行执行不同的任务,多线程可以提交程序的执行效率,比如同时执行任务ABC。

  • 多线程原理:

    同一时间,CPU只能执行一个线程,只有一个线程正在执行,多线程并发执行,其实是CPU快速的在多个线程之间切换,如果CPU的切换线程的时间足够快,就会造成多线程并发执行的假象。

  • 多线程的优缺点:

优点: 1.能适当的提高程序的执行效率 2.能适当的提高资源的利用率 缺点: 1.开启线程会占用一定的内存空间(主线程1M,子线程0.5M),如果开启过多的线程就会占用大量的内存空间,降低程序的性能。 2.线程越多,CPU在调度线程上的开销就越大。

##3、主线程

一个IOS程序运行以后,默认会开启一个线程,这个线程就被称为主线程或(UI线程)。主线程的主要作用是显示/刷新UI界面,处理UI事件(点击,滚动,拖拽等)。

IOS中的多线程:

iOS中有四种多线程编程的技术:

1.Pthread (基本不会使用,线程的生命周期由我们⾃己管理)

2.NSThread(每个Thread对象对应⼀一个线程)(使⽤用得⽐比较少,线程的⽣生命周期由我们⾃己管理)

3.NSOperation(面向对象的线程技术)(基于gcd来实现,经常使⽤,⽣命周期由系统管理)

4.GCD(是基于C语⾔言的框架,可以充分利用多核,是苹果推荐使⽤的多线程技术)(经常使用,生命周期由系统管理))

以上这四种编程⽅方式从上到下,抽象度层次是从低到高的,抽象度越高的使用越简单,也是Apple最推荐使用的。但是就目前而言,iOS的开发者,需要了解三种多线程技术的基本使⽤用过程。因为很多 框架技术分别使用了不同多线程技术。

##4、NSThread

创建线程、执行下载任务,需要start手动开启执行

NSThread *thread_A = [[NSThread alloc] initWithTarget:self selector:@selector(run_A) object:nil];
    thread_A.name = @"线程A";

启动任务

 [thread_A start];

阻塞线程,等待几秒,是这个线程里面的所有任务一起阻塞

[NSThread sleepForTimeInterval:5];

阻塞到某个时间

[NSThread sleepUntilDate:[[NSDate date] dateByAddingTimeInterval:1000]];

方式二:指定任务直接执行不需要手动开启 start

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

取消进程

[thread_A cancel];

获取当前线程信息
+ (NSThread *)currentThread;
获取主线程信息
+ (NSThread *)mainThread;

ps:如果是有关UI更新的操作,在其它线程中处理完之后要回到主线程进行改变

##5、GCD

(GCD)是Apple开发的⼀个多核编程的解决⽅方法。该⽅方法在Mac OS X 10.6首次推出,并随后被引入到了iOS4.0中。GCD是⼀个替代诸如NSThread, NSOperationQueue, NSInvocationOperation等技术的很高效和强大的技术。

###5.1 任务和队列

在GCD中,加入了两个非常重要的概念:任务和队列。

任务:

即操作,你想要干什么,说白了就是一段代码,在 GCD 中就是一个 Block,所以添加任务十分方便。

任务有两种执行方式: 同步执行 和 异步执行,

  • 同步(sync) 和 异步(async) 的主要区别在于会不会阻塞当前线程,直到 Block 中的任务执行完毕!

  • 如果是 同步(sync) 操作,它会阻塞当前线程并等待 Block 中的任务执行完毕,然后当前线程才会继续往下运行。

  • 如果是 异步(async)操作,当前线程会直接往下执行,它不会阻塞当前线程。

队列:

  • 用于存放任务。一共有两种队列, 串行队列DISPATCH_QUEUE_SERIAL 和 并行队列DISPATCH_QUEUE_CONCURRENT.

放到串行队列的任务,GCD 会 FIFO(先进先出) 地取出来一个,执行一个,然后取下一个,这样一个一个的执行。

放到并行队列的任务,GCD 也会 FIFO的取出来,但不同的是,它取出来一个就会放到别的线程,然后再取出来一个又放到另一个的线程。这样由于取的动作很快,忽略不计,看起来,所有的任务都是一起执行的。不过需要注意,GCD 会根据系统资源控制并行的数量,所以如果任务很多,它并不会让所有任务同时执行。

并行队列 中的任务根据同步或异步有不同的执行方式。请看下表:

主队列全局队列自定义串行队列自定义并行队列
同步死锁(一直卡主)在主线程中顺序执行在主线程中顺序执行在主线程中顺序执行
异步在主线程中顺序执行在其它线程中同时执行在其它线程中顺序执行在其它线程中同时执行

###5.2 创建队列

  • 主队列

这是一个特殊的串行队列。它用于刷新UI,任何需要刷新UI的工作都要在主队列执行,所以一般耗时的任务都要放到别的线程执行。

同步任务:dispatch_sync,会阻塞后面的任务,必需当前任务完成后才能执行下一个

主队列中不能执行同步操作,不然会死锁

异步执行:dispatch_async,不会阻塞后面的任务,任务会马上返回下一个任务不需要等待,当这个任务完成后会通知主线程。 dispatch_get_main_queue() 获取主队列,主队列中的任务都会在主线程中执行, 是一个串行队列

dispatch_async(dispatch_get_main_queue(), ^{
       
        [self run_D];
    });
  • 全局队列

dispatch_get_global_queue() 全局队列,是一个并行队列,可以将队列中的任务放到不同的线程中执行

任务执行的优先级
        DISPATCH_QUEUE_PRIORITY_HIGH 2  最高
        DISPATCH_QUEUE_PRIORITY_DEFAULT 0 中等(默认)
        DISPATCH_QUEUE_PRIORITY_LOW (-2) 低
        DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN 后台执行
        
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       
        [self run_A];
    });

如果在并行队列中同步执行任务,那么这些任务都会在主线程中按顺序执行,也就没有并发性了。

  • 自定义串行队列

自定义的串行队列中异步执行任务,队列会把任务放到一个新的线程中按顺序执行

dispatch_queue_t serialQueue = dispatch_queue_create("串行队列", DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(serialQueue, ^{
        
        [self run_A];
    });
  • 自定义的并行队列

自定义的并行队列中异步执行任务,队列会把任务放到不同的线程中执行

ispatch_queue_t concurrentlQueue = dispatch_queue_create("并行队列", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(concurrentlQueue, ^{
        
        [self run_A];
    });

将更新UI的操作放到主队列中

dispatch_async(dispatch_get_main_queue(), ^{
        
        self.imagView_1.image = image;
    
    });

###5.3 队列组

队列组可以将很多队列添加到一个组里,这样做的好处是,当这个组里所有的任务都执行完了,队列组会通过一个方法通知我们。

创建一个队列组

    dispatch_group_t group = dispatch_group_create();

创建队列

    dispatch_queue_t queue = dispatch_queue_create("并行队列", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t global = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

在group中异步执行任务,block中调用局部变量需要加__block

	__block UIImage *image_1;
   
    dispatch_group_async(group, queue, ^{
       
        image_1 = [self downloadImage_one];
    });

当group中的任务都执行完后,就会发送一个通知notify调用这个方法

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
       
        self.imagView_1.image = image_1;        
    });

###5.4 线程安全

一次执行,block中的代码只能被执行一次,是线程保护的(同时只能一个线程访问)

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        
        [self run_B];
    });

单例一般用这个 线程安全的单例创建

+ (instancetype)shareUser_GCD {

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        user = [[self alloc] init];
    });
    
    return user;
}

+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    
    static User *user = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        user = [super allocWithZone:zone];
    });
    
    return user;
}

延迟执行

 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        
        [self run_B];
    });

##6、NSOperation和NSOperationQueue

NSOperation 是苹果公司对 GCD 的封装,完全面向对象,所以使用起来更好理解。 NSOperation 和 NSOperationQueue 分别对应 GCD 的 任务 和 队列 。操作步骤也很好理解:

1.将要执行的任务封装到一个 NSOperation 对象中。 2.将此任务添加到一个 NSOperationQueue 对象中。

直接执行会在主线程中顺序执行

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

启动任务

  [opertaion_1 start];

至少会有一个任务在主线程中执行,其他任务会被放到其他线程中执行

NSBlockOperation *opertion = [NSBlockOperation blockOperationWithBlock:^{
       
        [self run_A];
    }];
    
    [opertion addExecutionBlock:^{
        
        [self run_B];
    }];
    
    [opertion addExecutionBlock:^{
       
        [self run_C];
    }];
    
    [opertion start];

###6.1 NSInvocationOperation依赖关系

NSOperation 有一个非常实用的功能,那就是添加依赖。比如有 3 个任务:A: 从服务器上下载一张图片,B:给这张图片加个水印,C:把图片返回给服务器。这时就可以用到依赖

注意:

  • 不能添加相互依赖,会死锁,比如 A依赖B,B依赖A。
  • 可以使用 removeDependency 来解除依赖关系。
  • 可以在不同的队列之间依赖,反正就是这个依赖是添加到任务身上的,和队列没关系。
NSInvocationOperation *opertaion_1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run_A) object:nil];
    NSInvocationOperation *opertaion_2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run_B) object:nil];
    NSInvocationOperation *opertaion_3 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run_C) object:nil];
    
    // 添加依赖,opertaion_1 ————》opertaion_2 ————》opertaion_3
    
//    [opertaion_2 addDependency:opertaion_1];
//    [opertaion_3 addDependency:opertaion_2];

在这里就是opertaion_2会等opertaion_1执行完后再执行,后面以此类推

创建一个队列,默认就是并行队列

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

设置当前最大的执行数,可以控制是并行还串行,为1时就是串行

queue.maxConcurrentOperationCount = 1;

将任务添加到队列中任务就自动执行了

    [queue addOperation:opertaion_1];
    [queue addOperation:opertaion_2];
    [queue addOperation:opertaion_3];
        [queue addOperationWithBlock:^{
    
            [self run_D];
        }];

添加多个任务,并且可以设置等待完成

[queue addOperations:@[opertaion_1,opertaion_2,opertaion_3] waitUntilFinished:YES];

取消所有的任务

[queue cancelAllOperations];

取消指定的任务

[opertaion_1 cancel];

获取当前的队列

[NSOperationQueue currentQueue];

获取主队列

[NSOperationQueue mainQueue];

###6.2 其它方法

以上就是一些主要方法, 下面还有一些常用方法:

NSOperation

  • BOOL executing; //判断任务是否正在执行
  • BOOL finished; //判断任务是否完成
  • void (^completionBlock)(void);//用来设置完成后需要执行的操作
  • (void)cancel; //取消任务
  • (void)waitUntilFinished;//阻塞当前线程直到此任务执行完 毕

NSOperationQueue

  • NSUInteger operationCount; //获取队列的任务数
  • (void)cancelAllOperations;//取消队列中所有的任务
  • (void)waitUntilAllOperationsAreFinished; //阻塞当前线程直到此队列中的所有任务执行完毕
  • [queue setSuspended:YES]; // 暂停queue
  • [queue setSuspended:NO]; // 继续queue

##7 GCD和NSOperation的区别:

1、GCD的底层是用C来实现的,NSOperation底层从ios4开始也是⽤的GCD来实 现的;(底层实现)

2、在NSOperationQueue中,我们可以随时取消已经设定要准备执⾏的任务(当然,已经开始的任务就⽆法阻⽌了),⽽GCD没法停止已经加入queue的block(其实是有的,但需要许多复杂的代码);(取消任务)

3、NSOperation能够⽅便地设置依赖关系,我们可以让⼀个Operation依赖于另⼀个Operation,这样的话尽管两个Operation处于同⼀个并行队列中,但前者会直到后者执行完毕后再执⾏;(依赖关系)

4、我们能将KVO应⽤用在NSOperation中,可以监听一个Operation是否完成或取消,这样子能⽐GCD更加有效地掌控我们执⾏的后台任务;(监听任务的执⾏情况)

5、在NSOperation中,我们能够设置NSOperation的priority优先级,能够使同⼀个并行队列中的任务区分先后地执行,⽽在GCD中,我们只能区分不同任务队列的优先级,如果要区分block任务的优先级,也需要大量的复杂代码;(优先级) 6、我们能够对NSOperation进行继承,在这之上添加成员变量与成员方法,提⾼整个代码的复⽤度,这⽐简单地将block任务排⼊执行队列更有自由度,能够在其之上添加更多自定制的功能。(代码复用)

Demo下载:

https://github.com/fuxinto/HfxDemo

转载于:https://my.oschina.net/6603044/blog/741977

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值