zoukankan      html  css  js  c++  java
  • 多线程

    一、进程

    • 进程是指在系统中正在运行的一个应用。一般说来,一个进程就是一个运行的应用。
    • 每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内。

    二、线程

    • 一个进程想要执行任务,就必须得有线程(每一个进程至少要有1条线程)
    • 一个进程(程序)的所有的任务都在线程中执行(可以认为线程就是进程中的执行路径)

    三、线程的串行

    • 一个线程中的所有任务都是串行的
    • 如果要在一个线程中执行多个任务,则必须一个一个地按顺序执行。
    • 也就是说,在同一时间内,一个线程只能执行一个任务

    四、多线程

    • 一个进程中可以开启多条线程,每条线程可以并行(同时)执行不同的任务。

    五、多线程的原理

    • CPU在同一时间只能处理一条线程,换句话说,在同一时间,只有一条线程在工作(执行)。
    • 多线程的并发(同时)执行,其实是CPU快度地在多线程之间调度(切换)。
    • 如果CPU的调度线程的速度足够快,就会造成多线程并发(同时)执行的假象。

    六、多线程的优缺点

    1.优点:

    • 能够适当的提高程序的执行效率
    • 能够适当的提高资源的利用率(CPU、内存利用率等)

    2.缺点:

    • 创建线程是有开销的,iOS下主要成本包括:内核数据结构(大约1KB)、栈空间(子线程512KB、主线程1MB,也可以使用-setStackSize:设置,单必须是4k的倍数,而且最小是16k),创建线程大约需要90毫秒的创建时间。
    • 如果开启大量的线程,会降低程序的性能。
    • 线程越多,CPU就会在调度线程上的开销越大。
    • 线程越多,程序的设计就会更加的复杂:比如线程之间的通信、多线程的数据共享。

     七、主线程

    • 一个程序运行后,默认会开启一跳线程,此线程称为“主线程”或者“UI线程”。
    • 主线程的作用:1.显示、刷新界面。2.处理UI事件(比如点击事件、滚动事件、拖拽事件等)。
    • 主线程的使用注意:1.别将比较耗时的操作放在主线程,如果有耗时操作,将其放在子线程(后台线程)中去。

    八、多线程的实现

    • NSThread(OC语言)
    • GCD(C语言)
    • NSOperation(OC语言)

    九、NSThread

    1.创建、启动线程(object是run方法的参数

    NSThread *thrad = [[NSThread alloc]initWithTarget:self selector:@selector(run:) object:@"HP"];
        [thrad start];

    线程一启动(start),就会在线程thread中调用self的run方法。

    2.主线相关用法:

    • +(NSThread *)mainThread; // 获得主线程
    • + (BOOL)isMainThread; // 是否为主线程
    • - (BOOL)isMainThread; // 是否为主线程

    3.其他用法:

    • 获得当前线程:[NSThread currentThread];
    • 设置线程的名字: - (void)setName:(NSString *)name;
    • 获取线程的名字: - (NSString *)name;

    4.其他创建线程方式:

    • 创建线程后自动启动线程(此方式是没有返回值的):[NSThread detachNewThreadSelector:@Selector(run) toTarget:self WithObject:nil];
    • 隐式创建线程(此方式是没有返回值的):[self performSelectorInBackground:@selector(run) withObject:nil];
    • 此两种方式创建线程的优点:简单快捷;缺点:由于没有返回值,所以无法对线程进行更详细的设置。

     5.线程的状态

    • 线程创建后会进入可调度线程池中等待CPU调度。

    • 线程的启动:- (void)start; 当线程的任务执行完毕就会自动进入死亡(指创建的线程对象为局部变量时)。
    • 阻塞(暂停)线程:(1)+(void)sleepUntilDate:(NSDate *)date; (2) +(void)sleepForTimeInterval:(NSTimeInterval)ti; 
    • 强制停止线程: +(void)exit;
    • 注意:一旦线程停止(死亡)了,就不能再次开启,只能重新创建。

    6.线程的安全隐患

    • 资源共享
      • 一块资源可能会被多个线程共享。
      • 比如多个线程访问同一个对象、同一个变量、同一个文件。

    7.线程安全隐患的解决---互斥锁

    • 互斥锁使用格式
      • @synchronized(锁对象){ // 需要锁定的代码 }
      • 注意:锁定一份代码只能用一把锁,用多把锁是无效的的。
    • 互斥锁的优缺点:
      • 优点:能够有效的防止多线程抢夺资源造成的安全问题。
      • 缺点:需要消耗大量的CPU资源(因为互斥锁锁定一段代码,别的线程需要等待解锁进入)。
    • 互斥锁的使用前提:多条线程抢夺同一块资源。
    • 相关专业术语:线程同步。
      • 线程同步的意思:多条线程在同一条线上执行(按顺序地执行任务)。
      • 互斥锁就是使用了线程同步技术。

    8.原子和非原子-----atomic和nonatmoic

    • atomic:线程安全(系统默认会在setter方法加锁),需要消耗大量资源。
    • nonatomic:非线程安全,适合内存小的移动设备。

    9.线程间的通信

    • 线程间通信常用的方法:
      • - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;

      • - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait 

      • NSPort、NSMessagePort、NSMachPort(了解)

    十、Grand Central Dispatch(GCD,大中枢调度)

    1.GCD介绍

    • 纯C语言编写。
    • GCD的优势:
      • GCD是苹果公司为多核的并行运算提出的解决方案。
      • GCD会自动利用CPU的多核(比如双核、四核)。
      • GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)。
      • 程序员只需要告诉GCD要执行什么任务,不需要编写任何线程管理代码。

    2.任务和队列

    • GCD中的2个概念:
      • 任务:执行什么操作。
      • 队列:用来存放任务。
    • GCD的使用步骤(2步):
      • 定制任务:确定想做的事情。
      • 将任务添加到队列中:
        • GCD会自动将队列中的任务取出来放到对应的线程中执行。
        • 任务的取出遵循队列的FIFO的原子:先进先出,后进后出。
    • 执行任务:GCD中有两个用来执行任务的常用函数。
      • 同步的方式执行:dispatch_sync(<#dispatch_queue_t queue#>, <#^(void)block#>),其中,queue:队列,block:任务。
      • 异步的方式执行:dispatch_async(<#dispatch_queue_t queue#>, <#^(void)block#>)。
    • 同步和异步的区别:
      • 同步:只能在当前线程中执行任务,不具备开启新线程的能力。
      • 异步:可以在新的线程中执行任务,具备开新线程的能力(如果遇到主队列,则不会开新线程)。
    • 队列类型:GCD的队列可以分为2大类型。
      • 并发队列(Concurrent Dispatch Queue):
        • 允许多个任务并发(同时)执行(自动开启多个线程,同时执行任务)。
        • 并发功能只有在异步(dispatch_async)函数下才有效。
      • 串行队列(Serial Dispatch Queue):
        • 让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)。
    • 容易混淆的术语:同步、异步、并行、串行
      • 同步和异步的主要区别:能不能开启新的线程。
        • 同步:只能在当前线程中执行任务,不具备开启新线程的能力。
        • 异步:可以在新的线程中执行任务,具备开启新线程的能力。
      • 并发和串行的主要区别:任务的执行方式。
        • 并发:允许多个任务并发(同时)执行。
        • 串行:一个任务执行完毕后,再执行下个任务。
    • 并发队列:
      • 使用dispatch_queue_create函数创建队列:dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
        • const char *label :设置队列的名字。
        • dispatch_queue_attr_t attr : 设置队列的类型(Concurrent Dispatch Queue、Serial Dispatch Queue)。
      • 使用GCD默认提供的全局并发队列。
        • 使用dispatch_get_global_queue(<#long identifier#>, <#unsigned long flags#>)函数获得全局的并发队列。identifier:表示优先级;flags:传0,为将来备用,苹果推荐传0。
    • 串行队列:
      • 使用dispatch_queue_create函数创建队列:设置队列的类型(Concurrent Dispatch Queue、Serial Dispatch Queue)为NULL,即可得到串行队列。
      • 使用主队列(跟主线程相关的队列):
        • 主队列是GCD自带的一种特殊的串行队列。
        • 放在主队列的任务,都会放到主线程中执行(所以异步执行也不是一定开新线程)。
        • 使用dispatch_get_main_queue()获得主队列。
    •  各种队列执行效果
    • 注意:
      • 使用sync函数往当前串行队列中添加任务,将会卡主当前的串行队列。

     3.线程间的通信

    • 从子线程回归主线程
          dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
              // 执行耗时异步操作...
              
              dispatch_async(dispatch_get_main_queue(), ^{
                 // 回归主线程,刷新UI界面...
              });
          });

     4.常用函数

    • barrier在一个并发队列中创建一个同步点。
      // 当在并发队列中,在barrier之前任务执行结束后才会执行barrier的函数,barrier执行结束后才会执行barrier后面的任务。
      // 这里指定的并发队列需要通过dispatch_queue_create函数创建的。如果你传的是一个串行队列或者全局并发队列,这个函数等同dispatch_async函数。 
      dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
    • 延迟函数
      • performSelector延迟函数

        // aSelector:延迟执行的方法; anArgument:执行方法的参数;delay: 延迟执行的时间
        - (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
      • dispatch_after延迟函数 

            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)2 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
            });
      • NSTimer定时器延迟
            [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(delayRun) userInfo:nil repeats:NO];
    • 一次性代码
      • dispatch_once:保证某段代码在整个程序运行过程中只被执行一次。
            static dispatch_once_t onceToken;
            dispatch_once(&onceToken, ^{
                // 执行一次性代码(默认是线程安全的)
            });
    •  快速迭代
      • Dispatch_apply:能够进行快速迭代遍历
        // size_t iterations:迭代的次数; dispatch_queue_t queue:队列(并发);
            dispatch_apply(10, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^(size_t index) {
                // 执行10次,index顺序不确定
            });
    • 队列组 

      • 首次执行队列组中任务,等队列组中所有任务执行完后,执行dispatch_group_notify函数中的任务。
         dispatch_group_t group = dispatch_group_create();
            dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
            
            dispatch_group_async(group, queue, ^{
                // 执行耗时异步操作
                NSLog(@"----------1操作----------");
            });
            
            dispatch_group_async(group, queue, ^{
                // 执行耗时异步操作
                NSLog(@"----------2操作----------");
                
            });
            
            dispatch_group_async(group, queue, ^{
                // 执行耗时异步操作
                NSLog(@"----------3操作----------");
                
            });
            
            dispatch_group_async(group, queue, ^{
                // 执行耗时异步操作
                NSLog(@"----------4操作----------");
                
            });
            
            dispatch_group_async(group, queue, ^{
                // 执行耗时异步操作
                NSLog(@"----------5操作----------");
                
            });
            
            // 此函数也是异步操作,并且也是新开线程
            dispatch_group_notify(group, queue, ^{
                // 此组(group)里面所有的异步任务都操作完后,执行此函数任务
                NSLog(@"group组里面所有的任务都已经做完%@", [NSThread currentThread]);
            });

    十一、NSOperation

    1.具体步骤:

    • 先将需要执行的操作封装到一个NSOperation对象中(NSOperation就是存放任务的)
    • 然后将NSOperation对象添加到NSOperationQueue中(NSOperationQueue为队列)
    • 系统会自动将NSOperationQueue中的NSOperation取出来
    • 将取出的NSOperation封装的操作(任务)放到一条新线程中执行

    2.NSOperation的使用:NSOperation是一个抽象类,并不具备封装操作的能力,必须使用它的子类来操作,方式如下:

    • NSInvocationOperation
      // 1.创建 NSInvocationOperation 对象
          NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(run) object:nil];
          
          // 2.调用 start 方法开始执行操作
          [operation start];
          
          // 注意:默认情况下,调用start方法后不会去开启一条新线程,而是在当前线程同步执行操作,只有将NSOperation放到一个NSOperationQueue中,才会异步执行操作。
    • NSBlockOperation
      // 创建 NSBlockOperation 对象
       NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
              NSLog(@"下载1------%@", [NSThread currentThread]);
          }];
          
      // 添加更多的操作
          [operation addExecutionBlock:^{
              NSLog(@"下载2------%@", [NSThread currentThread]);
              
          }];
          
          [operation addExecutionBlock:^{
              NSLog(@"下载3------%@", [NSThread currentThread]);
              
          }];
          
          [operation addExecutionBlock:^{
              NSLog(@"下载4------%@", [NSThread currentThread]);
              
          }];
          
      // 开始执行
          [operation start];
      
      // 注意:只要NSBlockOperation封装的任务操作 > 1,就会异步执行操作。
    • 自定义子类继承NSOperation,实现内部相应的方法

    3.NSOperationQueue

      • 队列的类型
        /*
             NSOperationQueue 队列类型
             1. 主队列:[NSOperationQueue mainQueue];
             2. 其他队列:[[NSOperationQueue alloc]init];
             */
      • NSOperation可以调用start方法执行任务,但默认是同步执行的。
      • 如果将NSOperation添加到NSOperationQueue中,系统会自动异步执行NSOperation中的操作。
      • 添加操作到NSOperationQueue中
        NSOperationQueue *queue = [[NSOperationQueue alloc]init];
        
             // 直接利用block执行耗时操作
             [queue addOperationWithBlock:^{
                // 直接在此处添加任务操作,相当于创建了operation
                NSLog(@"开新线程执行耗时操作...");
             }];
        
            // 创建 NSInvocationOperation 对象
            NSInvocationOperation *opration1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(downPicture1) object:nil];
            NSInvocationOperation *opration2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(downPicture2) object:nil];
            
            // 创建 NSBlockOperation 对象
            NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
                NSLog(@"下载图片3------------%@", [NSThread currentThread]);
            }];
            
            [operation3 addExecutionBlock:^{
                NSLog(@"下载图片3-1------------%@", [NSThread currentThread]);
            }];
            
            [operation3 addExecutionBlock:^{
                NSLog(@"下载图片3-2------------%@", [NSThread currentThread]);
            }];
            
            NSBlockOperation *operation4 = [NSBlockOperation blockOperationWithBlock:^{
                NSLog(@"下载图片4------------%@", [NSThread currentThread]);
            }];
            
            [operation3 addExecutionBlock:^{
                NSLog(@"下载图片4-1------------%@", [NSThread currentThread]);
            }];
            
            [operation3 addExecutionBlock:^{
                NSLog(@"下载图片4-2------------%@", [NSThread currentThread]);
            }];
            
            // 自定义Opetation,继承NSOperation类
            HPCustomOperation *operaton5 = [[HPCustomOperation alloc]init];
        
            // 加入队列中的操作是并发执行
            [queue addOperations:@[opration1, opration2, operation3, operation4, operaton5] waitUntilFinished:NO];
            NSLog(@"--------------------------------------");
        }
        
        - (void)downPicture1 {
            NSLog(@"下载图片1------------%@", [NSThread currentThread]);
        }
        
        - (void)downPicture2 {
            NSLog(@"下载图片2------------%@", [NSThread currentThread]);
        }
      • 最大并发队列(实际是控制队列是串行还是并行)
        // 并发执行,最多同时执行三条线程
        queue.maxConcurrentOperationCount = 3;
        // 串行队列
        queue.maxConcurrentOperationCount = 1;
        // 无并发性,队列中操作不会执行
        queue.maxConcurrentOperationCount = 0;
        
        // 总结:> 1 时即可为并发队列;= 1 时为串行队列;不设置,默认为并发队列,最大并发执行数由系统默认。 
    • NSOperationQueue 的挂起和取消
          NSOperationQueue *queue = [[NSOperationQueue alloc]init];
      
          // 暂时挂起
          queue.suspended = YES;
      
          // 线程取消
          [queue cancelAllOperations];
      
          // 注意:1. 线程队列取消,会停止接下来的任务,对于已经执行未完成的任务是不会立刻停止的。           2. 如果是自定义Operation,则实际是调用自定义Operation内部的cancelled方法。
                  3. 如果是自定义Operation,队列取消时,建议在每次执行耗时操作都来判断下,该任务所在队列是否经行了取消操作。
    • NSOperation 的依赖和监听 

          // 依赖函数
          - (void)addDependency:(NSOperation *)op;
          // 移除依赖
          - (void)removeDependency:(NSOperation *)op;
      
          // 监听函数(监听某个任务操作完成后做什么。。。)
          @property (nullable, copy) void (^completionBlock)(void)
          
          NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
              NSLog(@"执行任务1");
          }];
          NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
              NSLog(@"执行任务2");
          }];
          NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
              NSLog(@"执行任务3");
          }];
          NSBlockOperation *operation4 = [NSBlockOperation blockOperationWithBlock:^{
              NSLog(@"执行任务4");
          }];
          NSBlockOperation *operation5 = [NSBlockOperation blockOperationWithBlock:^{
              for (int i = 0; i < 1000; i++) {
                  NSLog(@"执行任务5");
              }
          }];
          NSBlockOperation *operation6 = [NSBlockOperation blockOperationWithBlock:^{
              NSLog(@"执行任务6");
          }];
          
          // 依赖: operation3依赖于operation1、2、5 任务,必须等operation1、2、5三个任务做完了才会执行operation3
          [operation3 addDependency:operation1];
          [operation3 addDependency:operation2];
          [operation3 addDependency:operation5];
          
          // 监听: 监听operation3 任务操作执行完,才会执行此block中操作
          [operation3 setCompletionBlock:^{
              NSLog(@"operation3已经执行完了");
          }];
          
          [queue addOperations:@[operation1, operation2, operation3, operation4, operation5, operation6] waitUntilFinished:NO];
    • NSOperation 线程间的通信
         // 线程间的通信:子线程做耗时操作,结束后回归主线程,做刷新UI界面操作。      
         NSOperationQueue *queue = [[NSOperationQueue alloc]init];
          
          [queue addOperationWithBlock:^{ // 子线程执行耗时操作
              NSURL *url = [NSURL URLWithString:@"http://www.zhlzw.com/sj/UploadFiles_9645/201208/20120819210352682.jpg"];
              
              NSData *data = [NSData dataWithContentsOfURL:url];
              UIImage *image = [UIImage imageWithData:data];
              
              // 回归主线程,刷新UI界面
              [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                  self.img.image = image;
              }];
          }]; 
    此文章为个人笔记,方便自己以及有需要的朋友查看,转载请注明出处!
  • 相关阅读:
    9、Spring Boot 2.x 集成 Thymeleaf
    【专题】Spring Boot 2.x 面试题
    8、Spring Boot 2.x 服务器部署
    7、Spring Boot 2.x 集成 Redis
    6、Spring Boot 2.x 集成 MyBatis
    5、Spring Boot 2.x 启动原理解析
    4、Spring Boot 2.x 自动配置原理
    3、Spring Boot 2.x 核心技术
    2、Spring Boot 2.x 快速入门
    centOS下安装JDK1.8.60,glassfish4.1.1以及MySQL
  • 原文地址:https://www.cnblogs.com/shpyoucan/p/5617157.html
Copyright © 2011-2022 走看看