码迷,mamicode.com
首页 > 编程语言 > 详细

UI进阶 多线程

时间:2016-05-18 21:55:39      阅读:244      评论:0      收藏:0      [点我收藏+]

标签:

一、多线程概述

  • 程序、进程、线程
    • 程序:由源代码生成的可执行应用。(例如:QQ.app)
    • 进程:一个正在运行的程序可以看做一个进程。(例如:正在运行的QQ就是一个进程),进程拥有独立运行所需的全部资源。
    • 线程:程序中独立运行的代码段。(例如:接收QQ消息的代码)

  一个进程是由一或多个线程组成。进程只负责资源的调度和分配,线程才是程序真正的执行单元,负责代码的执行。

  • 单线程
    • 每个正在运行的程序(即进程),至少包含一个线程,这个线程叫主线程。
    • 主线程在程序启动时被创建,用于执行main函数。
    • 只有一个主线程的程序,称作单线程程序。
    • 在单线程程序中,主线程负责执行程序的所有代码(UI展现以及刷新,网络请求,本地存储等等)。这些代码只能顺序执行,无法并发执行
  • 多线程
    • 拥有多个线程的程序,称作多线程程序。
    • iOS允许用户自己开辟新的线程,相对于主线程来讲,这些线程,称作子线程。
    • 可以根据需要开辟若干子线程
    • 子线程和主线程 都是 独立的运行单元,各自的执行互不影响,因此能够并发执行
  • 单线程、多线程区别
    • 单线程程序:只有一个线程,即主线程,代码顺序执行,容易出现代码阻塞(页面假死)。
    • 多线程程序:有多个线程,线程间独立运行,能有效的避免代码阻塞,并且提高程序的运行性能。
    • 注意:iOS中关于UI的添加和刷新必须在主线程中操作。

二、iOS平台下的多线程

  • iOS多线程实现种类
    • NSThread
    • NSOperationQueue
    • NSObject
    • GCD

三、NSThread

  • NSThread是一个轻量级的多线程,它有以下两种创建方法
    方法 功能
    - (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument 初始化一个子线程,但需要手动开启
    + (void)detachNewThreadSelector:(SEL)aSelector toTarget:(id)aTarget withObject:(id)anArgument 初始化一个子线程并自动开启
    -start 开启子线程
    -cancel 取消当前子线程,不是真正的取消,而是给子线程发送了一个“取消”消息,标记为canceled
    +exit

    直接将线程退出

    • 第一种:手动开辟一个子线程
      技术分享
       1 - (void)viewDidLoad {
       2 #pragma mark - NSThread手动开辟子线程
       3     // 参数1: target
       4     // 参数2: 方法
       5     // 参数3: 要传的参数
       6     NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(sayHello:) object:@"说你好!"];
       7 #pragma mark - 启动子线程
       8     [thread start];
       9     
      10     // 判断一个线程是否正在执行
      11     NSLog(@"是否正在执行%d",[thread isExecuting]);
      12     // 判断一个线程是否完成了任务(是否执行完毕)
      13     NSLog(@"是否执行完毕%d", [thread isFinished]);
      14     
      15 #pragma mark - 释放子线程的两种方式
      16     // 第一种:取消线程,不是真正的取消,而是给子线程发送了一个“取消”消息,标记为canceled
      17     // 判断线城是否被标记为canceled
      18     //    NSLog(@"%d", [thread isCancelled]);
      19     //    [thread cancel];
      20     //    // 第二种:直接将线程退出
      21     //    [NSThread exit];
      22 
      23 }
      24 
      25 - (void)sayHello:(NSString *)hello {
      26     NSLog(@"%@", hello);
      27     // 打印当前线程
      28     NSLog(@"当前线程:%@", [NSThread currentThread]);
      29     NSLog(@"主线程:%@", [NSThread mainThread]);
      30 }
      Thread手动开辟一个子线程

    开辟子线程成功

    技术分享
    • 第二种:

      技术分享
       1 - (void)viewDidLoad {
       2 #pragma mark - NSThread自动开辟子线程
       3     // 线程自动开启,不需要创建NSThread的对象
       4     // 与手动开启线程的方法相比,target 和 selector 两个参数顺序颠倒
       5         [NSThread detachNewThreadSelector:@selector(sayHello:) toTarget:self withObject:@"说你好!"];
       6 }
       7 
       8 - (void)sayHello:(NSString *)hello {
       9     NSLog(@"%@", hello);
      10     // 打印当前线程
      11     NSLog(@"当前线程:%@", [NSThread currentThread]);
      12     NSLog(@"主线程:%@", [NSThread mainThread]);
      13 }
      Thread自动开辟子线程
  • 开辟子线程成功
    技术分享
  • 其他方法
    技术分享
  • 注意:
    • 每个线程都维护着与自己对应的NSAutoreleasePool对象,将其放在线程栈的栈顶。当线程结束时,会清空自动释放池。
    • 为保证对象的及时释放,在多线程方法中需要添加自动释放池。
    • 在应用程序打开的时候,系统会自动为主线程创建一个自动释放池。
    • 我们手动创建的子线程需要我们手动添加自动释放池。

