多线程在iOS开发中的应用

1. 多线程基本概念


1.1 进程

进程是指在系统中正在运行的一个应用程序。每一个进程之间是独立的,每一个进程均运行在其专用且受保护的内存空间内。ios

1.2 线程

基本概念:一个进程要想执行任务,必须得有线程(每个进程至少要有一条线程),线程是进程的基本执行单元,一个进程(程序)的全部任务都在线程中执行。
线程的串行:一条线程中任务的执行是串行的,若是要在一条线程中执行多个任务,那么只能一个一个地按顺序执行这些任务。也就是说,在同一时间内,一条线程只能执行一个任务。编程

1.3 多线程

基本概念:即一个进程中能够开启多个线程,每条线程能够并行(同时)执行不一样的任务。
线程的并行:并行即同时执行。好比同时开启3条线程分别下载3个文件(分别是文件A、文件B、文件C)。
多线程并发执行的原理:在同一时间里,CPU只能处理1条线程,只有1条线程在工做(执行)。多线程并发(同时)执行,实际上是CPU快速地在多条线程之间调度(切换),若是CPU调度线程的时间足够快,就形成了多线程并发执行的假象。
多线程优缺点
优势:能适当提升程序的执行效率、能适当提升资源利用率(CPU、内存利用率)
缺点:
1)开启线程须要占用必定的内存空间(默认状况下,主线程占用1M,子线程占用512KB),若是开启大量的线程,会占用大量的内存空间,下降程序的性能。
2)线程越多,CPU在调度线程上的开销就越大。
3)程序设计更加复杂:好比线程之间的通讯、多线程的数据共享安全

1.4 多线程在iOS开发中的应用

1.4.1 主线程

​ 1)一个iOS程序运行后,默认会开启1条线程,称为“主线程”或“UI线程”。
​ 2)做用。刷新显示UI,处理UI事件。多线程

1.4.2 使用注意

​ 1)不要将耗时操做放到主线程中去处理,会卡住线程。
​ 2)和UI相关的刷新操做必须放到主线程中进行处理。
并发

2. 多线程实现方案

技术方案 简介 语言 线程生命周期管理 使用频率
pthread —> 一套通用的多线程API
—> 适用于Unix\Linux Windows等系统
—> 跨平台\可植入
—> 使用难度比较大
C 手动 极低
NSThread —> 使用更加面向对象
—> 简单易用,能够直接操做线程对象
Objective-C 手动
GCD —> 旨在替代NSThread等线程技术
—> 充分利用设备的多核
C 自动
NSOperation —> 基于GCD (底层是GCD)
—> 比GCD多了一些更简单实用的功能
—> 使用更加面向对象
Objective-C 自动

2.1 pthread

//pthread须要包含头文件
//1.建立线程对象
pthread_t thread;
NSString *name = @"线程";
//2.使用pthread建立线程
//第一个参数:线程对象地址
//第二个参数:线程属性
//第三个参数:指向函数的指针
//第四个参数:传递给该函数的参数
pthread_create(&thread, NULL, run, (__bridge void *)(name));

2.2 NSThread

2.2.1 基本使用

// 第一种:alloc init,须要手动开启线程,能够拿到线程对象进行详细设置
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run:) object:@"线程"];
[thread start];

// 第二种:分离(detach)出一条子线程,自动启动线程,没法对线程进行更详细的设置
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"分离出的子线程"];

// 第三种:后台线程,自启动,不能详细设置
[self performSelectorInBackground:@selector(run:) withObject:@"后台线程"];

2.2.2 设置线程属性

thread.name = @"线程A"; // 线程名称
thread.threadPriority = 1.0; // 线程的优先级,取值范围0.0~1.0,1.0的优先级最高,默认0.5

2.2.3 线程的状态

// 线程的各类状态:新建-就绪-运行-阻塞-死亡
// 经常使用的控制线程状态的方法
[NSThread exit]; // 退出当前线程
[NSThread sleepForTimeInterval:2.0]; // 阻塞线程
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]];//阻塞线程
// 注意:线程死了不能复生

2.2.4 线程安全

