多线程(一)

作者: Shawn_ | 来源:发表于2016-05-21 08:55 被阅读80次

    基本概念

    1. 进程:系统中正在运行的一个应用程序。
    2. 线程:进程想要执行任务,必须要有线程(每1个进程至少要有个1条线程),线程是进程的基本单元,一个进程的所有任务都是在线程中执行。
    3. 线程的串行:1个线程中任务的执行时串行的,若1个线程中执行多个任务,只能一个个的按顺序执行。同一时间内,1个线程只能执行1个任务。
    4. 多线程
      • 一个进程中开启多条线程,每条线程可以并行(同时)执行不同的任务
      • 什么叫线程的并行
        • 并行即同时执行。若同时开启3条线程分别下载3个文件(文件A,文件B,文件C)
      • 多线程并发执行的原理
        • 同一时间CPU只能处理1条线程。多线程并发执行,只是CPU在多条线程之间进行切换,如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象。

    5.多线程的优缺点

    • 优点
      • 能适当提高程序的执行效率
      • 能适当提高资源利用率(CPU,内存利用率)
    • 缺点
      • 开启线程需要占用一定的内存空间,若开启大量的线程,则会占用大量的内存空间,降低程序的性能
      • 线程越多,CPU在调度线程上的开销就越大
      • 程序设计更加复杂(线程之间的通信,多线程的数据共享)

    6.iOS中多线程的实现方案

    - pthread
    - NSThread
    - GCD
    - NSOperation
    

    NSThread

    • 创建方式

      • [alloc init];
        • 需要手动开启线程;
        • 可以拿到线程对象
    • 分离一条子线程;
      - 不可拿到线程对象;

    • 后台创建一条线程;

      • 不可拿到线程对象;
    • 线程状态

    • 新建

    • 就绪

    • 运行

    • 阻塞

    • 死亡(线程死亡之后,不能再重新启用)

    • 线程安全

      • 多个线程访问同一块区域
      • 增加互斥锁
      • 相关代码:@synchoronized(self){}(一般都是使用self)
      • 这叫线程同步
    • 原子属性和非原子属性

    • 用@porperty生命一个属性的时候,内部会进行三步操作
      1.生成一个有下划线的成员变量
      2.生成一个setter方法
      3.生成一个getter方法

    • 原子属性 : atomic(为setter方法加锁,默认就是atomic)

    • 非原子属性:nonatomic(不会为setter方法加锁)

    • 为什么开发中经常使用nonatomic而不是atomic

      • atomic 代表线程安全,需要消耗大量资源
      • nonatomic代表非线程安全,适合内存小的移动设备
      • 在开发中尽量使用nonatomic
      • 尽量避免多线程访问同一资源
      • 尽量将加锁、资源抢夺的业务逻辑交给服务器,从而减少客户端的压力
    • 线程间通信

    • 把程序中的耗时操作尽量放到子线程当中进行

    • 必须要回到主线程中进行UI刷新

    GCD

    • 两个核心概念

      • 任务 : 执行什么操作
      • 队列 : 用来储存任务的
    • 同步函数/异步函数

    • 同步函数:只能在当前线程中进行任务,不具备开启子线程的能力,立刻马上执行任务

    • 异步函数:可以在新线程中执行任务,具备开启子线程的能力

    • 并发队列/串行队列

    • 并发队列 : 多个任务可以同时执行,前提是在异步函数的情况下

    • 可以创建并发队列,也可以获取并发队列。GCD里本身存在一个并发队列

    • 串行队列 : 任务不能同时执行,需一个接一个的执行

    • 主队列 : 放在主队列里面的任务只能在主线程中执行,并且也是一个接一个的执行任务

    • GCD的使用

    • 异步函数 + 并发队列 : 开启子线程,并发执行任务

       //1.创建队列
      dispatch_queue_t  queue =          dispatch_queue_creat("one",DISPATHC_QUEUE_CONCURRENT);
       //2.封装任务,将任务添加到队列中
      dispathc_async(queue,^{
      
      NSLog(@"xxxx");
      
      });
      
    • 异步函数 + 串行队列 : 开启一条线程,串行执行任务

     //1.创建队列
     dispatch_queue_t queue = dispatch_queue_creat("two",DISPATCH_QUEUE_SERIAL);
    
     dispatch_async(queue,^{
    
      NSLog(@"xxxx");
    
      });
    
    • 同步函数 + 并发队列 : 不开线程,串行执行任务
      //1.创建队列
      dispatch_queue_t queue = dispatch_queue_creat("three",DISPATCH_QUEUE_CONCURRENT);
    
      dispatch_sync(queue,^{
    
      NSLog(@"xxx");
    
      });
    
    • 同步函数 + 串行队列 : 不开线程,串行执行任务
    //1.创建队列
       dispatch_queue_t queue = dispatch_queue_creat("four",DISPATCH_QUEUE_SERIAL);
      dispatch_sync(queue,^{
    
        NSLog(@"xxx");
      });
    
    • 异步函数 + 主队列 : 不开线程,在主线程中串行执行任务
       //1.获得主队列
      dispatch_queue_t queue = dispatch_get_main_queue();
      //
      dispatch_async(queue,^{
    
         NSLog(@"xxx");
    
      });
    
    
    • 同步函数 + 主队列 : 造成死锁状况

      //获得主队列
      dispatch_queue_t queue = dispatch_get_main_queue();
      

    //
    dispatch_sync("six",^{

    NSLog(@"xxx");
    });

    
     
    - GCD中的线程通信
    
    

    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        
        NSURL * url = [NSURL URLWithString:@"http://static.jstv.com/img/2016/5/18/20165181463528366178_0.jpg"];
        
        NSData * imagedate = [NSData dataWithContentsOfURL:url];
        
        UIImage * image = [UIImage imageWithData:imagedate];
        
        
        dispatch_async(dispatch_get_main_queue(), ^{
            
            self.imageView.image = image;
        });
      
        
    });
    

    }

     
    - GCD的常用函数
     - 延迟
    
    ![GCD中的延迟](http:https://img.haomeiwen.com/i1404354/5a589a7e984c4150.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    
    ![调用延迟方法](http:https://img.haomeiwen.com/i1404354/b691033dde114ed0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    
    ![NSTimer](http:https://img.haomeiwen.com/i1404354/63a320ee97f5eea8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
     - 一次代码       
    
    ![一次代码](http:https://img.haomeiwen.com/i1404354/18b974230d99a0ab.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    
    - 栅栏函数
        
        - 用于异步函数
        - 用于控制多线程执行任务顺序
        - 在使用栅栏函数的时候,*不能使用全局并发队列,只能进行手动创建*。
        - 栅栏函数之前的线程执行顺序,栅栏函数是没有办法进行控制的
    ![栅栏函数](http:https://img.haomeiwen.com/i1404354/7b9aa4054951553b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    
    - 快速迭代
    
     - 开启多个线程,完成快速迭代操作
     - 类似于for循环
     - GCD里面的快速迭代是并发队列
     - for循环里面是串行队列
            小案例:图片的移动
            思路:(使用了GCD里面的快速迭代)
             1.获得最初文件夹的路径
             2.获得目的文件夹的为路径
             3.移动文件需要全路径,需要对最初文件夹下的文件进行路径拼接
             4.文件名不变,所以目的文件夹的文件路径也需要进行拼接
             5.然后用文件管理者进行文件移动
    
    - 案例代码
    
    
    
    
    
    

    dispatch_apply(count, dispatch_get_global_queue(0, 0), ^(size_t index) {

    1. 第一个参数:遍历的次数
    2. 第二个参数: 队列,必须使用并发队列
      3.第三个参数:设置索引

    };

    
    ![GCD快速迭代 图片移动](http:https://img.haomeiwen.com/i1404354/b1633fd217ea64d3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
        
    - 队列组
    
      - 用来控制队列任务的完成情况
      
    
    // 创建队列组
    dispatch_group_t group = dispatch_group_create();
    
    // 获取全局并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    //将队列添加到队列组中,执行任务(下载图1)
    dispatch_group_async(group, queue, ^{
        
        //确定图片地址
        NSURL * url = [NSURL URLWithString:@"http://img.2258.com/d/file/yule/mingxing/neidi/2016-04-20/6b6d95c044b5282cf5b8c78f73c23c4c.jpg"];
        
        //根据图片地址下载二进制数据
        NSData * imageData = [NSData dataWithContentsOfURL:url];
        
        //转换二进制数据
        self.image1 = [UIImage imageWithData:imageData];
    
    });
    
    //将队列添加到队列组中,执行任务(下载图2)
    dispatch_group_async(group, queue, ^{
        
        //确定图片地址
        NSURL * url = [NSURL URLWithString:@"http://pic.yesky.com/uploadImages/2016/126/00/7HLRG65LQ5FJ.jpg"];
        
        //根据图片地址下载二进制数据
        NSData * imageData = [NSData dataWithContentsOfURL:url];
        
        //转换二进制数据
        self.image2 = [UIImage imageWithData:imageData];
        
    });
    
    
    //当队列组中的任务完成之后会进入这个方法
    

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{

        //开启图片上下文
        UIGraphicsBeginImageContext(CGSizeMake(200, 200));
        
        //绘制图片到上下文中
        [self.image1 drawInRect:CGRectMake(0, 0, 100, 200)];
        [self.image2 drawInRect:CGRectMake(100, 0, 100, 200)];
        
        //获得新图片
        UIImage * newImage = UIGraphicsGetImageFromCurrentImageContext();
        
        //关闭图片上下文
        UIGraphicsEndImageContext();
        
        //回到主线程,刷新UI
        dispatch_async(dispatch_get_main_queue(), ^{
            
             // 设置图片
            self.image.image = newImage;
            
        });
        
    });
    
    
    - 这个方法内部并不是阻塞,内部本身是异步的
    ![方法内部](http:https://img.haomeiwen.com/i1404354/100b4d4670a1fc69.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    
    - 这个方法是阻塞的,会等之前的任务执行完成之后才能执行
    ![等待](http:https://img.haomeiwen.com/i1404354/c561530c74eb514e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    
    - 关于队列组的另一种写法
    
    

    //获得全局并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

    //创建队列组
    dispatch_group_t group = dispatch_group_create();
    
    //在该方法后面的异步任务会被纳入监听范围,进入队列组
    dispatch_group_enter(group);
    
    dispatch_async(queue, ^{
        
        NSLog(@"%@",[NSThread currentThread]);
        
        //任务执行完成之后离开队列组
        dispatch_group_leave(group);
    });
    
    
    dispatch_group_enter(group);
    
    dispatch_async(queue, ^{
        
        NSLog(@"%@",[NSThread currentThread]);
        
        dispatch_group_leave(group);
        
    });
    
    
    - 两个异步函数的方法的区别
    
          1. 一个用block块封装任务
          2.一个用函数来进行任务的封装
    
    ![两个方法的区别](http:https://img.haomeiwen.com/i1404354/dcf9193766c33fbf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    
    - 全局并发队列和手动创建的并发队列的区别
    
      - 全局并发队列在GCD中本身就存在的,而手动创建的并发队列是重新创建的
      - 在使用栅栏函数的时候,必须要使用手动创建的并发队列,这样才能有效果
      - 在iOS6以前,GCD中只要带有了Creat和retain函数,在最后都要进行一次release操作。但是现在GCD已经被纳入ARC管理范围,已经不需要我们再进行手动release操作了。

    相关文章

      网友评论

        本文标题:多线程(一)

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