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;
              }];
          }]; 
    此文章为个人笔记,方便自己以及有需要的朋友查看,转载请注明出处!
  • 相关阅读:
    从Java角度理解Angular之入门篇:npm, yarn, Angular CLI
    大数据开发实战:Stream SQL实时开发一
    大数据开发实战:Spark Streaming流计算开发
    大数据开发实战:Storm流计算开发
    大数据开发实战:Hadoop数据仓库开发实战
    数据仓库中的拉链表
    java通过jdbc连接impala
    impala-shell常用命令
    Kudu-java数据库简单操作
    拉链表--实现、更新及回滚的具体实现( 转载)
  • 原文地址:https://www.cnblogs.com/shpyoucan/p/5617157.html
Copyright © 2011-2022 走看看