美文网首页
ios多线程

ios多线程

作者: 瑞阳gg | 来源:发表于2017-07-26 18:26 被阅读0次
    • 什么是进程

    • 进程是指在系统中正在运行的一个应用程序

    • 每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内

    • 比如同时打开迅雷、Xcode,系统就会分别启动2个进程

    • 什么是线程

    • 1个进程要想执行任务,必须得有线程(每1个进程至少要有1条线程)

    • 一个进程(程序)的所有任务都在线程中执行

    • 比如使用酷狗播放音乐、使用迅雷下载电影,都需要在线程中执行

    • 1个线程中任务的执行是串行的

    • 如果要在1个线程中执行多个任务,那么只能一个一个地按顺序执行这些任务

    • 也就是说,在同一时间内,1个线程只能执行1个任务

    什么是多线程
    • 1个进程中可以开启多条线程,每条线程可以并行(同时)执行不同的任务
    • 多线程技术可以提高程序的执行效率
    • 比如同时开启3条线程分别下载3个文件(分别是文件A、文件B、文件C
    多线程的原理
    • 同一时间,CPU只能处理1条线程,只有1条线程在工作(执行)
    • 多线程并发(同时)执行,其实是CPU快速地在多条线程之间调度(切换)
    • 如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象

    如果线程非常非常多,会发生什么情况?
    CPU会在N多线程之间调度,CPU会累死,消耗大量的CPU资源
    每条线程被调度执行的频次会降低(线程的执行效率降低)

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

    • 主线程的主要作用

      • 显示\刷新UI界面
      • 处理UI事件(比如点击事件、滚动事件、拖拽事件等)
    • 主线程的使用注意

      • 别将比较耗时的操作放到主线程中
      • 耗时操作会卡住主线程,严重影响UI的流畅度,给用户一种“卡”的坏体验

    在主线程放一个需要耗时10秒的操作;用户在第五秒的时候点击屏幕按钮,在第十秒的时候才会做出响应;造成卡顿现象;

    • 在ios领域里面真正的多线程技术只有这两个pthread和NSthread;
      GCD不能直接操作多线程;属于并发技术;NSOperation也是一样,是操作队列的,与线程无关,线程部分GCD已经帮你封装好了!
    • GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
    • 我们只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码
    • 苹果不建议开发者使用多线程技术;
      鼓励使用并发技术;
    NSThread
    • 创建线程的几种方法
    // 创建一个NSThread
    NSThread * thread = [[NSThread alloc]initWithTarget:self selector:@selector(demo:) object:@"Thread"];
    //线程就绪-之后等着cpu调度; 
    [thread start];
    
    //demo函数在子线程执行
    -(void)demo:(id)obj{
        for (int i = 0; i < 2; i++) {
            //number !=1
            NSLog(@"%@",[NSThread currentThread]);
            //[NSThread currentThread] 打印当前线程   
            //返回一个Thread对象,里面有number和name属性
            //number == 1 说明是主线程   number != 1 就是其他线程
        }
    }
    
    
    //detach ==> 分离
    //分离出一条子线程
    [NSThread detachNewThreadSelector:@selector(demo:) toTarget:self withObject:@"Detach"];
    
    
    //InBackground 就是在后台(子线程)运行!!
    //是NSObject的分类 意味着所有的继承NSObject的都可以使用这个方法
    //非常方便.不用NSThread对象
    [self performSelectorInBackground:@selector(demo:) withObject:@"background"];
    
    
    线程的状态
    • 新建一条线程,线程在可调度线程池里 (由cpu进行调度的)
    • 当 [thread start] 的时候,在可调度线程池里的线程都处于就绪状态
    • cpu调度处于就绪状态的线程
    • 运行状态
    • 线程执行完毕之后线程在可调度线程池取出,干掉;

    线程阻塞

    • 当运行满足某个条件,会让线程"睡一会

    提示:sleep 方法是类方法,会直接休眠当前线程!!

    //当前线程睡两秒
    [NSThread sleepForTimeInterval:2.0];
    
    //一旦强行终止线程,后续的所有代码都不会被执行
    //注意:在终止线程之前,应该要释放之前分配的对象!!
    [NSThread exit];
    
    //创建线程
    NSThread * t = [[NSThread alloc]initWithTarget:self selector:@selector(theadStatus) object:nil];
    //线程就绪(CPU翻牌)
    [t start];
    
    -(void)theadStatus{
        for (int i = 0; i < 20;i++) {
            //阻塞,当运行满足某个条件,会让线程"睡一会"
            //提示:sleep 方法是类方法,会直接休眠当前线程!!
            if (i == 8) {
                NSLog(@"睡一会");    
                //睡的是子线程 
                //注意!!! exit会杀掉主线程!但是APP不会挂掉!!
                 [NSThread sleepForTimeInterval:2.0];
            }
            NSLog(@"%@  %d",[NSThread currentThread],i);
            
            //当线程满足某一个条件时,可以强行终止的
            //exit 类方法,哥么终止当前线程!!!!
            if (i == 15) {
                [NSThread exit];  //线程就就处于死亡状态了
            }
        }
        NSLog(@"能来吗???");  //来不了了
    }
    
    
    threadPriority  //线程优先级
    
    • 优先级只是保证cpu调度的可能性会高!
      用优先级来控制线程得执行顺序是不理性的!
    • 建议:再开发的时候不要修改优先级;
      在多线程的开发中,不要相信一次的运行结果!
    多线程的目的
    • 将耗时操作放在后台,不阻塞UI线程

    多线程的安全隐患

    • 1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源

      • 比如多个线程访问同一个对象、同一个变量、同一个文件
    • 当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题

    安全隐患的解决 - 互斥锁
    • 一个线程访问一个资源时,先给他上锁,访问完成之后再解锁让其他线程访问;
    • 互斥锁 -- 保证锁内的代码,同一时间,只有一条线程执行!
    • 互斥锁 的范围 应该尽量小,范围大了 效率就差!!
            //互斥锁
            @synchronized (self) {
                //括号里是 同一时间只能有一条线程执行的代码
            };
    

    好比你去上厕所,你把门锁上了,别人只能在外面等着,等你操作完厕所,别人才能进来;如果你不锁门,两个人在厕所后果不堪设想!😏

    原子属性
    • atomic-用来保护线程安全(多线程执行写入操作的时候,保证同一时间只有一个线程执行写入 只对写入操作上锁,读不上锁)
    • 单写多读的原子属性(只能单个线程写,但可以多个线程读)
    • 实际上原子属性内部有一把锁叫 - 自旋锁
      • 自旋锁&互斥锁异同
        • 共同点

          • 都能保证线程安全
        • 不同点

          • 互斥锁:被锁在外面的线程,处于休眠状态;等待锁打开,然后被唤醒;
          • 自旋锁:被锁在外面的线程,用死循环的方式,等待锁打开;
    • 不管什么锁,都很消耗性能,效率不高;

    想要模拟原子属性的时候就在set方法里加了一把锁; @synchronized
    如果不写nonatomic | atomic 默认是 atomic

    线程间的通信方法

    • 就这五个方法
      • 把SEL丢到某一个线程去执行
    /**
     waitUntilDone : 当前线程是否等待SEL执行完再继续
    */
    
    - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
    - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
        // equivalent to the first method with kCFRunLoopCommonModes
    
    - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array NS_AVAILABLE(10_5, 2_0);
    - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);
        // equivalent to the first method with kCFRunLoopCommonModes
    - (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg NS_AVAILABLE(10_5, 2_0);
    
    

    GCD(重点)

    GCD并不是多线程技术;属于并发解决技术;

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

    • 将任务添加到队列中

    GCD中有2个核心概念
    • 任务:执行什么操作
      • 执行任务方式有两种; 同步/异步
        • 同步:不会到线程池里面去获取子线程!
        • 异步:只要有任务,就会到线程池取子线程!(主队列除外!)
    • 队列:用来存放任务
      • 串行:一个接一个的调度任务
      • 并行:可以同时调度多个任务

    GCD会自动将队列中的任务取出,放到对应的线程中执行
    任务的取出遵循队列的FIFO原则:先进先出,后进后出

    /**
     同步执行方法,这句话不执行完,就不会执行下一个任务,同步执行不会开启线程
     */
       //1.创建队列
        dispatch_queue_t q = dispatch_get_global_queue(0, 0);
        //2.任务添加到队列中
        //2.1 定义任务 -- block
        void(^task)() = ^{
            NSLog(@"%@",[NSThread currentThread]);
        };
        //2.2 添加任务到队列,并且会执行
        dispatch_sync(q, task);
    
    /**
     异步执行任务  哥么如果任务没有执行完毕,可以不用等待,异步执行下一个任务 
     具备开启线程的能力!  异步通常又是多线程的代名词!!
     */
    -(void)gcdDemo2{
        //1.创建队列
        dispatch_queue_t q = dispatch_get_global_queue(0, 0);
        //2 定义任务 -- block
        void(^task)() = ^{
            NSLog(@"%@",[NSThread currentThread]);
        };
        //3. 添加任务到队列
        dispatch_async(q, task);
    }
    
    //线程间通信-切换子线程到主线程更新UI
    //指定任务执行方法 -- 异步
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            //耗时操作
            NSLog(@"%@",[NSThread currentThread]);
            
            //更新UI 主队列,就是专门负责在主线程上调度任务的队列!
            //dispatch_get_main_queue  主队列 住能操作主线程
            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"更新UI%@",[NSThread currentThread]);
                
            });
            
        });
    
    //MARK:串行队列,同步任务
    /**
    *   不会开启线程,会顺序执行
    */
    -(void)gcdDemo1{
        //1.队列 - 串行
        
        /**
         1."ios" 队列名称:
         2. NULL 队列的属性: DISPATCH_QUEUE_SERIAL 表示串行!
         */
        dispatch_queue_t q = dispatch_queue_create("ios", NULL);
        
        //2.同步执行任务
        for (int i = 0; i < 10; i++) {
            dispatch_sync(q, ^{
                NSLog(@"%@ %d",[NSThread currentThread],i);
            });
        }
    }
    
    //MARK: 串行队列,异步任务
    -(void)gcdDemo2{
        /**
         会开几条线程?会顺序执行吗?
         开一条线程,顺序执行
         */
        //1.队列 - 串行
        dispatch_queue_t q = dispatch_queue_create("tanzhouios", NULL);
        
        //2.异步执行任务
        for (int i = 0; i < 10; i++) {
            NSLog(@"%d------------",i);
            dispatch_async(q, ^{
                NSLog(@"%@ %d",[NSThread currentThread],i);
            });
        }
        //哥么在主线程!
        NSLog(@"come here");
    }
    
    //MARK : 并发队列,异步执行
    -(void)gcdDemo3{
        //1.队列 - 并发 DISPATCH_QUEUE_CONCURRENT
        dispatch_queue_t q = dispatch_queue_create("tanzhouios", DISPATCH_QUEUE_CONCURRENT);
        
        //2.异步执行任务
        for (int i = 0; i < 10; i++) {
            dispatch_async(q, ^{
                NSLog(@"%@ %d",[NSThread currentThread],i);
            });
        }
        //哥么在主线程!
        NSLog(@"come here");
    }
    
    //MARK : 并发队列,同步执行   和 串行队列,同步执行 效果一样!
    -(void)gcdDemo4{
        
        // 会开线程吗?  顺序执行?  come here?
        //  不会          顺序     最后
        
        //1.队列 - 并发 DISPATCH_QUEUE_CONCURRENT
        dispatch_queue_t q = dispatch_queue_create("tanzhouios", DISPATCH_QUEUE_CONCURRENT);
        
        //2.同步执行任务
        for (int i = 0; i < 10; i++) {
            dispatch_sync(q, ^{
                NSLog(@"%@ %d",[NSThread currentThread],i);
            });
        }
        //哥么在主线程!
        NSLog(@"come here");
    }
    

    小结:
    只有异步才会开启子线程!同步不开启!
    开几条线程,取决于队列,串行开一条,并发可以开多条(异步)

    个人理解锁这个问题

    线程锁:是多条线程同时访问一个资源时需要一个互斥锁;
    GDC-死锁:主队列是一个串行队列;在主队列里添加一个同步任务会造成死锁;互相等待的局面;

        //主队列是专门负责在主线程上调度任务的队列 --> 不会开线程
        //1.队列 --> 已启动主线程,就可以获取主队列
        dispatch_queue_t q = dispatch_get_main_queue();
        
        //2.同步任务  ==> 死锁
        //走到这里添加一个同步任务,主队列的任务是不是要等他执行完了在执行;
        dispatch_sync(q, ^{
            NSLog(@"能来吗? ");
            //主队列里的任务没执行完呢你想执行这同步任务,你是不是要等主队列执行完了才能执行;
        });
        NSLog(@"come here");
    
    死锁

    注意:
    主队列不是全局队列
    全局队列:dispatch_get_global_queue
    全局队列本质上是并发队列
    主队列:dispatch_get_main_queue()
    主队列是一个串行队列

    主队列是串行的 主队列里的任务默认只有一个(不手动添加的话) 这个任务是同步的 主队列里直接添加一个异步任务 不开开启子线程

    主队列只负责主线程

        //主队列是专门负责在主线程上调度任务的队列 --> 不会开线程
        
        //1.队列 --> 一启动主线程,就可以获取主队列
        dispatch_queue_t q = dispatch_get_main_queue();
        
        //2.异步任务
        dispatch_async(q, ^{
            NSLog(@"%@",[NSThread currentThread]);
        });
        NSLog(@"come here");
    
        //全局队列
        /*  参数
         1. 涉及到系统适配
         iOS 8   服务质量
         QOS_CLASS_USER_INTERACTIVE    用户交互(希望线程快速被执行,不要用好使的操作)
         QOS_CLASS_USER_INITIATED      用户需要的(同样不要使用耗时操作)
         QOS_CLASS_DEFAULT             默认的(给系统来重置队列的)
         QOS_CLASS_UTILITY             使用工具(用来做耗时操作)
         QOS_CLASS_BACKGROUND          后台
         QOS_CLASS_UNSPECIFIED         没有指定优先级
         iOS 7  调度的优先级
         - DISPATCH_QUEUE_PRIORITY_HIGH 2               高优先级
         - DISPATCH_QUEUE_PRIORITY_DEFAULT 0            默认优先级
         - DISPATCH_QUEUE_PRIORITY_LOW (-2)             低优先级
         - DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN 后台优先级
         
         提示:尤其不要选择BACKGROUND 优先级,服务质量,线程执行会慢到令人发指!!!
         
         
         2. 为未来使用的一个保留,现在始终给0.
         
         老项目中,一般还是没有淘汰iOS 7  ,没法使用服务质量
         */
        dispatch_queue_t q = dispatch_get_global_queue(0, 0);
        //以第一个0用来设置优先级
        //第二个0还没有定义
    
        /*
         全局队列 & 并发队列   区别
         1> 名称,并发队列取名字,适合于企业开发跟踪错误
         2> release,在MRC 并发队列 需要使用的
            dispatch_release(q);//ARC 情况下不需要release !
         
         
         全局队列 & 串行队列
            全局队列: 并发,能够调度多个线程,执行效率高
                - 费电
            串行队列:一个一个执行,执行效率低
                - 省点
         
            判断依据:用户上网方式
                - WIFI : 可以多开线程
                - 流量  : 尽量少开线程
         
         */
        //1.队列
        dispatch_queue_t q = dispatch_queue_create("tanzhou", DISPATCH_QUEUE_CONCURRENT);
        //2.全局队列
        dispatch_queue_t q = dispatch_get_global_queue(0, 0);
    
    
     延迟执行
        /**
         从现在开始,进过多少纳秒之后,让 queue队列,调度 block 任务,异步执行!
         参数:
         1.when
         2.queue
         3.block
         */
        dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.00003 * NSEC_PER_SEC));
        dispatch_after(when, dispatch_queue_create("tanzhou", NULL), ^{
            NSLog(@"%@",[NSThread currentThread]);
        });
    
    GCD:执行一次 常用于单利
        //苹果提供的 一次执行机制,不仅能够保证一次执行!而且是线程安全的!!
        static dispatch_once_t onceToken;
        NSLog(@"%ld",onceToken);
        //苹果推荐使用 gcd 一次执行,效率高
        //不要使用互斥锁,效率低!
        dispatch_once(&onceToken, ^{
            //只会执行一次!!
            NSLog(@"执行了%@",[NSThread currentThread]);
        });
    
    GCD调度组
     //1.队列
        dispatch_queue_t q = dispatch_get_global_queue(0, 0);
        
        //2.调度组
        dispatch_group_t g = dispatch_group_create();
        
        //3.添加任务,让队列调度,任务执行情况,最后通知群组
        dispatch_group_async(g, q, ^{
            NSLog(@"download A%@",[NSThread currentThread]);
        });
        dispatch_group_async(g, q, ^{
            [NSThread sleepForTimeInterval:1.0];
            NSLog(@"download B%@",[NSThread currentThread]);
        });
        dispatch_group_async(g, q, ^{
            [NSThread sleepForTimeInterval:1.0];
            NSLog(@"download C%@",[NSThread currentThread]);
        });
        
        //4.所有任务执行完毕后,通知
        //用一个调度组,可以监听全局队列的任务,主队列去执行最后的任务
        //dispatch_group_notify 本身也是异步的!
        dispatch_group_notify(g, dispatch_get_main_queue(), ^{
            //更新UI,通知用户
            NSLog(@"OK %@",[NSThread currentThread]);
        });
    

    相关文章

      网友评论

          本文标题:ios多线程

          本文链接:https://www.haomeiwen.com/subject/wjlokxtx.html