关于iOS多线程

移动开发 来源:曾经是超人 32℃ 0评论

本篇文章主要描述了多线程的一方面知识。可能有一些不足之处,希望大家指出。文章最后有一些关于多线程方面的面试题,仅供参考。

多线程

  • 同步&异步
  • 继承&线程
  • 多线程基本概念
  • 多线程的优缺点

    同步&异步

    1 同步和异步是两种执行任务的方式。
    2 同步:代码从上到下顺序执行就叫做同步执行(多个任务依次执行)。
    3 异步:多个任务同时执行就是异步执行。

    线程&进程

    1 进程:在系统中正在运行的一个程序叫做进程,进程可以类比成正在正常运营的公司。
    2 线程:线程可以类比成公司里的员工,程序启动默认会开启一个线程。

    多线程的基本概念

    1 多线程:一个进程中可以开启多条线程,多条线程可以同时执行不同的任务。
    2 举个例子:酷我音乐的边下载边听歌,迅雷的边下载边播放。

    多线程的优缺点

    1 优点:能‘适当’提高程序的执行效率,能适当提高CPU的内存利用率,线程上的任务执行完成后,线程会自动销毁节省内存。
    2 缺点:如果开启线程过多会占用大量CPU资源降低程序性能。

多线程的目的

  • 将耗时操作放在后台处理,保证UI界面的正常显示和交互。
  • 网络操作是非常耗时的,在做网络开发,所有网络访问都是耗时操作.需要在后台线程中执行。
  • 多线程开发的原则:越简单越好。

常见的多线程几种方式

  • PThread(开发中几乎用不到)
  • NSThread(开发中很少使用到)
  • GCD(经常使用)
  • NSoperation(经常使用)

    GCD

  • GCD的概念
  • GCD的简单使用
  • GCD的任务和队列

    GCD的概念

    1 什么是GCD:全称是Grand Central Dispatch,纯C语言的,提供了非常多强大的函数。
    2 GCD的核心:将任务添加到队列。
    3 GCD使用的两个步骤:创建任务,确定要做的事情,GCD中的任务是使用BLOCK封装的。将任务添加到队列中,GCD会自动将队列中的任务取出,放到对应的线程中执行。任务的取出遵循队列的FIFO原则 : 先进先出,后进后出。

    GCD的简单使用

    1 任务添加到队列
- (void)GCDDemo1
{
    // 1. 创建队列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

    // 2. 创建任务 : 用block指定的 (无参无返回值的)
    void (^task)() = ^ {
        NSLog(@"%@",[NSThread currentThread]);
    };

    // 3. 把任务添加到队列
    // dispatch_async : 表示任务是异步的
    // dispatch_sync : 表示任务是同步的
    dispatch_async(queue, task);
}

2 简写

- (void)GCDDemo2
{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"%@",[NSThread currentThread]);
    });
}

3 线程间的通信

- (void)GCDDemo4
{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{

        NSLog(@"假装在努力下载...%@",[NSThread currentThread]);
        // 下载结束之后,回到主线程更新UI
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"假装在更新UI...%@",[NSThread currentThread]);
        });
    });
}

4 使用GCD的线程间的通信实现异步下载网络图片

- (void)downloadImage
{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{

        NSLog(@"downloadImage %@",[NSThread currentThread]);

        // URL
        NSURL *URL = [NSURL URLWithString:@"http://atth.eduu.com/album/201203/12/1475134_1331559643qMzc.jpg"];
        // data
        NSData *data = [NSData dataWithContentsOfURL:URL];
        // image
        UIImage *image = [UIImage imageWithData:data];

        // 拿到图片对象之后,回到主线程刷新UI
        dispatch_async(dispatch_get_main_queue(), ^{

            NSLog(@"updateUI %@",[NSThread currentThread]);

            self.myImageView.image = image;
            [self.myImageView sizeToFit];
            [self.myScrollView setContentSize:image.size];
        });
    });
}

GCD的任务和队列

1 GCD的任务:
同步的方式执行任务 : 在当前线程中依次执行任务。
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
异步的方式执行任务 : 新开线程在新线程中执行任务。
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
2 GCD队列:
串行队列:让任务一个接着一个有序的执行:不管队列里面放的是什么任务,一个任务执行完毕后,再执行下一个任务,同时只能调度一个任务执行。



并发队列:可以让多个任务并发/同时执行,自动开启多个线程同时执行多个任务,同时可以调度多个任务执行。并发队列的并发功能只有内部的任务是异步任务时,才有效。


代码小结

1 串行队列+同步任务