四、NSObject实现异步后台执行

 技术分享

技术分享
 1 - (void)viewDidLoad {
 2 #pragma mark - NSObject开辟子线程
 3     /**
 4      *  开启线程的方式之二:NSObject
 5      */
 6     // 使用performSelectorInBackground开辟子线程
 7     // 参数1:方法
 8     // 参数2:要传的参数
 9     [self performSelectorInBackground:@selector(sayHello:) withObject:@"说你好!"];
10     self.view.backgroundColor = [UIColor yellowColor];
11 
12 }
13 
14 - (void)sayHello:(NSString *)hello {
15     NSLog(@"%@", hello);
16     // 打印当前线程
17     NSLog(@"当前线程:%@", [NSThread currentThread]);
18     NSLog(@"主线程:%@", [NSThread mainThread]);
19     
20     // 回到主线程修改view的背景颜色
21     // 参数1:方法
22     // 参数2:要传的参数
23     // 参数3:是否等待子线程完成后再进入主线程
24     [self performSelectorOnMainThread:@selector(changeColor) withObject:nil waitUntilDone:NO];
25 }
26 
27 - (void)changeColor {
28     self.view.backgroundColor = [UIColor cyanColor];
29     // 打印当前线程
30     NSLog(@"==当前线程:%@", [NSThread currentThread]);
31     NSLog(@"==主线程:%@", [NSThread mainThread]);
32 }
NSObject异步后台执行,开辟子线程

开辟子线程成功

技术分享

技术分享

NSThread和NSObject结合使用:同步请求延迟加载图片

技术分享
 1 @interface ViewController ()
 2 /// 声明一个ImageView用来显示图片
 3 @property (nonatomic, strong) UIImageView *showImageView;
 4 /// 声明一个NSData类型的数据用于接收图片的数据
 5 @property (nonatomic, strong) NSData *imageData;
 6 @end
 7 
 8 @implementation ViewController
 9 
10 - (void)viewDidLoad {
11     [super viewDidLoad];
12     // 创建imageView
13     _showImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 100, 414, 300)];
14     _showImageView.backgroundColor = [UIColor greenColor];
15     [self.view addSubview:_showImageView];
16     
17     
18 }
19 
20 #pragma mark - 触发子线程去加载数据
21 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
22     [self performSelectorInBackground:@selector(loadImageData) withObject:nil];
23 }
24 
25 // 加载图片数据
26 - (void)loadImageData {
27     NSLog(@"当前线程:%@", [NSThread currentThread]);
28     NSLog(@"主线程:%@", [NSThread mainThread]);
29     NSURL *url = [NSURL URLWithString:@"http://img4q.duitang.com/uploads/item/201506/13/20150613185209_nHy5E.jpeg"];
30     NSURLRequest *request = [NSURLRequest requestWithURL:url];
31     self.imageData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
32     // 创建主线程用于刷新UI
33     [self performSelectorOnMainThread:@selector(renovateUI) withObject:nil waitUntilDone:YES];
34  
35 }
36 
37 // 主线程刷新UI
38 - (void)renovateUI {
39     // 安全判断
40     if ([NSThread mainThread]) {
41         _showImageView.image = [UIImage imageWithData:self.imageData];
42     }
43 }
44 @end
同步请求实现延迟加载图片

 

