iOS_小蟹专题iOS开发的正确姿势iOS develop

iOS开发之多线程编程总结(一)

2016-10-21  本文已影响8190人  Dely

背景

前段时间锻炼身体胃出血的事情,每天想着这个事情,害怕有什么大问题!这周一才预约下周一去做胃镜检查,又要担心好几天啊!我在思考如果真有大病,医院这种制度得耽误多少病人,因为这种制度而耽误多少人的最佳治疗时间?

我们还是要多读书的,因为最近动物考古学家一行在饭店吃饭,点的羊腿中赫然发现猪骨,愤而与店家对质(你们知道我们是干什么的吗?我们连一根毛都知道出自什么动物身上),最后店家给免单了(我怎么就没这本事呢_)!

真TM好看.png

为什么要读书?
你失恋时…你会说:“人生若只如初见,何事秋风悲画扇。等闲变却故人心,却道故人心易变。”而不是千万遍呼喊:“蓝瘦,香菇!”

当你看到夕阳余晖…你脑海里浮现的是:“落霞与孤鹜齐飞,秋水共长天一色。”而不是:“卧槽,这多鸟,这鸟真肥啊,真好看,真他妈太好看了!”

所以我们要多读书,多学习,这样才能出去装X!

前言

说到多线程编程大家肯定不陌生,也是面试和工作中经常碰到的,所以网上各种博客层出不穷,光在简书上搜索iOS 多线程就有1w+的文章,所以我也来凑凑热闹混个脸熟(不要脸啊)!
谈到多线程就到说到线程和进程,就要说到NSThread、GCD、NSOperation!就要提到RunLoop,你既然提到了RunLoop了,他兄弟Runtime肯定要提到吧(他俩不是一回事),这些我都会在后面一一道来啊!我能从诗词歌赋谈到人生哲学,从人生哲学谈到诗词歌赋。下面就跟我一起来装X!

基本知识

1. 进程(process)

2. 线程(thread)

3. 进程和线程的关系

举个例子:进程就好比公司中的一个个部门,线程则代表着部门中的同事,而主线程当然是我们的老板了,一个公司不能没有老板,一个程序不能没有线程其实都是一个道理.

4. 多线程

CPU命令列.jpg

Mac、iPhone的操作系统OS X、iOS根据用户的指示启动应用程序后,首先便将包含在应用程序中的CPU命令列配置到内存中。CPU从应用程序知道的地址开始,一个一个执行CPU命令列。

在OC的if或for语句等控制语句或函数调用的情况下,执行命令列的地址会远离当前的位置(位置迁移)。但是,由于一个CPU一次只能执行一个命令,不能执行某处分开的并列的两个命令,因此通过CPU执行的CPU命令列就好比一条无分叉的大道,其执行不会出现分歧。

通过CPU执行的CPU命令列.png

这里所说的“1个CPU执行的CPU命令列为一条无分叉路径”,即为“线程”

这种无分叉路径不只1条,存在有多条时即为“多线程”。 1个CPU核执行多条不同路径上的不同命令。

在多线程中执行CPU命令列.png

OS X和iOS的核心XNU内核在发生操作系统事件时(如每隔一定时间,唤起系统调用等情况)会切换执行路径。例如CPU的寄存器等信息保存到各自路径专用的内存块中,从切换目标路径专用的内存块中,复原CPU寄存器等信息,继续执行切换路径的CPU命令列。这被称为“上下文切换

由于使用多线程的程序可以在某个线程和其他线程之间反复多次进行上下文切换,因此看上去就好像1个CPU核能够并列地执行多个线程一样。而且在具有多个CPU核的情况下,就不是“看上去像”了,而是真的提供了多个CPU核并行执行多个线程的技术。

这种利用多线程编程的技术就被称为“多线程编程

但是,多线程编程实际上是一种易发生各种问题的编程技术。比如多个线程更新相同资源会导致数据的不一致(数据竞争)、停止等待事件的线程会导致多个线程相互等待(死锁)、使用太多线程会消耗大量内存等。


多线程编程易发问题.png

4.多线程的优点和缺点

5.多线程实际应用

6.主线程

7.串行(Serial)和 并行(Parallelism)

