美文网首页
多线程全知道

多线程全知道

作者: 冲上云霄90 | 来源:发表于2016-07-04 09:32 被阅读61次

iOS中三种多线程编程技术分别是:

  • NSThread
  • Cocoa NSOperation
  • GCD (全称: Grand Central Dispatch)

这三种编程方式从上到下, 抽象度层次从低到高, 抽象度越高的使用越简单, 也是Apple最推荐使用的.

各自优缺点:

  • NSThread:

优点: NSThread 比其他两个轻量级
缺点: 需要自己管理线程的生命周期, 线程同步. 而线程同步对数据的枷锁会有一定的系统开销.

  • NSOperation

优点: 不需要关心线程管理, 数据同步的事情, 可以把经理放在自己需要执行的操作上. Cocoa operation 相关的类是 NSOperation, NSOperationQueue.
NSOperation 是个抽象类, 使用它必须用它的子类, 可以实现它挥着使用它自定义的两个子类: NSInvocationOperation和 NSBlockOperation.
创建NSOperation子类的对象, 把对象添加到NSOPerationQueue对队列里执行.

  • GCD

Grand Central Dispatch(GCD)是Apple开发的一个多核编程的解决方法, 在iOS4.0 开始之后才能使用. GCD是一个替代诸如NSThread, NSOperationQueue, NSInvocationOperation等技术的很高效和强大的技术, 现在的iOS系统升级 不用担心技术不能使用.

NSThread的使用

NSThread 有两种直接创建方式

一. 实例方法 (先创建线程并要有个开始start指令,这里可设置线程的优先级等线程信息)