点击触发事件,开辟子线程成功,在主线程中刷新UI,图片显示成功

技术分享

技术分享

五、NSOperation和NSOperationQueue

  • NSOperation
    • NSOperation类,在MVC中属于M,是用来封装单个任务相关的代码和数据的抽象类
    • 因为它是抽象的,不能够直接使用这个类,而是使用子类( NSInvocationOperation或NSBlockOperation )来执行实际任务。
    • NSOperation(含子类),只是一个操作,本身无主线程、子线程之分,可在任意线程中使用。通常与NSOperationQueue结合使用。
  • NSInvocationOperation
    • NSInvocationOperation是NSOperation的子类
    • 封装了执行操作的target和要执行的action。
技术分享
 1 - (void)viewDidLoad {
 2     /**
 3      *  NSOperation不能直接进行多线程的创建,需要借助:NSOperationQueue。
 4      NSOperation的子类本身和多线程没有任何关系,它只是封装了一定的代码段和数据去实现一个功能。
 5      在单独使用NSOperation的子类去创建线程的时候,实际上线程没有真正被创建。
 6      */
 7     // 使用NSOperation的第一个子类去创建子线程 -- NSInvocationOperation
 8     NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(sayHello:) object:@"??"];
 9     // 在单独使用NSOperation的子类去创建线程的时候,一定要启动
10     [operation start];
11     
12 }
13 
14 - (void)sayHello:(NSString *)str {
15     NSLog(@"%@", str);
16     // 此时发现两个线程地址相同,也就是当前线程就是主线程,子线程没有创建成功
17     //
18     NSLog(@"%@", [NSThread currentThread]);
19     NSLog(@"%@", [NSThread mainThread]);
20 }
NSInvocationOperation

没有开辟子线程

技术分享

  • NSBlockOperation
    • NSBlockOperation是NSOperation的子类
    • 封装了需要执行的代码块
技术分享
 1 - (void)viewDidLoad {
 2     /**
 3      *  NSOperation不能直接进行多线程的创建,需要借助:NSOperationQueue。
 4      NSOperation的子类本身和多线程没有任何关系,它只是封装了一定的代码段和数据去实现一个功能。
 5      在单独使用NSOperation的子类去创建线程的时候,实际上线程没有真正被创建。
 6      */
 7     // 使用NSOperation的第二个子类去创建子线程 -- NSBlockOperation
 8     NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
 9         NSLog(@"我是Block");
10         // 此时发现两个线程地址相同,也就是当前线程就是主线程,子线程没有创建成功
11         //
12         NSLog(@"++++++%@", [NSThread currentThread]);
13         NSLog(@"++++++%@", [NSThread mainThread]);
14     }];
15     // 在单独使用NSOperation的子类去创建线程的时候,一定要启动
16     [blockOperation start];
17 }
NSBlockOperation

没有开辟子线程

技术分享

  • NSOperationQueue
    • NSOperationQueue是操作队列,他用来管理一组Operation对象的执行,会根据需要自动为Operation开辟合适数量的线程,以完成任务的并行执行
    • 其中NSOperation可以调节它在队列中的优先级(使用addDependency:设置依赖关系)
    • 当最大并发数设置为1的时候,能实现线程同步(串行执行)

  注意:一旦将创建完成的线程放到队列中,就不能再start启动,否则程序会crush