串行和并行描述的是任务和任务之间的执行方式. 串行是任务A执行完了任务B才能执行, 它们俩只能顺序执行. 并行则是任务A和任务B可以同时执行.

8.同步(Synchronous) 和 异步(Asynchronous)

同步和异步描述的其实就是函数什么时候返回. 比如用来下载图片的函数A: {download image}, 同步函数只有在image下载结束之后才返回, 下载的这段时间函数A只能搬个小板凳在那儿坐等... 而异步函数, 立即返回. 图片会去下载, 但函数A不会去等它完成. So, 异步函数不会堵塞当前线程去执行下一个函数!

9.并发(Concurrency) 和 并行(Parallelism)

Ray大神的示意图和说明来解释一下:

并发是程序的属性(property of the program), 而并行是计算机的属性(property of the machine).

Concurrent_vs_Parallelism.png

还是很抽象? 那我再来解释一下, 并行和并发都是用来让不同的任务可以"同时执行", 只是并发是伪同时, 而并行是真同时. 假设你有任务T1和任务T2(这里的任务可以是进程也可以是线程):

a. 首先如果你的CPU是单核的, 为了实现"同时"执行T1和T2, 那只能分时执行, CPU执行一会儿T1后马上再去执行T2, 切换的速度非常快(这里的切换也是需要消耗资源的, context switch), 以至于你以为T1和T2是同时执行了(但其实同一时刻只有一个任务占有着CPU).

b. 如果你是多核CPU, 那么恭喜你, 你可以真正同时执行T1和T2了, 在同一时刻CPU的核心core1执行着T1, 然后core2执行着T2, great!

其实我们平常说的并发编程包括狭义上的"并行"和"并发", 你不能保证你的代码会被并行执行, 但你可以以并发的方式设计你的代码. 系统会判断在某一个时刻是否有可用的core(多核CPU核心), 如果有就并行(parallelism)执行, 否则就用context switch来分时并发(concurrency)执行.

Parallelism requires Concurrency, but Concurrency does not guarantee Parallelism!(并行要求并发性,但并发并不能保证并行性)

iOS中的多线程

类型 | 简介| 实现语言| 线程生命周期|使用频率|
---- |----- | ----- |-----
pthread| 1. 一套通用的多线程API
2. 适用于 Unix / Linux / Windows 等系统
3. 跨平台\可移植
4. 使用难度大|C|程序员管理|几乎不用
NSThread |1. 使用更加面向对象
2. 简单易用,可直接操作线程对象|OC|程序员管理|偶尔使用
GCD|1. 旨在替代NSThread等线程技术
2. 充分利用设备的多核
3. 基于 C 的底层的 API|C|自动管理|经常使用
NSOperation|1. 是基于 GCD 实现的 Objective-C API
2. 比GCD多了一些更简单实用的功能
3. 使用更加面向对象|OC|自动管理|经常使用

小结:上面只是介绍了多线程涉及的基本概念和基本知识(要理解啊),防止在后面学习的过程中混淆。下面才刚刚踏上多线程编程的道路:NSThread、GCD、NSOperation,pthread就不介绍了(有兴趣的自行学习),因为我在开发中没用过,用到再补充吧(哪那么多借口,不会就不会呗)。下面先介绍简单的NSThread。GCD和NSOperation会单独拿出来介绍(东西可能比较多)!

NSThread的使用

创建线程的方式

1、 通过NSThread的对象方法

- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument NS_AVAILABLE(10_5, 2_0);

2、 通过NSThread的类方法

+ (void)detachNewThreadWithBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;

3、通过NSObject的分类方法

- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg NS_AVAILABLE(10_5, 2_0);

NSThread代码Demo

- (IBAction)downloadAction:(UIButton *)sender {
//    [self categoryNSthreadMethod];
//    [self classNSthreadMethod];
    [self objectNSthreadMethod];
    
}

//通过NSObject的分类方法开辟线程
- (void)categoryNSthreadMethod{
    [self performSelectorInBackground:@selector(downloadImage) withObject:nil];
}

//通过NSThread类方法开辟线程
- (void)classNSthreadMethod{
    //异步1
//    [NSThread detachNewThreadSelector:@selector(downloadImage) toTarget:self withObject:nil];
    
    //异步方式2
    [NSThread detachNewThreadWithBlock:^{
        [self downloadImage];
    }];
}