01 前提:多个线程访问同一块资源会发生数据安全问题
02 解决方案:加互斥锁
03 相关代码:@synchronized(self){}
04 专业术语-线程同步
05 原子和非原子属性(是否对setter方法加锁)

2.2.5线程间通讯

-(void)touchesBegan:(nonnull NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event {
    // 开启子线程下载图片
    [NSThread detachNewThreadSelector:@selector(downloadImage) toTarget:self withObject:nil];
}

-(void)downloadImage {
  
    NSURL *url = [NSURL URLWithString:@"http://p6.qhimg.com/t01d2954e2799c461ab.jpg"];
    UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
  
    // 回到主线程刷新UI,如下三种任选其一,第二种方面些
    [self performSelectorOnMainThread:@selector(showImage:) withObject:image waitUntilDone:YES];
    [self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES];
    [self.imageView performSelector:@selector(setImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];
}

2.2.6 如何计算代码段的执行时间

//第一种方法
NSDate *start = [NSDate date];
NSData *data = [NSData dataWithContentsOfURL:url];
NSDate *end = [NSDate date];
NSLog(@"耗时%f",[end timeIntervalSinceDate:start]);

//第二种方法
CFTimeInterval start = CFAbsoluteTimeGetCurrent();
NSData *data = [NSData dataWithContentsOfURL:url];
CFTimeInterval end = CFAbsoluteTimeGetCurrent();
NSLog(@"耗时%f",end - start);

2.3 GCD

2.3.1 GCD基本知识

  • 两个核心概念 队列和任务
  • 同步函数和异步函数

2.3.2 GCD基本使用

主队列:dispatch_get_main_queue()app

并发队列:dispatch_get_global_queue(0, 0),dispatch_queue_create("com.test", DISPATCH_QUEUE_CONCURRENT)异步

手动建立串行队列:dispatch_queue_create("com.test", DISPATCH_QUEUE_SERIAL)async

并发队列 手动建立的串行队列 主队列
dispatch_sync 没有开启新的线程,串行执行 同← 同←
dispatch_async 开启新的线程,并发执行 开启新的线程,串行执行 没有开启新的线程,串行执行

2.3.3 GCD线程间通讯

// 获取一个全局的队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 先开启一个线程,把下载图片的操做放在子线程中处理
dispatch_async(queue, ^{
    // 下载图片
    NSString *urlString = @"http://h.hiphotos.baidu.com/zhidao/pic/item/6a63f6246b600c3320b14bb3184c510fd8f9a185.jpg";
    NSURL *url = [NSURL URLWithString:urlString];
    UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
    // 回到主线程刷新UI
    dispatch_async(dispatch_get_main_queue(), ^{
        self.imageView.image = image;
    });
});

2.3.4 GCD其它经常使用函数

—> 栅栏函数(控制任务的执行顺序)函数

如下实例,会保证Task One和Task Two所有完成后执行以后的Task Three和Task Four

dispatch_queue_t queue = dispatch_queue_create("com.example.myqueue", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{
    for (int i = 0; i < 10; i++) {
         NSLog(@"Task One");
    }
});
dispatch_async(queue, ^{
    for (int i = 0; i < 10; i++) {
        NSLog(@"Task Two");
    }
});

// The queue you specify should be a concurrent queue that you create yourself using the dispatch_queue_create function. If the queue you pass to this function is a serial queue or one of the global concurrent queues, this function behaves like the dispatch_async function.
// 使用 dispatch_queue_create 函数建立并发队列(使用DISPATCH_QUEUE_CONCURRENT, 而不是DISPATCH_QUEUE_SERIAL), 不要使用dispatch_get_global_queue, 不然和dispatch_async同样效果
dispatch_barrier_async(queue, ^{
    NSLog(@"++++++++++");
});

dispatch_async(queue, ^{
    for (int i = 0; i < 10; i++) {
        NSLog(@"Task Three");
    }
});

dispatch_async(queue, ^{
    for (int i = 0; i < 10; i++) {
        NSLog(@"Task Four");
    }
});

—> 延迟执行(延迟而且能够控制在哪一个线程执行)

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // code to be executed after a specified delay
});

—> 一次性代码