技术分享
 1 - (void)viewDidLoad {
 2     [super viewDidLoad];
 3     /**
 4      *  NSOperation不能直接进行多线程的创建,需要借助:NSOperationQueue。
 5      NSOperation的子类本身和多线程没有任何关系,它只是封装了一定的代码段和数据去实现一个功能。
 6      在单独使用NSOperation的子类去创建线程的时候,实际上线程没有真正被创建。
 7      */
 8     // 创建操作队列
 9     NSOperationQueue *queue = [[NSOperationQueue alloc] init];
10     // 最大并发数量
11     // 当值设置为1的时候,可以叫做串行:即顺序执行
12     // 当值设置大于1的时候,叫做并行:多条通道同时进行各自的任务
13     queue.maxConcurrentOperationCount = 2;
14     for (int i = 0; i < 10; i++) {
15         NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
16             NSLog(@"%@, %@, %d", [NSThread currentThread], [NSThread mainThread], i);
17         }];
18 #warning - 注意:一旦将创建完成的线程放到队列中,就不能再start启动,否则程序会crush
19         [queue addOperation:blockOperation];
20     }
21 }
NSOperationQueue

 

开辟子线程成功

技术分享

六、GCD(Grand Central Dispatch)使用

    • GCD简介
      • Grand Central Dispatch (GCD)是Apple开发的一种多核编程技术。主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。
      • GCD纯C代码封装,提供函数实现多线程开发,性能更高,功能也更加强大。
      • 它首次发布在Mac OS X 10.6 ,iOS 4及以上也可用
    • GCD的优势
      • GCD是苹果公司为多核的并行运算提出的解决方案
      • GCD会自动利用更多的CPU内核(比如双核、四核)
      • GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
      • 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码
    • GCD核心概念
      • 任务:具有一定功能的代码段。一般是一个block或者函数
      • 分发队列:GCD以队列的方式进行工作,FIFO
      • GCD会根据分发队列的类型,创建合适数量的线程执行队列中的任务
    • GCD中两种队列
      • SerialQueue:一次只执行一个任务。Serial queue通常用于同步访问特定的资源或数据。当你创建多个Serial queue时,虽然它们各自是同步执行的,但Serial queue与Serial queue之间是并发执行的。SerialQueue能实现线程同步
      • Concurrent:可以并发地执行多个任务,但是遵守FIFO
    • 创建串行队列
      • 使用主队列(跟主线程相关联的队列)
        主队列是GCD自带的一种特殊的串行队列,放在主队列中的任务,都会放到主线程中执行

        技术分享
         1 - (void)viewDidLoad {
         2     [super viewDidLoad];
         3 #pragma mark - 使用GCD去创建串行队列
         4     // 第一种:系统提供的创建串行队列的方法
         5     // 使用主队列(跟主线程相关联的队列)
         6     // 主队列是GCD自带的一种特殊的串行队列,放在主队列中的任务,都会放到主线程中执行
         7     dispatch_queue_t queue = dispatch_get_main_queue();
         8     // 往队列里面添加任务
         9     dispatch_async(queue, ^{
        10         NSLog(@"这是第一个任务,当前线程是:%@, 是否主线程 :%d ", [NSThread currentThread],  [[NSThread currentThread] isMainThread]);
        11     });
        12 
        13 }
        使用主队列

         所有任务都在主线程中进行

        技术分享
    • 创建并行队列
      • GCD默认已经提供了全局的并发队列,供整个应用使用,不需要手动创建
      • 四个优先级
        • DISPATCH_QUEUE_PRIORITY_DEFAUL, 默认(中) 
        • DISPATCH_QUEUE_PRIORITY_HIGH,  高
        • DISPATCH_QUEUE_PRIORITY_LOW,  低
        • DISPATCH_QUEUE_PRIORITY_BACKGROUND  后台

          技术分享
           1 - (void)viewDidLoad {
           2     [super viewDidLoad];
           3 #pragma mark - 使用GCD创建并行队列
           4     // 第一种方式
           5     // global queue GCD默认已经提供了全局的并发队列,供整个应用使用,不需要手动创建
           6     // 优先级
           7     // DISPATCH_QUEUE_PRIORITY_DEFAUL, 默认(中)
           8     // DISPATCH_QUEUE_PRIORITY_HIGH,  高
           9     // DISPATCH_QUEUE_PRIORITY_LOW,  低
          10     // DISPATCH_QUEUE_PRIORITY_BACKGROUND  后台
          11     // 第一个参数就是队列的优先级,第二个参数是苹果预留的参数为了以后去使用,目前没有用到,填写0
          12     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
          13 #pragma mark - 向队列中添加任务
          14     dispatch_async(queue, ^{
          15         NSLog(@"current1%@, main1%@", [NSThread currentThread], [NSThread mainThread]);
          16     });
          17     dispatch_async(queue, ^{
          18         NSLog(@"current2%@, main2%@", [NSThread currentThread], [NSThread mainThread]);
          19     });
          20     dispatch_async(queue, ^{
          21         NSLog(@"current3%@, main3%@", [NSThread currentThread], [NSThread mainThread]);
          22     });
          23     dispatch_async(queue, ^{
          24         NSLog(@"current4%@, main4%@", [NSThread currentThread], [NSThread mainThread]);
          25     });
          26     dispatch_async(queue, ^{
          27         NSLog(@"current5%@, main5%@", [NSThread currentThread], [NSThread mainThread]);
          28     });
          29 }
          全局并发队列

           开辟子线程成功,子线程并发执行
          技术分享

    • 手动创建串行/并行队列
      • 自己创建的队列,第一个参数是队列的名字,第二个参数队列的类型(串行队列、并行队列),这种方式创建的队列,它会自己去开辟一个子线程去完成队列里面的任务

        • 串行队列
          技术分享
           1 - (void)viewDidLoad {
           2     [super viewDidLoad];
           3 #pragma mark - 第二种创建串行队列的方式
           4     // 参数1:系统的保留字段,队列名
           5     // 参数2:系统提供的宏
           6     // 两个参数的位置可以互换,没有严格限制
           7     dispatch_queue_t queue = dispatch_queue_create(0, DISPATCH_QUEUE_SERIAL);
           8     
           9 #pragma mark - 向队列中添加任务
          10     dispatch_async(queue, ^{
          11         NSLog(@"current1%@, main1%@", [NSThread currentThread], [NSThread mainThread]);
          12     });
          13     dispatch_async(queue, ^{
          14         NSLog(@"current2%@, main2%@", [NSThread currentThread], [NSThread mainThread]);
          15     });
          16     dispatch_async(queue, ^{
          17         NSLog(@"current3%@, main3%@", [NSThread currentThread], [NSThread mainThread]);
          18     });
          19     dispatch_async(queue, ^{
          20         NSLog(@"current4%@, main4%@", [NSThread currentThread], [NSThread mainThread]);
          21     });
          22     dispatch_async(queue, ^{
          23         NSLog(@"current5%@, main5%@", [NSThread currentThread], [NSThread mainThread]);
          24     });
          25 }
          手动创建串行队列

           开辟了一个子线程,任务顺序执行


          技术分享

        • 并行队列
          技术分享
           1 - (void)viewDidLoad {
           2     [super viewDidLoad];
           3 #pragma mark - 第二种创建并行队列的方式
           4     // 参数1:系统保留字段,队列名
           5     // 参数2:系统提供的宏
           6     dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
           7     
           8 #pragma mark - 向队列中添加任务
           9     dispatch_async(queue, ^{
          10         NSLog(@"current1%@, main1%@", [NSThread currentThread], [NSThread mainThread]);
          11     });
          12     dispatch_async(queue, ^{
          13         NSLog(@"current2%@, main2%@", [NSThread currentThread], [NSThread mainThread]);
          14     });
          15     dispatch_async(queue, ^{
          16         NSLog(@"current3%@, main3%@", [NSThread currentThread], [NSThread mainThread]);
          17     });
          18     dispatch_async(queue, ^{
          19         NSLog(@"current4%@, main4%@", [NSThread currentThread], [NSThread mainThread]);
          20     });
          21     dispatch_async(queue, ^{
          22         NSLog(@"current5%@, main5%@", [NSThread currentThread], [NSThread mainThread]);
          23     });
          24 }
          手动创建并行队列

           开辟了多个子线程,任务无序执行

          技术分享
    • 系统保留字段(队列名)在断点调试时可以用来辨别当前执行任务的是哪个队列
      技术分享
    • GCD功能
      • dispatch_async()   异步函数,往队列中添加任务,任务会排队执行
      • dispatch_after()     往队列中添加任务,任务不但会排队,还会在延迟的时间点执行
      • dispatch_apply()    往队列中添加任务,任务会重复执行n次
    • 延迟  dispatch_after()
      技术分享
       1 - (void)viewDidLoad {
       2     [super viewDidLoad];
       3 #pragma mark - 延迟
       4     // 参数1:时间
       5     // 参数1.1:DISPATCH_TIME_NOW   表示当前
       6     //         DISPATCH_TIME_FOREVER  表示遥远的未来;
       7     // 参数1.2:距离参数1有多长时间
       8     // 参数2:队列
       9     // 参数3:block
      10  
      11     dispatch_async(dispatch_get_main_queue(), ^{
      12            NSLog(@"视图加载完成");
      13     });
      14     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)3 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
      15         NSLog(@"3.0秒之后");
      16     });
      17     
      18 }
      延迟 dispatch_after

       延迟成功

      技术分享
  • dispatch_apply()  重复添加任务
    技术分享
     1 - (void)viewDidLoad {
     2     [super viewDidLoad];
     3 #pragma mark - 重复向一个队列中添加多个任务
     4     dispatch_queue_t queue = dispatch_queue_create(0, DISPATCH_QUEUE_CONCURRENT);
     5     // 重复向队列中添加任务
     6     // block要随便添加一个参数名
     7     dispatch_apply(10, queue, ^(size_t index) {
     8         NSLog(@"%ld, %@", index, [NSThread currentThread]);
     9     });
    10 }
    重复添加任务 dispatch_apply()

    添加成功

    技术分享

  • 队列组
    • dispatch_group_async()   将任务添加到队列中,并添加分组标记
    • dispatch_group_notify()   监听,当某个分组的所有任务执行完之后,此任务才会执行

  要实现多个任务并行执行,所有任务执行完毕之后执行某个操作时,建议用队列组