/*
 1.不开线程
 2.有序执行
*/
- (void)GCDDemo1
{
    /* 
     创建串行队列
     参数1 : 队列的标识符
     参数2 :  队列的属性,决定了队列是串行的还是并行的
     DISPATCH_QUEUE_SERIAL : 串行队列
    */
    dispatch_queue_t queue = dispatch_queue_create("GL", DISPATCH_QUEUE_SERIAL);

    // 循环的创建了10个同步任务,添加到队列
    for (NSInteger i = 0; i<10; i++) {

        // 把同步任务添加到串行队列
        dispatch_sync(queue, ^{
            NSLog(@"%zd %@",i,[NSThread currentThread]);
        });
    }

    NSLog(@"哈哈哈");
}


2 串行队列+异步任务

- (void)GCDDemo2
{
    // 串行队列
    dispatch_queue_t queue = dispatch_queue_create("GL", DISPATCH_QUEUE_SERIAL);

    for (NSInteger i = 0; i<10; i++) {
        // 把异步任务添加到串行队列
        dispatch_async(queue, ^{
            NSLog(@"%zd %@",i,[NSThread currentThread]);
        });
    }

    NSLog(@"嘿嘿嘿");
}


3 并行队列+同步任务

/*
 不开线程
 有序执行
 */
- (void)GCDDemo1
{
    // 创建并行队列
    // DISPATCH_QUEUE_CONCURRENT : 并行队列
    // 并行队列只能决定"是否"可以同时调度多个任务;不能决定开不开线程
    dispatch_queue_t queue = dispatch_queue_create("GL", DISPATCH_QUEUE_CONCURRENT);

    for (NSInteger i = 0; i<10; i++) {
        // 把同步任务添加到并行队列
        dispatch_sync(queue, ^{
            NSLog(@"%zd %@",i,[NSThread currentThread]);
        });
    }

    NSLog(@"哈哈哈");
}


4 并行队列+异步任务

/*
 开线程
 无序执行
 */
- (void)GCDDemo2
{
    // 并行队列
    dispatch_queue_t queue = dispatch_queue_create("GL", DISPATCH_QUEUE_CONCURRENT);

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

        // 把异步任务添加到并发队列
        dispatch_async(queue, ^{
            NSLog(@"%zd %@",i,[NSThread currentThread]);
        });
    }

    NSLog(@"嘿嘿嘿");
}

Paste_Image.png

NSOperation

  • NSOperation的简介
  • NSOperation的简单使用
  • NSOperation的高级功能

    NSOperation的简介

    1 是OC语言中基于GCD的面向对象的封装,使用起来比GCD更加简单。提供了一些GCD不好实现的功能,苹果推荐使用。NSOperation还不用关心线程和线程的声明周期。
    2 NSOperation是个抽象类无法直接使用。因为方法只有声明没有实现。
    3 子类:NSInvocationOperation和NSBlockOperation,自定义NSOperation操作默是异步的。
    4 队列 : NSOperationQueue队列默认是并发的。
    5 核心:GCD的核心 : 将任务添加到队列中。OP的核心 : 将操作添加到队列中。

    NSOperation的简单使用

    1 先将需要执行的操作封装到一个NSOperation对象中,创建NSOperation对象。
    2 将NSOperation对象添加到NSOperationQueue中。
    3 NSOperationQueue会自动将NSOperation取出来。
    4 将取出的NSOperation封装的操作自动放到一条对应的新线程中执行。

    NSOperation的高级功能

1 最大操作并发数。
1>设置最大并发数

// 队列的最大并发数的属性
// 作用 : 控制队列同时调度任务执行的个数;
// 间接控制了线程的数量;
// 注意 : 队列的最大并发数,不是线程数;

@implementation ViewController {

    /// 全局队列
    NSOperationQueue *_queue;
}

- (void)viewDidLoad {
    [super viewDidLoad];

    _queue = [[NSOperationQueue alloc] init];

    // 设置队列的最大并发数 : 至少开两个
    _queue.maxConcurrentOperationCount = 2;
}

2>演示

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self GCDDemo];
}

- (void)GCDDemo
{
    for (NSInteger i = 0; i<50; i++) {

        NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"%zd %@",i,[NSThread currentThread]);
        }];

        [_queue addOperation:op];
    }
}

3>执行结果:任务是两个两个的执行。



2 继续/暂停/取消全部。
1>在最大并发数代码的基础上增加暂停、继续、取消。

#pragma 取消全部
/*
 1.正在执行的操作无法被取消;
 2.如果非要取消正在执行的操作,需要自定义NSOperation
 3.这个取消全部的操作有一定的时间延迟
 */
