关于iOS多线程

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

本篇文章排版不是很好,可以去看 修改版

多线程

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

同步&异步

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>线程间的通信:一个线程执行完任务之后,把执行的结果传递到另外一个线程叫线程间通信。线程间通信可用来在两个线程间传递数据。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,829评论 1 331
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,603评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,846评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,600评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,780评论 3 272
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,695评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,136评论 2 293
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,862评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,453评论 0 229
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,942评论 2 233
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,347评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,790评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,293评论 3 221
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,839评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,448评论 0 181
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,564评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,623评论 2 249

推荐阅读更多精彩内容