技术分享
 1 - (void)viewDidLoad {
 2     [super viewDidLoad];
 3 #pragma mark - 分组
 4     // 创建一个分组
 5     dispatch_group_t group = dispatch_group_create();
 6     // 创建一个并行队列
 7     dispatch_queue_t queue = dispatch_queue_create(0, DISPATCH_QUEUE_CONCURRENT);
 8     // 创建任务
 9     dispatch_group_async(group, queue, ^{
10         NSLog(@"任务1");
11     });
12     dispatch_group_async(group, queue, ^{
13         NSLog(@"任务2");
14     });
15     dispatch_group_async(group, queue, ^{
16         NSLog(@"任务3");
17     });
18     // 用于监听所有任务的执行情况
19     // 代码必须不能写在第一个任务之前,否则不能监听全部任务,
20     dispatch_group_notify(group, queue, ^{
21         NSLog(@"完事儿了");
22     });
23     
24 }
队列组

   监听成功
  技术分享

  • dispatch_barrier_async()    将任务添加到队列中,此任务执行的时候,其他任务停止执行
    例如数据库的读取可以并行执行,但数据库的写入需要串行执行,此时将写入数据库的任务通过dispatch_barrier_async() 函数添加到队列中,实现串行执行。
  • dispatch_once()   任务添加到队列中,但任务在程序运行过程中,只执行一次
    该函数接收一个dispatch_once用于检查该代码块是否已经被调度的谓词(是一个长整型,实际上作为BOOL使用)。它还接收一个希望在应用的生命周期内仅被调度一次的代码块。
    dispatch_once不仅意味着代码仅会被运行一次,而且还是线程安全的,这就意味着你不需要使用诸如@synchronized之类的来防止使用多个线程或者队列时不同步的问题。
    此函数可以用来完善单例模式中的单例方法
    技术分享
     1 #import "MyHandle.h"
     2 
     3 
     4 static MyHandle *myHandle = nil;
     5 @implementation MyHandle
     6 
     7 #pragma mark - 传统单例写法
     8 // 此时如果多个任务并发执行,就不会只初始化一次
     9 //+ (instancetype)sharedMyHandle {
    10 //    if (myHandle == nil) {
    11 //        myHandle = [[MyHandle alloc] init];
    12 //    }
    13 //    return myHandle;
    14 //}
    15 
    16 #pragma mark - 完整的单例写法
    17 + (instancetype)sharedMyHandle {
    18     // 保证在GCD多线程情况下只执行一次
    19     static dispatch_once_t onceToken;
    20     dispatch_once(&onceToken, ^{
    21         myHandle = [[MyHandle alloc] init];
    22     });
    23     return myHandle;
    24 }
    25 @end
    单例方法
  • dispatch_sync()   同步函数,将任务添加到队列中,block不执行完,下面代码不会执行,与dispatch_async()异步函数对应记忆
    async 不等 block 体执行完,就去执行下面的代码
    sync会等待 block 体执行完成之后,才会去执行 block 体外面的代码
  • dispatch_async_f () 将任务添加到队列中,任务是函数非block

    技术分享
     1 // 函数
     2 void function(void * str){
     3     NSLog(@"这是一个函数,%s",str);
     4 }
     5 
     6 - (void)viewDidLoad {
     7     [super viewDidLoad];
     8     // 第一个参数:队列
     9     // 第二个参数:函数参数的内容,注意不要写成OC中的字符串
    10     // 第三个参数:函数
    11     dispatch_queue_t queue  = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    12     dispatch_async_f(queue, "passValue", function);
    13     
    14 }
    dispatch_async_f()

