zoukankan      html  css  js  c++  java
  • iOS笔记058

    IOS开发中多线程

    • 主线程
      • 一个iOS程序运行后,默认会开启1条线程,称为“主线程”或“UI线程”
      • 作用
        • 显示和刷新界面
        • 处理UI事件(点击、滚动、拖拽等)
      • 注意事项
        • 耗时操作不能放在主线程中没,比如资源记载,文件下载,等比较耗时间的任务,不然会卡死界面
        • 可以将耗时操作放到子线程中,将操作结果返回给主线程
    • IOS中得几种多线程实现方案
      • pThread

        • 一套通用的多线程API
        • 适用于UnixLinuxWindows等系统
        • 跨平台可移植
        • 使用难度大
        • C语言,手动管理线程生命周期
      • NSThread

        • 使用更加面向对象
          简单易用,可直接操作线程对象
        • OC,手动管理线程生命周期
      • GCD

        • 旨在替代NSThread等线程技术,充分利用设备的多核
        • C,自动管理
      • NSOperation

        • 基于GCD(底层是GCD)
        • 比GCD多了一些更简单实用的功能
        • 使用更加面向对象
        • OC,自动管理
      • 其中GCD和NSOperation比较常用

    pThread 的使用

        // 多线程 pThread
        pthread_t thread;
        // 开启线程
        pthread_create(&thread, NULL, run, NULL);
        // 多线程 pThread
        pthread_t thread1;
        // 开启线程
        pthread_create(&thread1, NULL, run, NULL);
    
        void* run(void *para)
    	{
        	for (int i = 0 ;i < 10000; i ++) {
    
            NSLog(@"run-%d----%@",i,[NSThread currentThread]);
        	}
       		return NULL;
    	}
    

    NSThread 使用

    • 这种方式创建的线程,在执行完线程函数里的方法后就由系统销毁了
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
        // NSThread
        // 创建NSThread
        NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(threadRun:) object:@"鸟"];
        NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(threadRun:) object:@"蛋"];
        NSThread *thread3 = [[NSThread alloc] initWithTarget:self selector:@selector(threadRun:) object:@"碎了"];
    
        thread1.name = @"张三";
        thread2.name = @"李四";
        thread3.name = @"鸟";
        // 开启线程
        [thread1 start];
        [thread2 start];
        [thread3 start];
    
        // 这种方法创建的线程无法获得线程对象,由系统管理
        [NSThread detachNewThreadSelector:@selector(thread1:) toTarget:self withObject:@"狗蛋"];
    
    }
    - (void)threadRun:(id)obj
    {
        for (int i = 0 ;i < 100; i ++) {
    
            NSLog(@"run-%d-%@---%@",i,obj,[NSThread currentThread]);
        }
    }
    
    

    创建子线程的其他方法

    • performSelectorInBackground
    • performSelector
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
        // 创建后台线程
        [self performSelectorInBackground:@selector(thread1:) withObject:@"控制器"];
        // 创建任务并添加到目标线程,可以是任意线程,子线程或者主线程
        [self performSelector:@selector(thread2:) onThread:[NSThread mainThread] withObject:@"Main" waitUntilDone:NO];
    
    }
    
    - (void)thread1:(NSString *)str
    {
        for (int i = 0 ;i < 100; i ++) {
    
            NSLog(@"run-%@---%@",str,[NSThread currentThread]);
        }
    }
    
    - (void)thread2:(NSString *)str
    {
        for (int i = 0 ;i < 100; i ++) {
    
            NSLog(@"run-%@---%@",str,[NSThread currentThread]);
        }
    }
    

    线程的睡眠(阻塞)和退出

    - (void)thread1:(NSString *)str
    {
        for (int i = 0 ;i < 100; i ++) {
    
            NSLog(@"run-%d-%@---%@",i,str,[NSThread currentThread]);
            if (i == 19) {
                sleep(2); // 当前线程睡眠2s
            }
            else if( i == 33 )
            {
                [NSThread sleepForTimeInterval:2]; // 睡眠2s
            }
            else if(i == 40)
            {
                // 获取当前时间延后2s后的时间
                NSDate *endDate = [NSDate dateWithTimeIntervalSinceNow:2];
    
                [NSThread sleepUntilDate:endDate]; // 根据日期睡眠线程
            }
            else if(i == 55)
            {
                NSLog(@"结束线程");
                [NSThread exit]; // 结束线程
            }
        }
    }
    
    

    线程同步 - 互斥锁

    • 开启三个线程对同一个数据进行读写,就会出现问题,必须对读写数据进行处理,例如加锁
    • 指令@synchronized()通过对一段代码的使用进行加锁。其他试图执行该段代码的线程都会被阻塞,直到加锁线程退出执行该段被保护的代码段,也就是说@synchronized()代码块中的最后一条语句已经被执行完毕的时候。
    • @synchronized() 参数传入一个OC对象即可,self也可以
    // 方法中对资源数的访问要加锁
    - (void)saleTickets:(NSString *)str
    {
        // 三个消费者线程
        while(1)
        {
            // 设置互斥锁
            @synchronized (self)
            {
                // 获取资源数
                NSInteger count = self.resourceCount;
                if (count > 0) {
                    // 资源数减1
                    count -- ;
                    // 写入标记数据
                    self.resourceCount = count;
                    NSLog(@"%@消费了一个单位,还剩下%zd",[NSThread currentThread].name,count);
                }
                else
                {
                    NSLog(@"资源全部使用完了");
                    break;
                }
            }
        }
    
    }
    

    GCD

    • GCD - Grand Central Dispatch

    • GCD中任务和队列

      • 任务:执行什么操作
      • 队列:存放任务
    • 使用方式

      • 定制自己的任务,添加到队列即可。线程会有GCD自动创建和销毁。
    • 并发队列+异步任务:创建多个线程,并发执行

     /*
     * 并发队列+异步任务:创建多个线程,并发执行
     */
    - (void)concurrentAndAsync
    {
        // 创建一个并发队列
        // 参数1:标识,一般用公司域名abc.com
        // 参数2:队列类型:串行和并行两种
        dispatch_queue_t queue = dispatch_queue_create("song.com", DISPATCH_QUEUE_CONCURRENT);
        // 异步方式创建一个任务,任务不会立即执行
        dispatch_async(queue, ^{
            NSLog(@"1--%@--",[NSThread currentThread]);
        });
        dispatch_async(queue, ^{
            NSLog(@"2--%@--",[NSThread currentThread]);
        });
        dispatch_async(queue, ^{
            NSLog(@"3--%@--",[NSThread currentThread]);
        });
        dispatch_async(queue, ^{
            NSLog(@"4--%@--",[NSThread currentThread]);
        });
        dispatch_async(queue, ^{
            NSLog(@"5--%@--",[NSThread currentThread]);
        });
        // 先将以上任务全部添加完毕,然后这个函数结束,再开始并发执行线程
        NSLog(@"dispatch_async--end -- %@--",[NSThread currentThread]);
        // 执行结果
        //    dispatch_async--end -- <NSThread: 0x7ff3b1400b80>{number = 1, name = main}--
    //    2--<NSThread: 0x7ff3b140bc40>{number = 2, name = (null)}--
    //    1--<NSThread: 0x7ff3b16672e0>{number = 5, name = (null)}--
    //    3--<NSThread: 0x7ff3b1556b20>{number = 3, name = (null)}--
    //    4--<NSThread: 0x7ff3b165c760>{number = 4, name = (null)}--
    //    5--<NSThread: 0x7ff3b1413aa0>{number = 6, name = (null)}--
    
    }
    
    • 并发队列+同步任务:不会开启新线程,在主线程中同步执行各个子线程,也就是逐一执行,并且是添加过任务后立即执行,之后才能添加下一个任务
     /*
     * 并发队列+同步任务:不会开启新线程,在父线程中同步执行各个子线程,也就是逐一执行,并且是添加过任务后立即执行,之后才能添加下一个任务
     */
    - (void)concurrentAndSync
    {
        // 创建一个并发队列
        // 参数1:标识,一般用公司域名abc.com
        // 参数2:队列类型:串行和并行两种
        dispatch_queue_t queue = dispatch_queue_create("song.com", DISPATCH_QUEUE_CONCURRENT);
        // 同步方式创建任务,任务会立即执行,执行完成后才会继续向下执行
        dispatch_sync(queue, ^{
            NSLog(@"1--%@--",[NSThread currentThread]);
        });
        dispatch_sync(queue, ^{
            NSLog(@"2--%@--",[NSThread currentThread]);
        });
        dispatch_sync(queue, ^{
            NSLog(@"3--%@--",[NSThread currentThread]);
        });
        dispatch_sync(queue, ^{
            NSLog(@"4--%@--",[NSThread currentThread]);
        });
        dispatch_sync(queue, ^{
            NSLog(@"5--%@--",[NSThread currentThread]);
        });
        // 先将以上任务逐个依次执行完毕后才会执行这句话,并且多有任务的执行都在主线程里完成
        NSLog(@"dispatch_sync--end -- %@--",[NSThread currentThread]);
        // 输出结果如下
        // 1--<NSThread: 0x7f9900711c90>{number = 1, name = main}--
        // 2--<NSThread: 0x7f9900711c90>{number = 1, name = main}--
        // 3--<NSThread: 0x7f9900711c90>{number = 1, name = main}--
        // ......
        // dispatch_sync--end -- <NSThread: 0x7f9900711c90>{number = 1, name = main}--
    }
    
    • 串行队列+同步任务:不会开启新线程,在父线程中同步执行各个子线程,也就是逐一执行,并且是添加过任务后立即执行,之后才能添加下一个任务
    /*
     * 串行队列+同步任务:不会开启新线程,在父线程中同步执行各个子线程,也就是逐一执行,并且是添加过任务后立即执行,之后才能添加下一个任务
     */
    - (void)serialAndSync
    {
        // 创建一个串行队列
        // 参数1:标识,一般用公司域名abc.com
        // 参数2:队列类型:串行和并行两种
        dispatch_queue_t queue = dispatch_queue_create("song.com", DISPATCH_QUEUE_SERIAL);
        // 同步方式创建任务,任务会立即执行,执行完成后才会继续向下执行
        dispatch_sync(queue, ^{
            NSLog(@"1--%@--",[NSThread currentThread]);
        });
        dispatch_sync(queue, ^{
            NSLog(@"2--%@--",[NSThread currentThread]);
        });
        dispatch_sync(queue, ^{
            NSLog(@"3--%@--",[NSThread currentThread]);
        });
        dispatch_sync(queue, ^{
            NSLog(@"4--%@--",[NSThread currentThread]);
        });
        dispatch_sync(queue, ^{
            NSLog(@"5--%@--",[NSThread currentThread]);
        });
        // 先将以上任务逐个依次执行完毕后才会执行这句话,并且多有任务的执行都在主线程里完成
        NSLog(@"dispatch_sync--end -- %@--",[NSThread currentThread]);
        // 1--<NSThread: 0x7fdc38f188d0>{number = 1, name = main}--
        // 2--<NSThread: 0x7fdc38f188d0>{number = 1, name = main}--
        // 3--<NSThread: 0x7fdc38f188d0>{number = 1, name = main}--
        // 4--<NSThread: 0x7fdc38f188d0>{number = 1, name = main}--
        // 5--<NSThread: 0x7fdc38f188d0>{number = 1, name = main}--
        // dispatch_sync--end -- <NSThread: 0x7fdc38f188d0>{number = 1, name = main}--
    }
    
    • 串行队列+异步任务:创建新线程,但是只会创建一个新线程,所有的任务都是在这个子线程里执行,执行顺序按照添加任务 的先后顺序,并且不是立即执行,而是等整个方法
    /*
     * 串行队列+异步任务:创建新线程,但是只会创建一个新线程,所有的任务都是在这个子线程里执行,执行顺序按照添加任务 的先后顺序,并且不是立即执行,而是等整个方法
     */
    - (void)serialAndAsync
    {
        // 创建一个串行队列
        // 参数1:标识,一般用公司域名abc.com
        // 参数2:队列类型:串行和并行两种
        dispatch_queue_t queue = dispatch_queue_create("song.com", DISPATCH_QUEUE_SERIAL);
        // 同步方式创建任务,任务会立即执行,执行完成后才会继续向下执行
        dispatch_async(queue, ^{
            NSLog(@"1--%@--",[NSThread currentThread]);
        });
        dispatch_async(queue, ^{
            NSLog(@"2--%@--",[NSThread currentThread]);
        });
        dispatch_async(queue, ^{
            NSLog(@"3--%@--",[NSThread currentThread]);
        });
        dispatch_async(queue, ^{
            NSLog(@"4--%@--",[NSThread currentThread]);
        });
        dispatch_async(queue, ^{
            NSLog(@"5--%@--",[NSThread currentThread]);
        });
        // 先将以上任务逐个依次执行完毕后才会执行这句话,并且多有任务的执行都在主线程里完成
        NSLog(@"dispatch_async--end -- %@--",[NSThread currentThread]);
    
        // dispatch_async--end -- <NSThread: 0x7f97f2611bf0>{number = 1, name = main}--
        // 1--<NSThread: 0x7f97f24461b0>{number = 2, name = (null)}--
        // 2--<NSThread: 0x7f97f24461b0>{number = 2, name = (null)}--
    }
    
    
    • 主队列 是GCD自带的一种特殊的串行队列
      • dispatch_get_main_queue()
    • 全局并发队列
      • dispatch_get_global_queue(优先级,0)
        全局并发队列的特性和手动创建的队列一样
    • 主队列+异步任务:不会创建新线程,所有的任务都是在这个父线程里执行,执行顺序按照添加任务 的先后顺序,并且不是立即执行,而是等整个方法结束后依次执行
    /*
     * 主队列+异步任务:不会创建新线程,所有的任务都是在这个父线程里执行,执行顺序按照添加任务 的先后顺序,并且不是立即执行,而是等整个方法结束后依次执行 */
    - (void)mainAndAsync
    {
        NSLog(@"dispatch_async--begin -- %@--",[NSThread currentThread]);
        // 创建一个串行队列
        // 参数1:标识,一般用公司域名abc.com
        // 参数2:队列类型:串行和并行两种
        dispatch_queue_t queue = dispatch_get_main_queue();;
        // 异步方式创建任务,任务不会立即执行
        dispatch_async(queue, ^{
            NSLog(@"1--%@--",[NSThread currentThread]);
        });
        dispatch_async(queue, ^{
            NSLog(@"2--%@--",[NSThread currentThread]);
        });
        dispatch_async(queue, ^{
            NSLog(@"3--%@--",[NSThread currentThread]);
        });
        dispatch_async(queue, ^{
            NSLog(@"4--%@--",[NSThread currentThread]);
        });
        dispatch_async(queue, ^{
            NSLog(@"5--%@--",[NSThread currentThread]);
        });
        // 先将以上任务逐个依次执行完毕后才会执行这句话,并且多有任务的执行都在主线程里完成
        NSLog(@"dispatch_get_main_queue--end -- %@--",[NSThread currentThread]);
    }
    
    • 主队列+同步任务:这个会产生问题,死锁,添加任务到主队列的任务要求立即执行,但是主队列是串行队列,当前任务要求执行完当前任务在执行新添加的任务。结果就是:两个任务互相等待,产生死锁
    /*
     * 主队列+同步任务:这个会产生问题,死锁,添加任务到主队列的任务要求立即执行,但是主队列是串行队列,当前任务要求执行完当前任务在执行新添加的任务。结果就是:两个任务互相等待,产生死锁
     */
    - (void)mainAndSync
    {
        NSLog(@"dispatch_sync--begin -- %@--",[NSThread currentThread]);
    
        // 创建一个串行队列
        // 参数1:标识,一般用公司域名abc.com
        // 参数2:队列类型:串行和并行两种
        dispatch_queue_t queue = dispatch_get_main_queue();
        // 同步方式创建任务,任务会立即执行,执行完成后才会继续向下执行
        dispatch_sync(queue, ^{
            NSLog(@"1--%@--",[NSThread currentThread]);
        });
        dispatch_sync(queue, ^{
            NSLog(@"2--%@--",[NSThread currentThread]);
        });
        dispatch_sync(queue, ^{
            NSLog(@"3--%@--",[NSThread currentThread]);
        });
        dispatch_sync(queue, ^{
            NSLog(@"4--%@--",[NSThread currentThread]);
        });
        dispatch_sync(queue, ^{
            NSLog(@"5--%@--",[NSThread currentThread]);
        });
        // 先将以上任务逐个依次执行完毕后才会执行这句话,并且多有任务的执行都在主线程里完成
        NSLog(@"dispatch_get_main_queue--end -- %@--",[NSThread currentThread]);
    }
    

    截止执行

    • dispatch_barrier_async :截断线程执行,以上两个任务完成后才会执行这个任务,并在完成barrier后继续执行下面的任务
    • 任务1,2的执行始终在任务3,4之前
    • 只有在并发队列中才有阶段功能

    延时执行

    - (void)delay
    {
        // 延时执行
         NSLog(@"start--time:%@",[NSDate date]);
        // NSObject 方法
        [self performSelector:@selector(run) withObject:nil afterDelay:2];
        // NSTimer
        [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(run) userInfo:nil repeats:NO];
        // GCD
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
              NSLog(@"run--time:%@",[NSDate date]);
        });
    }
    

    一次性代码

    • 一次性代码内部默认就是线程安全的,不会出现多个线程同时访问内部代码
    - (void)excuteOnce
    {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            NSLog(@"只执行一次---%@",[NSThread currentThread]);
        });
        NSLog(@"excuteOnce--%@",[NSThread currentThread]);
    }
    

    快速迭代

    // 快速迭代,顺序不确定
    - (void)apply
    {
        dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index) {
             NSLog(@"dispatch_apply--%ld--%@",index,[NSThread currentThread]);
        });
    }
    

    队列组

    /*
     * 队列组:先执行async里的任务,最后执行notify任务
     */
    - (void)groupAndAsync
    {
        dispatch_group_t group = dispatch_group_create();
    
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        // 先执行3个耗时操作
        dispatch_group_async(group, queue, ^{
            for (int i = 0 ; i < 1000; i ++) {
                NSLog(@"1--%@--",[NSThread currentThread]);
            }
        });
    
        dispatch_group_async(group, queue, ^{
            for (int i = 0 ; i < 1000; i ++) {
                NSLog(@"2--%@--",[NSThread currentThread]);
            }
        });
        dispatch_group_async(group, queue, ^{
            for (int i = 0 ; i < 1000; i ++) {
                NSLog(@"3--%@--",[NSThread currentThread]);
            }
        });
        // 等到以上任务完成后才会执行这个notify任务
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            NSLog(@"main--%@--",[NSThread currentThread]);
        });
    }
    

    NSOperation

    • NSOperation是一抽象类,只能使用它的子类NSBlockOperation和NSInvocationOperation,或者自定义NSOperation。

    1、NSInvocationOperation

    • 使用方法initWithTarget:进行初始化。
    • 默认情况下,调用了start方法后并不会开一条新线程去执行操作,而是在当前线程同步执行操作。
    • 只有将NSOperation放到一个NSOperationQueue中,才会异步执行操作。
        NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
        [op start];
    

    2、NSBlockOperation

    • 使用blockOperationWithBlock添加任务,或者addExecutionBlock添加多个任务。
    • 只要添加的任务数量大于1就会自动创建多个线程,异步执行。
     	NSBlockOperation *ope1 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"0---%@",[NSThread currentThread]);
        }];
    
        [ope1 addExecutionBlock:^{
            NSLog(@"1---%@",[NSThread currentThread]);
    
        }];
        [ope1 addExecutionBlock:^{
            NSLog(@"2---%@",[NSThread currentThread]);
    
        }];
        [ope1 addExecutionBlock:^{
            NSLog(@"3---%@",[NSThread currentThread]);
    
        }];
        [ope1 start];
    

    3、NSOperationQueue

    • NSOperation可以调用start方法来执行任务,但默认是同步执行的。
    • 如果将NSOperation添加到NSOperationQueue(操作队列)中,系统会自动异步执行NSOperation中的操作。
        // 添加到队列的任务会自动并发执行
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
        NSBlockOperation *ope1 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"1---%@",[NSThread currentThread]);
        }];
        NSBlockOperation *ope2 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"2---%@",[NSThread currentThread]);
        }];
    
        NSBlockOperation *ope3 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"3---%@",[NSThread currentThread]);
        }];
    
        // 添加到队列会自动执行,不需要手动调用start方法
        [queue addOperation:ope1];
        [queue addOperation:ope2];
        [queue addOperation:ope3];
        [queue addOperationWithBlock:^{
            NSLog(@"4---%@",[NSThread currentThread]);
        }];
    

    4、最大并发数

    • 设置队列的最大并发数
    • maxConcurrentOperationCount = 2;,同一时刻只会有两条线程在执行
    • [queue setMaxConcurrentOperationCount:2]; // 这样设置
    • 如果设置为1,就变成串行队列了
      // 最大并发数,同一时刻只会有两条线程在执行
        queue.maxConcurrentOperationCount = 2;
    

    5、任务依赖 addDependency

    • 多个线程之间可以建立依赖关系
        // 添加依赖:任务1和2的执行在3之前
        [ope3 addDependency:ope1];
        [ope3 addDependency:ope2];
    

    6、线程通信

    • 获取主队列mainQueue,将任务添加到主队列
    • 如果定义的时全局的队列,可以在其他地方向这个队列添加任务。
         // 获取主队列
            [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                 NSLog(@"main---%@",[NSThread currentThread]);
            }];
    
  • 相关阅读:
    记一次MD5妙用
    go执行外部应用
    Go语言中的HTTP
    Go语言中的UDP应用
    Go学习
    Element-ui学习使用
    Vue学习
    BootCDNApi使用记录
    jquery.easypiechart.js简介
    jquery.gritter.js简介
  • 原文地址:https://www.cnblogs.com/songliquan/p/4655438.html
Copyright © 2011-2022 走看看