- (IBAction)cancelAll:(id)sender
{
    // 移除队列里面"所有"的操作
    [_queue cancelAllOperations];

    NSLog(@"取消全部 %tu",_queue.operationCount);
}

#pragma 继续
- (IBAction)jixu:(id)sender
{
    // 不挂起队列,使队列继续调度任务执行
    _queue.suspended = NO;

    NSLog(@"继续 %tu",_queue.operationCount);
}

#pragma 暂停
/*
 1.正在执行的操作无法被暂停
 2.operationCount : 队列里面的操作个数;统计的是队列里面还没有执行完的操作;
 3.队列里面的任务一旦执行完,会从队列里面移除;
 */
- (IBAction)zanting:(id)sender
{
    // 挂起队列,使队列暂停调度任务执行
    _queue.suspended = YES;

    NSLog(@"暂停 %tu",_queue.operationCount);
}

2>暂停队列结论
将队列挂起之后,队列中的操作就不会被调度,但是正在执行的操作不受影响。
operationCount:操作计数,没有执行和没有执行完的操作,都会计算在操作计数之内。
注意:如果先暂停队列,再添加操作到队列,队列不会调度操作执行。所以在暂停队列之前要判断队列中有没有任务,如果没有操作就不暂停队列。
3>取消队列结论
一旦调用的 cancelAllOperations方法,队列中的操作,都会被移除,正在执行的操作除外。
正在执行的操作取消不了,如果要取消,需要自定义NSOperation。
队列取消全部操作时,会有一定的时间延迟。
3 操作间依赖关系。
场景:登陆-->付费-->下载-->通知用户
1>准备需要执行的操作

    // 登录
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"登录 %@",[NSThread currentThread]);
    }];

    // 付费
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"付费 %@",[NSThread currentThread]);
    }];

    // 下载
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"下载 %@",[NSThread currentThread]);
    }];

    // 通知用户
    NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"通知用户 %@",[NSThread currentThread]);
    }];

2>添加依赖(核心代码)

    /*
     添加依赖关系
     1.不能在操作添加到队列之后,在建立依赖关系;因为已经晚了
     2.可以跨队列建立依赖关系
     3.不能建立循环依赖
     */
    [op2 addDependency:op1]; // 付费依赖登录
    [op3 addDependency:op2]; // 下载依赖付费
    [op4 addDependency:op3]; // 通知用户依赖下载

    // [op1 addDependency:op4]; // 登录依赖通知用户 : 循环依赖;会卡死

    // 批量把操作添加到队列
    // waitUntilFinished : 是否等待前面的异步任务执行完,在执行后面的代码
    [_queue addOperations:@[op1,op2,op3] waitUntilFinished:NO];

    // 一个操作不能同时添加到两个队列
    [[NSOperationQueue mainQueue] addOperation:op4];

3>结论
不能循环建立操作间依赖关系,否则队列不调度操作执行。
操作间可以跨队列建立依赖关系。
要将操作间的依赖建立好了之后,再添加到队列中,先建立操作依赖关系,再把操作添加到队列。


面试题

面试题仅供参考。 ><!

  1. 用 NSOpertion 和 NSOpertionQueue 处理 A,B,C 三个线程,要求执行完 A,B 后才能执行 C, 怎么做?
    答: 添加操作依赖,C依赖于A同时依赖于B。创建操作队列,将操作添加到操作队列中。
  2. 线程间怎么通信?
    答:什么是线程通信:不同线程之间传递数据,一般线程传递到主线程。 iOS中开启多线程的方式:三种。比如:在子线程下载图片,然后回到主线程显示图片。
  3. 简述多线程的作用以及什么地方会用到多线程?OC实现多线程的方法有哪些?谈谈多线程安全问题的几种解决方案?何为线程同步,如何实现的?分线程回调主线程方法是什么,有什么作用?
    答:1>耗时操作、界面卡死的时候使用多线程
    2.>作用:可以同时执行多个任务,适当提高程序的执行效率。为了提高CPU的使用率,采用多线程的方式去同时完成几件事互不干扰。
    3>iOS中多线程的方法:NSThread、NSOperation、GCD、pthread
    4>使用场景:同时上传和下载多个文件:加载网络数据同时展示Loading的UI、大量数据I/O操作。
    5>资源共享造成的安全问题:多线程环境下,当多个线程同时操作共享资源的setter和getter方法时,会造成数据的读写错乱就是线程安全问题。
    6>线程同步技术:使多个线程一次有序的执行,实现方案是加锁,把共享资源的读写操作锁起来常用的是互斥锁。
    7>线程间的通信:一个线程执行完任务之后,把执行的结果传递到另外一个线程叫线程间通信。线程间通信可用来在两个线程间传递数据。