七、线程间通信

  • 线程间通信分为两种:
    • 主线程进入子线程(前面的方法都可以)
    • 子线程回到主线程
  • 返回主线程的方法
    技术分享
    注:dispatch_get_main_queue()在前面介绍GCD创建串行队列--主队列时有提到,此处不再赘述
      - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait 在前面NSThread和NSObject结合使用:同步请求延迟加载图片时有提到,此处不再赘述

八、线程互斥

多线程并行编程中,线程间同步与互斥是一个很有技巧的也很容易出错的地方。

线程间互斥应对的是这种场景:
多个线程操作同一个资源(即某个对象),需要保证线程在对资源的状态(即对象的成员变量)进行一些非原子性操作后,状态仍然正确。

典型的例子是“售票厅售票应用”。售票厅剩余20张票,10个窗口去卖这些票。这10个窗口,就是10条线程,售票厅就是他们共同操作的资源,其中剩余的20张票就是这个资源的一个状态。线程买票的过程就是去递减这个剩余数量的过程。

我们看看会发生什么问题

技术分享
 1 - (void)viewDidLoad {
 2     [super viewDidLoad];
 3     // 模拟买票系统
 4     // 一共20张票,10个窗口卖
 5     __block NSInteger count = 20;
 6     dispatch_queue_t ticketQueue = dispatch_queue_create("sell ticket", DISPATCH_QUEUE_CONCURRENT);
 7     for (int i = 0; i < 10; i ++) {
 8         dispatch_async(ticketQueue, ^{
 9             //这里相当于每个窗口卖2张票
10             for (int i = 0; i < 2; i ++) {
11                 NSLog(@"买到了第%ld张票",count);
12                 count--;
13             }
14         });
15     }
16 }
线程间互斥

 