-(void)once {
    // 整个程序运行过程当中只会执行一次(注意区分一次性代码和懒加载)
    // onceToken用来记录该部分的代码是否被执行过
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    });
}

—> 快速迭代(开多个线程并发完成迭代操做)

dispatch_apply(12, dispatch_get_global_queue(0, 0), ^(size_t index) {
    NSLog(@"SuperLog------   %zd",index);
});

—> 队列组(同栅栏函数)

dispatch_queue_t queue = dispatch_queue_create("com.example", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
    
});
// 队列组中的任务执行完毕以后,执行该函数
dispatch_group_notify(group, queue, ^{
    
});

2.4.NSOperation

2.4.1 概念

  • NSOperation是对GCD的包装
  • 两个核心概念【队列+操做】

2.4.2 使用

  • NSOperation自己是抽象类,只能使用它的子类
  • 三个子类分别是:NSBlockOperation、NSInvocationOperation以及自定义继承自NSOperation的类
  • NSOperation和NSOperationQueue结合使用实现多线程并发

2.4.3 代码

—> NSInvocationOperation

NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(test) object:nil];
[operation start];

—> NSBlockOperation

NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
    // 在主线程中执行
    NSLog(@"downloadOne--%@",[NSThread currentThread]);
}];

// 增长操做,增长的操做在子线程中执行
[operation addExecutionBlock:^{
    NSLog(@"downloadTwo--%@",[NSThread currentThread]);
}];

[operation addExecutionBlock:^{
    NSLog(@"downloadThree--%@",[NSThread currentThread]);
}];

[operation start];

—> 自定义NSOperation

// 自定义的NSOperation, 经过重写内部的main方法实现封装操做
-(void)main {
    NSLog(@"--main--%@",[NSThread currentThread]);
}
// 使用
SPOperation *operation = [[SPOperation alloc] init];
[operation start];

2.5 NSOperationQueue

2.5.1 NSOperation中的两种队列

  • 主队列 经过mainQueue得到,凡是放到主队列中的任务都将在主线程执行
  • 非主队列 直接alloc init出来的队列。非主队列同时具有了并发和串行的功能,经过设置最大并发数属性来控制任务是并发执行仍是串行执行

2.5.2 代码

GCD中的队列
串行队列:dispatch_queue_create("com.test", DISPATCH_QUEUE_SERIAL),dispatch_get_main_queue()
并发队列:dispatch_queue_create("com.test", DISPATCH_QUEUE_CONCURRENT),dispatch_get_global_queue(0, 0)

NSOperationQueue
主队列:[NSOperationQueue mainqueue] 放在主队列中的操做都在主线程中执行
非主队列:[[NSOperationQueue alloc] init] 并发和串行,默认是并发执行的

// 自定义NSOperation
-(void)customOperation {
    // 建立队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        // 建立操做
    SPOperation *operationOne = [[SPOperation alloc] init];
    SPOperation *operationTwo = [[SPOperation alloc] init];
    // 添加操做到队列中
    [queue addOperation:operationOne];
    [queue addOperation:operationTwo];
  
    // You can call this method explicitly if you want to execute your operations manually. However, it is a programmer error to call this method on an operation object that is already in an operation queue or to queue the operation after calling this method. Once you add an operation object to a queue, the queue assumes all responsibility for it.
    // 有队列管理,不要手动调用 start
    // [operationOne start]
}

//NSBlockOperation
- (void)block {
    // 建立队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
  
    // 封装操做
    NSBlockOperation *operationOne = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"operationOne--%@",[NSThread currentThread]);
    }];
    NSBlockOperation *operationTwo = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"operationTwo--%@",[NSThread currentThread]);

    }];
    [operationTwo addExecutionBlock:^{
        NSLog(@"operationThree--%@",[NSThread currentThread]);
    }];
  
    // 添加操做到队列中
    [queue addOperation:operationOne];
    [queue addOperation:operationTwo];
  
    // 开发直接使用该方法便可,简单方便
    [queue addOperationWithBlock:^{
        NSLog(@"operationFour--%@",[NSThread currentThread]);
    }];
}