//通过NSThread对象方法去下载图片
- (void)objectNSthreadMethod{
    //创建一个程序去下载图片
    NSThread *thread=[[NSThread alloc]initWithTarget:self selector:@selector(downloadImage) object:nil];
    //开启线程
    [thread start];
    thread.name = @"imageThread";
}

//下载图片
- (void)downloadImage{
    NSURL *url = [NSURL URLWithString:@"https://p1.bpimg.com/524586/475bc82ff016054ds.jpg"];
    
    //线程延迟10s
    [NSThread sleepForTimeInterval:5.0];
    NSData *data = [NSData dataWithContentsOfURL:url];
    
    NSLog(@"downLoadImage:%@",[NSThread currentThread]);//在子线程中下载图片
    //在主线程更新UI
    [self performSelectorOnMainThread:@selector(updateImage:) withObject:data waitUntilDone:YES];

}

//更新imageView
- (void)updateImage:(NSData *)data{
    NSLog(@"updateImage:%@",[NSThread currentThread]);//在主线程中更新UI
    //将二进制数据转换为图片
    UIImage *image=[UIImage imageWithData:data];
    //设置image
    self.imageView.image=image;
}

线程状态

线程通信

线程在运行过程中,可能需要与其它线程进行通信,如在主线程中修改界面等等,可以使用如下接口:

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait

线程的属性

@property (readonly, getter=isExecuting) BOOL executing NS_AVAILABLE(10_5, 2_0);//是否正在执行
@property (readonly, getter=isFinished) BOOL finished NS_AVAILABLE(10_5, 2_0);//是否完成
@property (readonly, getter=isCancelled) BOOL cancelled NS_AVAILABLE(10_5, 2_0);//是否取消

线程的同步与锁

线程的同步与锁什么时候会遇到?就是我们在线程公用资源的时候,导致的资源竞争。举个例子:多个窗口同时售票的售票系统!

#import "SellTicketsViewController.h"

@interface SellTicketsViewController (){
    NSInteger tickets;//总票数
    NSInteger count;//当前卖出去票数
}

@property (nonatomic, strong) NSThread* ticketsThreadOne;
@property (nonatomic, strong) NSThread* ticketsThreadTwo;
@property (nonatomic, strong) NSLock *ticketsLock;

@end

@implementation SellTicketsViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    tickets = 100;
    count = 0;
    
    //锁对象
    self.ticketsLock = [[NSLock alloc] init];

    self.ticketsThreadOne = [[NSThread alloc] initWithTarget:self selector:@selector(sellAction) object:nil];
    self.ticketsThreadOne.name = @"thread-1";
    [self.ticketsThreadOne start];
    
    self.ticketsThreadTwo = [[NSThread alloc] initWithTarget:self selector:@selector(sellAction) object:nil];
    self.ticketsThreadTwo.name = @"thread-2";
    [self.ticketsThreadTwo start];
    
}

- (void)sellAction{
    while (true) {
        //上锁
        [self.ticketsLock lock];
        if (tickets > 0) {
            [NSThread sleepForTimeInterval:0.5];
            count = 100 - tickets;
            NSLog(@"当前总票数是:%ld----->卖出:%ld----->线程名:%@",tickets,count,[NSThread currentThread]);
            tickets--;
        }else{
            break;
        }
        //解锁
        [self.ticketsLock unlock];
    }
}

@end

通过上面的Demo应该理解线程的同步以及锁的使用问题

[myLock lock]

资源处理....

[myLock unLock];

总结:又到了一篇最后的总结问题了,这篇主要讲解了一些基本概念、基本知识点、以及简单的NSThread。一些基本知识点在后面的博客中也会用到,希望你们能理解,更好的理解多线程!今天的博客就写到这里了。

如果我的文章对你有帮助,请点个喜欢,关注我(楼主真是厚颜无耻啊),如果还没看过瘾那就期待下期的GCD的知识吧_

参考资料:
http://www.jianshu.com/p/9f2fc08f9947
http://www.jianshu.com/p/ebb3e42049fd
http://www.jianshu.com/p/7ce30a806c51
书籍:Objective-C高级编程 iOS与OS X多线程和内存管理

上一篇下一篇

猜你喜欢

热点阅读