不同“售票窗口”贩卖了同一张“票”
技术分享

技术分享

  • 线程互斥解决方案
    • 方法一 @synchronized 自动对参数对象加锁,保证临界区内的代码线程安全
    • 方法二 NSLock
    • 方法三 NSConditionLock 条件锁 可以设置条件
    • 方法四 NSRecursiveLock 递归锁 多次调用不会阻塞已获取该锁的线程
  • @synchronized, 代表这个方法加锁, 相当于不管哪一个线程(例如线程A),运行到这个方法时,都要检查有没有其它线程例如B正在用这个方法,有的话要等正在使用synchronized方法 的线程B运行完这个方法后再运行此线程A,没有的话,直接运行。它包括两种用法:synchronized 方法和 synchronized 块。
    @synchronized 方法控制对类(一般在IOS中用在单例中)的访问:每个类实例对应一把锁,每个 synchronized 方法都必须获得调用该方法锁方能执行,否则所属就会发生线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。这种机制确保了同一时刻对于每一个类,至多只有一个处于可执行状态,从而有效避免了类成员变量的访问冲突(只要所有可能访问类的方法均被声明为 synchronized)
    技术分享
     1 - (void)viewDidLoad {
     2     [super viewDidLoad];
     3     // 模拟买票系统
     4     // 一共20张票,10个窗口卖
     5     __block NSInteger count = 20;
     6     __weak typeof(self) weakSelf = self;
     7     dispatch_queue_t ticketQueue = dispatch_queue_create("sell ticket", DISPATCH_QUEUE_CONCURRENT);
     8     for (int i = 0; i < 10; i ++) {
     9         dispatch_async(ticketQueue, ^{
    10             // 给买票操作加锁,保证代码块只有一个线程访问
    11             @synchronized(weakSelf) {
    12                 //这里相当于每个窗口卖2张票
    13                 for (int i = 0; i < 2; i ++) {
    14                     NSLog(@"买到了第%ld张票",count);
    15                     count--;
    16                 }
    17             }
    18        
    19         });
    20     }
    21 }
    @synchronized
    解决了线程互斥问题
    技术分享

  • NSLock
    技术分享
     1 - (void)viewDidLoad {
     2     [super viewDidLoad];
     3     // 模拟买票系统
     4     // 一共20张票,10个窗口卖
     5     __block NSInteger count = 20;
     6     
     7     // 创建线程锁
     8     NSLock *lock = [[NSLock alloc]init];
     9     dispatch_queue_t ticketQueue = dispatch_queue_create("sell ticket", DISPATCH_QUEUE_CONCURRENT);
    10     for (int i = 0; i < 10; i ++) {
    11         dispatch_async(ticketQueue, ^{
    12             // 给买票操作加锁,保证代码块只有一个线程访问
    13             // 加锁,不会出现多个窗口同时卖一张票的情况
    14             [lock lock];
    15             //这里相当于每个窗口卖2张票
    16             for (int i = 0; i < 2; i ++) {
    17                 NSLog(@"买到了第%ld张票",count);
    18                 count--;
    19             }
    20             // 解锁
    21             [lock unlock];
    22         });
    23     }
    24 }
    NSLock