// NSInvocationOperation
- (void)invocation {
    // 建立队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 封装操做
    NSInvocationOperation *operationOne = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(downloadOne) object:nil];
    NSInvocationOperation *operationTwo = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(downloadTwo) object:nil];
  
    // 把封装好的操做添加到队列中
    [queue addOperation:operationOne];
    [queue addOperation:operationTwo];
}

2.5.3 NSOperation其它用法

—> 设置最大并发数

NSOperationQueue *queue = [[NSOperationQueue alloc]init];
// 设置最大并发数, 该属性须要在任务添加到队列中以前进行设置
// 该属性控制队列是串行执行仍是并发执行, 1 串行 >1 并行
// 系统的最大并发数有个默认的值,为-1,若是该属性设置为0,那么不会执行任何任务
queue.maxConcurrentOperationCount = 2;

—> 暂停和恢复以及取消

//设置暂停和恢复
//暂停表示不继续执行队列中的下一个任务,暂停操做是能够恢复的
if (self.queue.isSuspended) {
    self.queue.suspended = NO;
}else {
    self.queue.suspended = YES;
}

// 取消队列里面的全部操做
// 取消以后,当前正在执行的操做的下一个操做将再也不执行,并且永远都不在执行,就像后面的全部任务都从队列里面移除了同样
// 取消操做是不能够恢复的
[self.queue cancelAllOperations];

// 自定义NSOperation取消操做
-(void)main {
    //耗时操做
    for (int i = 0; i<1000; i++) {
        NSLog(@"任务1-%d--%@",i,[NSThread currentThread]);
    }
    NSLog(@"+++++++++++++++++++++++++++++++++");
    //苹果官方建议,每当执行完一次耗时操做以后,就查看一下当前队列是否为取消状态,若是是,那么就直接退出(为了提升程序的性能)
    if (self.isCancelled) {
        return;
    }
}

2.5.4 NSOperation实现线程间通讯

—> 开子线程下载图片

NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperationWithBlock:^{
    NSURL *url = [NSURL URLWithString:@"http://h.hiphotos.baidu.com/zhidao/pic/item/6a63f6246b600c3320b14bb3184c510fd8f9a185.jpg"];
    UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
    // 主线程刷新UI
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        self.imageView.image = image;
    }];
}];

—> 操做依赖(operationThree 依赖operationOne和operationTwo)

NSOperationQueue *queue = [[NSOperationQueue alloc]init];
NSBlockOperation *operationOne = [NSBlockOperation blockOperationWithBlock:^{
    // Task One
}];

NSBlockOperation *operationTwo = [NSBlockOperation blockOperationWithBlock:^{
    // Task Two
}];

NSBlockOperation *operationThree = [NSBlockOperation blockOperationWithBlock:^{
    // Task Three
}];

// 操做依赖
[operationThree addDependency:operationOne];
[operationThree addDependency:operationTwo];

// 添加操做到队列中执行
[queue addOperation:operationOne];
[queue addOperation:operationTwo];
[queue addOperation:operationThree];

补充

使用create函数建立的并发队列和全局并发队列的主要区别:

  1. 全局并发队列在整个应用程序中自己是默认存在的,而且对应有高优先级、默认优先级、低优先级和后台优先级一共四个并发队列,咱们只是选择其中的一个直接拿来用。而create函数是从头开始去建立一个队列。
  2. 在iOS6.0以前,在GCD中凡是使用了带create和retain的函数在最后都须要作一次release操做。而主队列和全局并发队列不须要咱们手动release。固然了,在iOS6.0以后GCD已经被归入到了ARC的内存管理范畴中,即使是使用retain或者create函数建立的对象也再也不须要开发人员手动释放,咱们像对待普通OC对象同样对待GCD就能够。
  3. 在使用栅栏函数的时候,苹果官方明确规定栅栏函数只有在和使用create函数本身的建立的并发队列一块儿使用的时候才有效(没有给出具体缘由)
  4. 其它区别涉及到XNU内核的系统级线程编程,不一一列举。
  5. 给出一些参考资料(能够自行研究):

GCDAPI

Libdispatch版本源码

相关文章
相关标签/搜索