- (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument 

二. 类方法 (直接创建线程并开始运行线程)

+ (void)detachNewThreadSelector:(SEL)aSelector toTarget:(id)aTarget withObject:(id)anArgument

参数的意义:

  • selector :线程执行的方法,这个selector只能有一个参数,而且不能有返回值。
  • target :selector消息发送的对象
  • argument:传输给target的唯一参数,也可以是nil

不显式创建线程的方法:

用NSObject的类方法 performSelectorInBackground:withObject: 创建一个线程:

[Obj performSelectorInBackground:@selector(doSomething) withObject:nil];

线程间通讯

线程下载完图片后怎么通知主线程更新界面呢?

[self performSelectorOnMainThread:@selector(updateUI:) withObject:image waitUntilDone:YES];

performSelectorOnMainThread是NSObject的方法,除了可以更新主线程的数据外,还可以更新其他线程的比如:

performSelector:onThread:withObject:waitUntilDone:

线程同步

如果没有线程同步的lock,卖票数可能是-1.加上lock之后线程同步保证了数据的正确性。
线程的顺序执行

他们都可以通过[ticketsCondition signal]; 发送信号的方式,在一个线程唤醒另外一个线程的等待。

使用****NSCondition****,实现多线程的同步,即可实现生产者消费者问题。

基本思路是,首先要创建公用的****NSCondition****实例。然后:
消费者取得锁,取产品,如果没有,则wait,这时会释放锁,直到有线程唤醒它去消费产品;
生产者制造产品,首先也是要取得锁,然后生产,再发signal,这样可唤醒wait的消费者。

这里需要注意****wait****和****signal****的问题:
1: 其实,wait函数内部悄悄的调用了unlock函数(猜测,有兴趣可自行分析),也就是说在调用wati函数后,这个NSCondition对象就处于了无锁的状态,这样其他线程就可以对此对象加锁并触发该NSCondition对象。当NSCondition被其他线程触发时,在wait函数内部得到此事件被触发的通知,然后对此事件重新调用lock函数(猜测),而在外部看起来好像接收事件的线程(调用wait的线程)从来没有放开NSCondition对象的所有权,wati线程直接由阻塞状态进入了触发状态一样。这里容易造成误解。
2: wait函数并不是完全可信的。也就是说wait返回后,并不代表对应的事件一定被触发了,因此,为了保证线程之间的同步关系,使用NSCondtion时往往需要加入一个额外的变量来对非正常的wait返回进行规避。
3: 关于多个wait时的调用顺序,测试发现与wait执行顺序有关。具体请查阅文档。

其它同步:

可使用指令 @synchonized 来简化 NSLock 的使用, 这样不必显示创建NSLock, 加锁并解锁相关代码.
除外还有比如:循环锁 NSRecursiveLock, 条件锁 NSConditionLock, 分布式锁 NSDistributedLock等等

Cocoa NSOperation 的使用

使用 NSOperation 的方式有两种,
一种是用定义好的两个子类:

  • NSInvocationOperation 和 NSBlockOperation.
  • 另一种是继承NSOperation

NSOperation 和 java.lang.Runnable接口很相似. 和其一样, NSOperation 也是设计用来扩展的, 只需继承重写NSOperation的一个main, 相当于 java 中Runnable的 Run方法. 然后把NSOperation子类的对象放入 NSOperationQueue队列中, 该队列就会启动并开始处理它.

默认情况下,NSOperation并不具备封装操作的能力,必须使用它的子类,使用NSOperation子类的方式有3种:

1> 自定义子类继承NSOperation,实现内部相应的方法
2> NSBlockOperation
3>NSInvocationOperation

先介绍如何用NSOperation封装一个操作,后面再结合NSOperationQueue来使用。
1.****首先介绍自定义****NSOperation****:
NSOperation是没法直接使用的,它只是提供了一个工作的基本逻辑,具体实现还是需要你通过定义自己的NSOperation子类来获得。

#import <Foundation/Foundation.h>
@protocol NSDefineOprationDelegate <NSObject>
- (void) handleDelegate;
@end
@interface NSDefineOpration : NSOperation
@property (nonatomic, assign) id <NSDefineOprationDelegate> delegate;
- (id)initWithDelegate:(id<NSDefineOprationDelegate>) delegate;
@end

实现文件里:

#import "NSDefineOpration.h"
@implementation NSDefineOpration
- (id)initWithDelegate:(id<NSDefineOprationDelegate>) delegate{
    if(self = [super init]){
        self.delegate = delegate;
    }
    return self;
}
- (void)main{
    @autoreleasepool {
        //do something
        sleep(15);
        NSLog(@"op1........handle......  on thread num :%@",[NSThread currentThread]);
        
        if([self.delegate respondsToSelector:@selector(handleDelegate)])
        {
            [self.delegate performSelector:@selector(handleDelegate) withObject:nil];
        }
    }
    
}
@end

这里的sleep(15)主要用来做一些延时的操作,比如网络下载等。
调用:

- (void)oprationTest
{
    NSDefineOpration *op1 = [[NSDefineOpration alloc] initWithDelegate:self];
    op1.completionBlock = ^(){
        NSLog(@"op1........OK !!");
    };
    [op1 start];
}

从执行结果可以看出,因为在实现的main函数里没有使用异步线程处理,导致直接阻塞了主线程1,所以使用这种方式一定注意main函数里操作时间过长导致主线程阻塞问题。耗时比较长的都放到其他线程里处理。

2.****接下来介绍****NSBlockOperation
第一种使用NSBlockOperation的方式 block类方法

    NSLog(@"block start");
    NSBlockOperation *bop2 = [NSBlockOperation blockOperationWithBlock:^{
        sleep(15);
        NSLog(@"bop2.....handle..... on thread num%@",[NSThread currentThread]);
    }];
    [bop2 setCompletionBlock:^{
        NSLog(@"bop2........OK !!");
    }];
    [bop2 start];

首先初始化了一个NSBlockOperation对象,它是用一个Block来封装需要执行的操作 调用了start方法,紧接着会马上执行Block中的内容
这里还是在当前线程同步执行操作,并没有异步执行,阻塞主线程。

第二种使用NSBlockOperation的方式: alloc 实例方法

- (void)blockOprationTest
{
    NSLog(@"block start");
    NSBlockOperation * op2 = [[NSBlockOperation alloc] init];
    [op2 addExecutionBlock:^{
        sleep(10);
        NSLog(@"op2.....handle..... on 10 hread num%@",[NSThread currentThread]);
    }];
    
    [op2 addExecutionBlock:^{
        sleep(6);
        NSLog(@"op2.....handle..... on 6 thread num%@",[NSThread currentThread]);
    }];
    
    [op2 addExecutionBlock:^{
        sleep(4);
        NSLog(@"op2.....handle..... on 4 thread num%@",[NSThread currentThread]);
    }];
    
    [op2 addExecutionBlock:^{
        sleep(8);
        NSLog(@"op2.....handle..... on 8 thread num%@",[NSThread currentThread]);
    }];
    
    [op2 addExecutionBlock:^{
        sleep(1);
        NSLog(@"op2.....handle..... on 1 thread num%@",[NSThread currentThread]);
    }];
    [op2 setCompletionBlock:^{
        NSLog(@"op2........OK !!");
    }];
    [op2 start];
    
    //bop2
    NSBlockOperation *bop2 = [NSBlockOperation blockOperationWithBlock:^{
        sleep(15);
        NSLog(@"bop2.....handle..... on thread num%@",[NSThread currentThread]);
    }];
    [bop2 setCompletionBlock:^{
        NSLog(@"bop2........OK !!");
    }];
    [bop2 start];
}

分析下结果:
首先看到了有1和2两个线程,线程2在56秒的时候开始执行6秒的操作,接下来执行4,1秒结束时间为07秒。线程1在56的时候开始执行10秒的操作,接下来执行8秒,结束时间为14秒。最后执行Bop2的15秒操作至29秒。时间看起来没有问题。为什么会启用2个线程而不是3个或者更多?

3.****接下来介绍****NSInvocationOperation

- (void)invocationOperation
{
    NSInvocationOperation * op3 = [[NSInvocationOperation alloc] initWithTarget:(id)self selector:@selector(handleInvoOpDelegate) object:nil];
    [op3 setCompletionBlock:^{
        NSLog(@"op3........OK !!");
    }];
    [op3 start];
}
selector函数:
- (void)handleInvoOpD
{
    sleep(5);
    NSLog(@"op3.....handle.....  on thread num :%@",[NSThread currentThread]);
}

NSInvocationOperation比较简单,就是继承了NSOperation,区别就是它是基于一个对象和selector来创建操作,可以直接使用而不需继承来实现自己的操作处理。

4.****最后介绍下****NSOperationQueue
把NSOperation子类的对象放入NSOperationQueue队列中,该队列就会启动并开始处理它。队列里可以加入很多个NSOperation, 可以把NSOperationQueue看作一个线程池,可往线程池中添加操作(NSOperation)到队列中。线程池中的线程可看作消费者,从队列中取走操作,并执行它。
eg:

- (void)handleOpqueue
{
    NSOperationQueue *qu = [[NSOperationQueue alloc] init];
    
    NSBlockOperation * bkOp1 = [NSBlockOperation blockOperationWithBlock:^{
        sleep(10);
        NSLog(@"bkOp1.....handle.....on thread num%@",[NSThread currentThread]);
    }];
    [bkOp1 setCompletionBlock:^{
        NSLog(@"bkOp1........OK !!");
    }];
    
    
    NSBlockOperation * bkOp2 = [NSBlockOperation blockOperationWithBlock:^{
        sleep(2);
        NSLog(@"bkOp2.....handle.....on thread num%@",[NSThread currentThread]);
    }];
    [bkOp2 setCompletionBlock:^{
        NSLog(@"bkOp2........OK !!");
    }];
    
    NSBlockOperation * bkOp3 = [NSBlockOperation blockOperationWithBlock:^{
        sleep(1);
        NSLog(@"bkOp3.....handle.....on thread num%@",[NSThread currentThread]);
    }];
    [bkOp3 setCompletionBlock:^{
        NSLog(@"bkOp3........OK !!");
    }];
    
    NSBlockOperation * bkOp4 = [NSBlockOperation blockOperationWithBlock:^{
        sleep(10);
        NSLog(@"bkOp4.....handle.....on thread num%@",[NSThread currentThread]);
    }];
    [bkOp4 setCompletionBlock:^{
        NSLog(@"bkOp4........OK !!");
    }];
    
    NSBlockOperation * bkOp5 = [NSBlockOperation blockOperationWithBlock:^{
        sleep(5);
        NSLog(@"bkOp5.....handle.....on thread num%@",[NSThread currentThread]);
    }];
    [bkOp5 setQueuePriority:NSOperationQueuePriorityHigh];
    [bkOp5 setCompletionBlock:^{
        NSLog(@"bkOp5........OK !!");
    }];
    
    NSInvocationOperation *invoOp6 = [[NSInvocationOperation alloc] initWithTarget:(id)self selector:@selector(handleInvoOp) object:nil];
    [invoOp6 setCompletionBlock:^{
        NSLog(@"invoOp6........OK !!");
    }];
    [invoOp6 setQueuePriority:NSOperationQueuePriorityHigh];
    
    [qu setMaxConcurrentOperationCount:2];
    [qu addOperation:bkOp3];
    [qu addOperation:bkOp2];
    [qu addOperation:bkOp1];
    [qu addOperation:bkOp4];
    [qu addOperation:bkOp5];
    [qu addOperation:invoOp6];
}

在设置了bkop5以及invOp6的优先级为高时,他们会优先执行,当然这个优先时相对,是相对正在排队的,不包括已经正在执行的。

总结:NSOperation、NSBlockOperation、NSInvocationOperation、NSOperationQueue都比较简单,NSOperation、NSBlockOperation、NSInvocationOperation单个都是表示一种操作,而NSOperationQueue是一个可以包含多个NSOperation的队列,可以自己在多个线程处理,只要加入队列之后,我们就不用去操作,直到Callback或者完成。

** 3. GCD****的介绍和使用**
介绍: Grand Central Dispatch 简称(GCD)是苹果公司开发的技术, 以优化的应用程序支持多核心处理器和其他的对称处理系统的系统. 这简历在任务并行执行的线程池模式的基础上的. 它首次发布在Mac OS X 10.6, iOS 4及以上可用.

设计: GCD的工作原理是: 让程序平行排列的特定任务, 根据可用的处理资源, 安排他们在任何可用的处理器核心上执行任务.

一个任务可以是一个函数(function)或者是一个block. GCD的底层依然是用线程实现, 不过这样可以让程序员不用关注实现的细节.

GCD中的FIFO队列称为Dispatch Queue, 它可以保证先进来的任务先得到执行.

Dispatch Queue分下面三种:
Serial
又称为private dispatch queues, 同时只执行一个任务. serial queue 通常用于同步访问特定的资源或数据. 当你创建多个 Serial Queue 时, 虽然他们各自是同步执行的, 但 Serial Queue 与 Serial queue之间是并发执行的.

Concurrent
又称为global dispatch queue, 可以并发执行多个任务, 但执行完成的顺序是随机的.

Main dispatch queue
它是全局可用的serial queue, 它是在应用程序主线程上执行任务的.

常用的方法 dispatch_async

为了避免界面在处理耗时的操作时卡死, 比如读取网络数据, IO, 数据库读写等, 我们会在另外一个线程中处理这些操作, 然后通知主线程更新界面.

用GCD实现这个流程的操作比前面介绍的 NSThread NSOperation 的方法都要简单. 代码框结构如下:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{   
    NSURL * url = [NSURL URLWithString:@"http://image.tianjimedia.com/uploadImages/2012/233/38/H439I0N71ARI.jpg"];   
    NSData * data = [[NSData alloc]initWithContentsOfURL:url];   
    UIImage *image = [[UIImage alloc]initWithData:data];   
    if (data != nil) {   
        dispatch_async(dispatch_get_main_queue(), ^{   
            self.imageView.image = image;   
         });   
    }   
});