总结:

  • NSThread、NSOperationQueue、NSObject、GCD都能实现多线程
  • GCD是苹果提供的性能更高的方式
  • 使用多线程开发的优点 :资源利用率更好; 程序设计在某些情况下更简单;程序响应更快;
  • 多线程的缺点:多线程尽管提升了性能,但是存在一些访问限制,比如线程同步、线程互斥等;多线程在使用的时候,最终是要回到主线程刷新UI的,如果开辟过多的多线程,会造成CPU的消耗。
  • 同步和异步的区别
    • 同步:在当前线程中执行任务,不具备开启新线程的能力
    • 异步:在新的线程中执行任务,具备开启新线程的能力
  • 串行和并行的区别
    • 并发:多个任务并发(同时)执行
    • 串行:一个任务执行完毕后,再执行下一个任务
  • 各种队列的执行效果
    • 同步函数不具备开启线程的能力,无论是什么队列都不会开启线程;异步函数具备开启线程的能力,开启几条线程由队列决定(串行队列只会开启一条新的线程,并发队列会开启多条线程)。
      • 同步函数
        (1)并发队列:不会开线程
        (2)串行队列:不会开线程
      • 异步函数
        (1)并发队列:能开启N条线程
        (2)串行队列:开启1条线程

   技术分享

UI进阶 多线程

标签:

原文地址:http://www.cnblogs.com/fearlessyyp/p/5503392.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!