zoukankan      html  css  js  c++  java
  • iOS多线程知识梳理

    iOS多线程知识梳理

    线程进程基础概念

    进程

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

    线程

    1个进程要想执行任务,必须得有线程(每1个进程至少要有1条线程,称为主线程)
    一个进程(程序)的所有任务都在线程中执行

    比较

    1.线程是CPU调用(执行任务)的最小单位。
    2.进程是CPU分配资源的最小单位。
    3.一个进程中至少要有一个线程。
    4.同一个进程内的线程共享进程的资源。

    多线程概述

    1个进程中可以开启多条线程,每条线程可以并行(同时)执行不同的任务
    多线程技术可以提高程序的执行效率

    原理

    同一时间,CPU只能处理1条线程,只有1条线程在工作(执行),多线程并发(同时)执行,其实是CPU快速地在多条线程之间调度(切换),如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象。
    那么如果线程非常非常多,会发生什么情况?
    CPU会在N多线程之间调度,CPU会累死,消耗大量的CPU资源,同时每条线程被调度执行的频次也会会降低(线程的执行效率降低)。
    因此我们一般只开3-5条线程

    优缺点

    多线程的优点
    能适当提高程序的执行效率
    能适当提高资源利用率(CPU、内存利用率)
    多线程的缺点
    创建线程是有开销的,iOS下主要成本包括:内核数据结构(大约1KB)、栈空间(子线程512KB、主线程1MB,也可以使用-setStackSize:设置,但必须是4K的倍数,而且最小是16K),创建线程大约需要90毫秒的创建时间
    如果开启大量的线程,会降低程序的性能,线程越多,CPU在调度线程上的开销就越大。
    程序设计更加复杂:比如线程之间的通信、多线程的数据共享等问题

    多线程应用

    主线程的主要作用
    显示刷新UI界面
    处理UI事件(比如点击事件、滚动事件、拖拽事件等)
    主线程的使用注意
    别将比较耗时的操作放到主线程中
    耗时操作会卡住主线程,严重影响UI的流畅度,给用户一种“卡”的坏体验
    将耗时操作放在子线程中执行,提高程序的执行效率

    iOS 开发中的几种线程知识点

    pthread(c)

    POSIX线程(POSIX threads),简称Pthreads,是线程的POSIX标准。该标准定义了创建和操纵线程的一整套API。在类Unix操作系统(Unix、Linux、Mac OS X等)中,都使用Pthreads作为操作系统的线程。Windows操作系统也有其移植版pthreads-win32

    具体使用

    //创建线程
    pthread_t thread;
    /*
    第一个参数pthread_t *restrict:线程对象
    第二个参数const pthread_attr_t *restrict:线程属性
    第三个参数void *(*)(void *) :指向函数的指针
    第四个参数void *restrict:函数的参数
    */
    pthread_create(&thread, NULL,run ,NULL);
    

    NSThread

    相对 pthread 提供了面向对象的方法使用更加简单

    // 获取当前线程
    + (NSThread *)currentThread;
    // 创建启动线程
    + (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument;
    // 判断是否是多线程
    + (BOOL)isMultiThreaded;
    // 线程休眠 NSDate 休眠到什么时候
    + (void)sleepUntilDate:(NSDate *)date;
    // 线程休眠时间
    + (void)sleepForTimeInterval:(NSTimeInterval)ti;
    // 结束/退出当前线程
    + (void)exit;
    // 获取当前线程优先级
    + (double)threadPriority;
    // 设置线程优先级 默认为0.5 取值范围为0.0 - 1.0 
    // 1.0优先级最高
    // 设置优先级
    + (BOOL)setThreadPriority:(double)p;
    // 获取指定线程的优先级
    - (double)threadPriority NS_AVAILABLE(10_6, 4_0);
    - (void)setThreadPriority:(double)p NS_AVAILABLE(10_6, 4_0);
    
    // 设置线程的名字
    - (void)setName:(NSString *)n NS_AVAILABLE(10_5, 2_0);
    - (NSString *)name NS_AVAILABLE(10_5, 2_0);
    
    // 判断指定的线程是否是 主线程
    - (BOOL)isMainThread NS_AVAILABLE(10_5, 2_0);
    // 判断当前线程是否是主线程
    + (BOOL)isMainThread NS_AVAILABLE(10_5, 2_0); // reports whether current thread is main
    // 获取主线程
    + (NSThread *)mainThread NS_AVAILABLE(10_5, 2_0);
    
    - (id)init NS_AVAILABLE(10_5, 2_0); // designated initializer
    // 创建线程
    - (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument NS_AVAILABLE(10_5, 2_0);
    // 指定线程是否在执行
    - (BOOL)isExecuting NS_AVAILABLE(10_5, 2_0);
    // 线程是否完成
    - (BOOL)isFinished NS_AVAILABLE(10_5, 2_0);
    // 线程是否被取消 (是否给当前线程发过取消信号)
    - (BOOL)isCancelled NS_AVAILABLE(10_5, 2_0);
    // 发送线程取消信号的 最终线程是否结束 由 线程本身决定
    - (void)cancel NS_AVAILABLE(10_5, 2_0);
    // 启动线程
    - (void)start NS_AVAILABLE(10_5, 2_0);
    
    // 线程主函数 在线程中执行的函数 都要在-main函数中调用,自定义线程中重写-main方法
    - (void)main NS_AVAILABLE(10_5, 2_0); // thread body meth
    

    GCD

    GCD的优势

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

    核心:任务和队列

    任务

    任务:执行什么操作,任务有两种执行方式: 同步函数 和 异步函数,他们之间的区别是

    同步:只能在当前线程中执行任务,不具备开启新线程的能力,任务立刻马上执行,会阻塞当前线程并等待 Block中的任务执行完毕,然后当前线程才会继续往下运行

    异步:可以在新的线程中执行任务,具备开启新线程的能力,但不一定会开新线程,当前线程会直接往下执行,不会阻塞当前线程

    • 异步操作:dispatch_async

    • 同步操作:dispatch_sync

    //async: asynchronous 将任务异步的追加到队列中
    
    dispatch_async(dispatch_get_global_queue(0,0), ^{
    
    NSLog(@"async");
    
    });
    
    //sync: synchronous将任务同步的追加到队列中(等队列中的任务执行完,再将任务追加到队列)
    
    //是同步追加,不是任务同步执行,在串行队列中,任务才同步执行
    
    dispatch_sync(dispatch_get_global_queue(0,0), ^{
    
    NSLog(@"sync");
    
    });
    
    

    队列

    队列:用来存放任务,分为串行队列 和 并行队列

    串行队列(Serial Dispatch Queue)
    让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)

    并发队列(Concurrent Dispatch Queue)
    可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)
    并发功能只有在异步(dispatch_async)函数下才有效

    队列代码

    dispatch_queue_t queue = dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
    
    • Serial Dispatch Queue --- 等待现在正在执行的任务处理结束(串行)

    • Concurrent Dispatch Queue --- 不等待现在正在执行的任务处理结束(并行、并发)

    并发队列

    dispatch_queue_t queue = dispatch_queue_create("com.xxcc", DISPATCH_QUEUE_CONCURRENT);
    

    串行队列

    dispatch_queue_t queue = dispatch_queue_create("com.xxcc", DISPATCH_QUEUE_SERIAL);
    

    优先级

    /** 
    第一个参数:优先级 也可直接填后面的数字
    #define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高
    #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默认
    #define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低
    #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后台
    第二个参数: 预留参数 0
    */
    dispatch_queue_t quque1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    

    任务和队列的组合

    1. 任务:同步函数 异步函数

    2. 队列:串行 并行

    • 异步函数+并发队列:会开启新的线程,并发执行
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
    NSLog(@"---download1---%@",[NSThread currentThread]);
    });
    
    • 异步函数+串行队列:会开启一条线程,任务串行执行
    dispatch_queue_t queue = dispatch_queue_create("com.xxcc", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
    NSLog(@"---download1---%@",[NSThread currentThread]);
    });
    
    • 同步函数+并发队列:不会开线程,任务串行执行
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_sync(queue, ^{
    NSLog(@"---download1---%@",[NSThread currentThread]);
    });
    
    • 同步函数+串行队列:不会开线程,任务串行执行
    dispatch_queue_t queue = dispatch_queue_create("com.xxcc", DISPATCH_QUEUE_SERIAL); 
    dispatch_sync(queue, ^{
    NSLog(@"---download1---%@",[NSThread currentThread]);
    });
    
    • 异步函数+主队列:不会开线程,任务串行执行
    dispatch_queue_t queue = dispatch_queue_create("com.xxcc", DISPATCH_QUEUE_SERIAL); 
    dispatch_sync(queue, ^{
    NSLog(@"---download1---%@",[NSThread currentThread]);
    });
    

    *同步函数+主队列:死锁

    //1.获得主队列
    dispatch_queue_t queue = dispatch_get_main_queue();
    //2.同步函数
    dispatch_sync(queue, ^{
    NSLog(@"---download1---%@",[NSThread currentThread]);
    });
    

    同步异步函数区别

    • 同步函数:立刻马上执行,会阻塞当前线程

    • 异步函数:当前线程会直接往下执行,不会阻塞当前线程

    GCD 常用函数用法

    1. dispatch_after

    延迟执行

    /*
    第一个参数:延迟时间
    第二个参数:要执行的代码
    如果想让延迟的代码在子线程中执行,也可以更改在哪个队列中执行 dispatch_get_main_queue() -> dispatch_get_global_queue(0, 0)
    */
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    NSLog(@"---%@",[NSThread currentThread]);
    });
    

    2. dispatch_once

    一次性代码

    -(void)once
    {
    //整个程序运行过程中只会执行一次
    //onceToken用来记录该部分的代码是否被执行过
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    NSLog(@"-----");
    });
    }
    
    //创建一个单例方法 
    + (ShareOnce *)shanreInstence{
    
     
    
    
    static dispatch_once_t onceToken;
    
    dispatch_once(&onceToken, ^{
    
     
    
     
    
    instance = [[ShareOncealloc] init];
    
    NSLog(@"只执行1次");
    
    });
    
     
    
     
    
    returninstance;
    
    }
    

    3. dispatch_group_async & dispatch_group_notify

    组队列

    // 创建队列组
    dispatch_group_t group = dispatch_group_create();
    // 创建并行队列 
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    // 执行队列组任务
    dispatch_group_async(group, queue, ^{ 
    });
    //队列组中的任务执行完毕之后,执行该函数
    dispatch_group_notify(group, queue, ^{
    });
    
    例子
    //并行队列执行任务,在并行队列中加入多个串行队列
    
    //disptch_group
    
    dispatch_group_t group =dispatch_group_create();
    
    dispatch_queue_t queue =dispatch_get_global_queue(0,0);
    
    dispatch_group_async(group, queue, ^{
    
    NSLog(@"task 01");
    
    });
    
    dispatch_group_async(group, queue, ^{
    
    NSLog(@"task 02");
    
    });
    
    dispatch_group_async(group, queue, ^{
    
    sleep(6);//等待6秒执行任务3 目的:测试等待时间的dispatch_group_wait这个方法
    
    NSLog(@"task 03");
    
    });
    
    dispatch_group_async(group, queue, ^{
    
    sleep(2);
    
    NSLog(@"task 04");
    
    });
    
    //监视函数
    
    //监视队列中得任务结束,执行block中得任务
    
    dispatch_group_notify(group, queue, ^{
    
    NSLog(@"done");
    
    });
    
    //等待时间
    
    dispatch_time_t time =dispatch_time(DISPATCH_TIME_NOW,5ull*NSEC_PER_SEC);
    
    //dispatch_group_wait 指定时间后,看你一眼queue是否执行完毕
    
    //如果执行完返回0
    
    //没有执行完,返回非0值
    
    long result = dispatch_group_wait(group, time);
    
    if (result == 0) {
    
    NSLog(@"finish");
    
    }else{
    
    NSLog(@"not finish");
    }
    }
    

    4. dispatch_barrier_async

    栅栏函数

    栅栏函数可以控制任务执行的顺序,栅栏函数之前的执行完毕之后,执行栅栏函数,然后在执行栅栏函数之后的

    5. dispatch_apply

    快速迭代

    /*
    第一个参数:迭代的次数
    第二个参数:在哪个队列中执行
    第三个参数:block要执行的任务
    */
    dispatch_apply(10, queue, ^(size_t index) {
    });
    

    6. dispatch_semaphore_create & dispatch_semaphore_signal & dispatch_semaphore_wait

    信号量机制

    //使用
    
    dispatch_queue_t queue =dispatch_get_global_queue(0,0);
    
    dispatch_semaphore_t dsema =dispatch_semaphore_create(1);
    
    NSMutableArray *array = [NSMutableArrayarray];
    
    for (int i =0; i < 1000; i++) {
    dispatch_async(queue, ^{
    dispatch_semaphore_wait(dsema,DISPATCH_TIME_FOREVER);
    [arrayaddObject:[NSNumbernumberWithInt:i]];
    dispatch_semaphore_signal(dsema);
    
    });
    
    }
    
    NSLog(@"%@", array);
    
    

    NSOperation

    NSOperation 是苹果公司对 GCD 的封装,完全面向对象,并比GCD多了一些更简单实用的功能,所以使用起来更加方便易于理解。NSOperation 和NSOperationQueue 分别对应 GCD 的 任务 和 队列。

    NSOperation和NSOperationQueue实现多线程的具体步骤

    1.将需要执行的操作封装到一个NSOperation对象中

    2.将NSOperation对象添加到NSOperationQueue中
    系统会自动将NSOperationQueue中的NSOperation取出来,并将取出的NSOperation封装的操作放到一条新线程中执行

    NSOperation

    NSOperation是个抽象类,并不具备封装操作的能力,必须使用它的子类

    使用NSOperation子类的方式有3种

    1.NSInvocationOperation
    NSInvocationOperation *op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download) object:nil];
    //启动操作
    [op start];
    
    2.NSBlockOperation 常用的一种
    //1.封装操作
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
    //要执行的操作,在主线程中执行
    NSLog(@"1------%@",[NSThread currentThread]); 
    }];
    //2.追加操作,追加的操作在子线程中执行,可以追加多条操作
    [op addExecutionBlock:^{
    NSLog(@"---download2--%@",[NSThread currentThread]);
    }];
    [op start];
    
    3.自定义类继承NSOperation,实现内部方法,复用性强
    // 重写自定义类的main方法实现封装操作
    -(void)main
    {
    // 要执行的操作
    }
    
    
    // 实例化一个自定义对象,并执行操作
    CustomOperation *op = [[CustomOperation alloc]init];
    [op start];
    

    NSOperationQueue队列的使用

    两种队列

    主队列:通过mainQueue获得,凡是放到主队列中的任务都将在主线程执行

    非主队列:直接alloc init出来的队列。非主队列同时具备了并发和串行的功能,通过设置最大并发数属性来控制任务是并发执行还是串行执行

    作用

    NSOperation可以调用start方法来执行任务,但默认是同步执行的
    如果将NSOperation添加到NSOperationQueue(操作队列)中,系统会自动异步执行NSOperation中的操作

    两者结合的方法使用 NSOperation和NSOperationQueue结合使用创建多线程

    注:这里使用NSBlockOperation示例,其他两种方法一样
    // 1. 创建非主队列 同时具备并发和串行的功能,默认是并发队列
    NSOperationQueue *queue =[[NSOperationQueue alloc]init];
    //NSBlockOperation 不论封装操作还是追加操作都是异步并发执行
    // 2. 封装操作
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"download1 -- %@",[NSThread currentThread]);
    }];
    // 3. 将封装操作加入主队列
    // 也可以不获取封装操作对象 直接添加操作到队列中
    //[queue addOperationWithBlock:^{
    // 操作
    //}];
    [queue addOperation:op1];
    
    

    NSOperation和NSOperationQueue的重要属性和方法

    • NSOperation的依赖

    • NSOperation监听操作

    • NSOperationQueue

    • maxConcurrentOperationCount 并发数

    • suspended 是否暂停

    • cancelAllOperations 取消所有任务,不再执行,不可逆

    关键点:暂停和取消只能暂停或取消处于等待状态的任务,不能暂停或取消正在执行中的任务,必须等正在执行的任务执行完毕之后才会暂停,如果想要暂停或者取消正在执行的任务,可以在每个任务之间即每当执行完一段耗时操作之后,判断是否任务是否被取消或者暂停。如果想要精确的控制,则需要将判断代码放在任务之中,但是不建议这么做,频繁的判断会消耗太多时间

    一个例子

    #import "ViewController.h"
    @interface ViewController ()
    @property (weak, nonatomic) IBOutlet UIImageView *imageView;
    @property(nonatomic,strong)UIImage *image1;
    @property(nonatomic,strong)UIImage *image2;
    @end
    @implementation ViewController
    
    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
    // 创建非住队列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    // 下载第一张图片
    NSBlockOperation *download1 = [NSBlockOperation blockOperationWithBlock:^{
    NSURL *url = [NSURL URLWithString:@"http://img2.3lian.com/2014/c7/12/d/77.jpg"];
    NSData *data = [NSData dataWithContentsOfURL:url];
    self.image1 = [UIImage imageWithData:data];
    }];
    // 下载第二张图片
    NSBlockOperation *download2 = [NSBlockOperation blockOperationWithBlock:^{
    NSURL *url = [NSURL URLWithString:@"http://img2.3lian.com/2014/c7/12/d/77.jpg"];
    NSData *data = [NSData dataWithContentsOfURL:url];
    self.image2 = [UIImage imageWithData:data];
    }];
    // 合成操作
    NSBlockOperation *combie = [NSBlockOperation blockOperationWithBlock:^{
    // 开启图形上下文
    UIGraphicsBeginImageContext(CGSizeMake(375, 667));
    // 绘制图片1
    [self.image1 drawInRect:CGRectMake(0, 0, 375, 333)];
    // 绘制图片2
    [self.image2 drawInRect:CGRectMake(0, 334, 375, 333)];
    // 获取合成图片
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    // 关闭图形上下文
    UIGraphicsEndImageContext();
    // 回到主线程刷新UI
    [[NSOperationQueue mainQueue]addOperationWithBlock:^{
    self.imageView.image = image;
    }];
    }];
    // 添加依赖,合成图片需要等图片1,图片2都下载完毕之后合成
    [combie addDependency:download1];
    [combie addDependency:download2];
    // 添加操作到队列
    [queue addOperation:download1];
    [queue addOperation:download2];
    [queue addOperation:combie];
    }
    @end
    

    多线程共享读写资源隐患问题

    容易死锁

    - (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"1========%@",[NSThread currentThread]);
    dispatch_sync(dispatch_get_main_queue(), ^{
    NSLog(@"2========%@",[NSThread currentThread]);
    });
    NSLog(@"3========%@",[NSThread currentThread]);
    }
    
    ThreadDemo[5615:874679] 1========{number = 1, name = main}
    
    

    原因:任务A等待任务B完成才能继续执行,但作为串行队列的主队列又不能让任务B在任务A未完成之前开始执行,所以任务A等着任务B完成,任务B等着任务A完成

    解决方案:

    1.普通执行
    2.同步队列
    3.异步队列

    多线程安全隐患的原因:1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源,比如多个线程访问同一个对象、同一个变量、同一个文件。
    那么当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题。

    可以使用互斥锁解决

    几种加锁方法

    • synchronized

    @synchronized(锁对象) {
    // 需要锁定的代码
    }

    • NSLock
  • 相关阅读:
    频繁FGC解决方案
    ThreadLocal
    Session与Cookie
    Socket通信流程
    SpringBoot面试题
    面向对象3大特性:封装、继承、多态——继承(继承方法的重写和初始化顺序、final & super关键字、Object类)
    面向对象3大特性:封装、继承、多态——封装(this 、访问修饰符、内部类)
    java类和对象、构造方法、静态变量、静态方法、静态初始化块
    数组的使用、eclipse调试程序、练习小demo以及方法的定义和重载
    java中的条件语句if...else... switch 和循环语句while do...while for
  • 原文地址:https://www.cnblogs.com/keyan1102/p/7466233.html
Copyright © 2011-2022 走看看