相比前两种, GCD会自动根据任务在多喝处理器上分配资源, 优化程序.

系统给每一个应用程序提供了三个 concurrent dispatch queues. 这三个并发调度队列是全局的, 他们只是优先级的不同, 因为是全局的, 我们不需要去创建,. 我们只需要通过使用函数dispath_get_global_queue去得到队列, 如下:

dispatch_queue_t globalQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

这里也用到了系统默认就有一个串行队列main_queue:
dispatch_queue_t mainQ = dispatch_get_main_queue();

虽然dispatch queue 是引用计数对象, 但是以上两个都是全局的队列, 不用retain 或 release.

dispatch_group_async的使用

dispatch可以监听一组任务是否完成, 完成后得到通知执行其他的操作. 这个方法很有用, 比如你执行三个下载任务, 当三个任务都下载完成后你才通知界面说完成的了, 举例:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);   
dispatch_group_t group = dispatch_group_create();   
dispatch_group_async(group, queue, ^{   
    [NSThread sleepForTimeInterval:1];   
    NSLog(@"group1");   
});   
dispatch_group_async(group, queue, ^{   
    [NSThread sleepForTimeInterval:2];   
    NSLog(@"group2");   
});   
dispatch_group_async(group, queue, ^{   
    [NSThread sleepForTimeInterval:3];   
    NSLog(@"group3");   
});   
dispatch_group_notify(group, dispatch_get_main_queue(), ^{   
    NSLog(@"updateUI”);   
});   
dispatch_release(group);

dispatch_group_async是异步的方法,运行后可以看到打印结果:
每隔一秒打印一个,当第三个任务执行后,upadteUI被打印。

dispatch_barrier_async的使用

dispatch_barrier_async是在前面的任务执行结束后他才执行, 而且它后面的任务等它执行完成之后才会执行
举例:

dispatch_queue_t queue = dispatch_queue_create(“haha.com”, DISPATCH_QUEUE_CONCURRENT);   
dispatch_async(queue, ^{   
    [NSThread sleepForTimeInterval:2];   
    NSLog(@"dispatch_async1");   
});   
dispatch_async(queue, ^{   
    [NSThread sleepForTimeInterval:4];   
    NSLog(@"dispatch_async2");   
});   
// 先执行完前面的在执行后面的
dispatch_barrier_async(queue, ^{   
    NSLog(@"dispatch_barrier_async");   
    [NSThread sleepForTimeInterval:4];   

});   
dispatch_async(queue, ^{   
    [NSThread sleepForTimeInterval:1];   
    NSLog(@"dispatch_async3");   
});

dispatch_apply
执行某个代码片段N次.

dispatch_apply(5, globalQ, ^(size_t index)) {
 // 执行5次
}

相关文章

  • 多线程全知道

    iOS中三种多线程编程技术分别是: NSThread Cocoa NSOperation GCD (全称: Gra...

  • Python多线程-thread.start_new_threa

    在使用python多线程的时候,踩到了主线程未等待多线程进程运行完成就结束,导致多线程无效的坑。后来想到自己写个全...

  • python——多线程

    多线程-threading 子类完成创建多线程 线程的执行顺序也是主线程和各个子线程随机执行,顺序不确定 线程对全...

  • 全知道

    品牌全知道 品牌全知道 理肤泉敏感全知道 薇姿健康肌肤全知道 中信银行全知道 Windows7全...

  • iOS之多线程(全)

    很多人都写过多线程的文章,今天我也来小小的总结一下: 多线程基本概念 1个应用程序要想执行任务,必须得有线程(每1...

  • 技术体系

    一,java核心 java基础,jvm,算法,多线程,设计模式 Java基础:java基础相关,全栈java基础 ...

  • (1)如何开启多线程

    前言 学习java多线程首先就是要知道如何开启多线程.在java中开启多线程主要有两种方式: 继承Thread类,...

  • 最强大的Android线程池框架

    背景 大家都知道在我们的开发中永远都离不开多线程,对于我们为什么要使用多线程,多线程的使用和多线程的一些基础知识这...

  • Java多线程基础学习

    Java多线程基础 1.多线程简介 在了解多线程之前我们要先知道什么是进程和线程: 进程:进程是系统进行调度和分配...

  • 肥水全知道

    一、前期肥水为什么困难,且不稳定? (1)不解毒或解毒不彻底。 消毒药或杀藻药药性太强,抑制了藻类的生长;河水里抗...

网友评论

      本文标题:多线程全知道

      本文链接:https://www.haomeiwen.com/subject/blmcjttx.html