iOS中你必须了解的多线程

多线程概念详解

什么是进程?
  • 简单的说进程就是我们电脑上运行的一个个应用程序,每一个程序就是一个进程,并且每个进程之间是独立的,每个进程运行在其专用受保护的内存空间内(window系统可以通过任务管理器进行查看,Mac系统中可以通过活动监视器对其进行查看)
什么是线程?
  • 通过上面的介绍我们知道了什么是进程,那么如何让进程运行起来,这个时候就要有线程了,也就是说每个应用程序想要跑起来,最少也要有一条线程存在,其实应用程序启动的时候我们的系统就会默认帮我们的应用程序开启一条线程,这条线程也叫做'主线程',或者'UI线程'
进程和线程之间的关系
  • 线程是进行的执行单元,进程的所有任务都在线程中执行,举例来说:进程就好比公司中的一个个部门,线程则代表着部门中的同事,而主线程当然是我们的老板了,一个公司部能没有老板,一个程序不能没有线程其实都是一个道理.
什么是CPU?
  • CPU(中央处理器,Central Processing Unit)是一块超大规模的集成电路,只要用来解释计算机指令以及处理计算机软件中的数据.
多线程的原理
  • 同一时间,CPU值能处理一个线程,只有一条线程在执行,多线程指的就是多条线程同时执行,其实就是CPU快速的在多条线程之间的切换,如果CPU调度线程的时间足够快,那么就会造成多线程并发执行的假象,而当线程特别多的时候,那么CPU在多条切换的效率也就会下降,同时消耗大量的CPU资源,线程的执行效率也就会下降.
多线程优点
  • 能适当提高程序的执行效率
  • 能适当提高资源的利用率
多线程的缺点
  • 开启线程需要占用一定的内存空间,如果开启大量的线程,则会占用大量的内存空间,降低程序的性能
  • 线程越多,CPU在调度线程上得开销就越大,程序的设计上也就更加的复杂,因此不推荐开启太多的线程,一般开启2~5条为最佳(且用且珍惜);
主线程
  • 也就是应用程序启动的时候,系统默认帮我们创建的线程,称之为'主线程'或者是'UI线程';
  • 主线程的作用一般都是用来显示或者刷新UI界面例如:点击,滚动,拖拽等事件
IOS中多线程的实现方案
方案 简介 语言 线程生命周期 使用频率
pthread <ul><li>一套通用的多线程API</li><li>适用于 Unix / Linux / Windows 等系统</li><li>跨平台\可移植</li><li>使用难度大</li></ul> C 程序员管理 几乎不用
NSThread <ul><li>使用更加面向对象</li><li>简单易用,可直接操作线程对象</li></ul> OC 程序员管理 偶尔使用
GCD <ul><li>旨在替代NSThread等线程技术</li><li>充分利用设备的多核</li></ul> C 自动管理 经常使用
NSOperation <ul><li>基于GCD(底层是GCD)</li><li>比GCD多了一些更简单实用的功能</li><li>使用更加面向对象</li></ul> OC 自动管理 经常使用
NSThread使用
创建线程的种方式
  • 通过NSThread的对象方法
  • 通过NSThread的类方法
  • 通过NSObject
NSThread 是iOS提供的轻量级开发,使用起来也相对简单
为什么要使用NSThread ?
  • 在开发中我们经常去网络上下载资源,由于网络原因,有的时候我们很难保证下载很快就能完成,这时候使用NSThread将下载的过程交给线程,那么在下载的同时我们的程序并不需要等待,可以继续操作界面,程序体验会更好.
NSThread-Demo:
//点击下载图片
- (IBAction)downLoadButton:(id)sender {
    //通过NSThread对象方法
    //[self objectMethod];
    //通过NSThread类方法
    //[self classMethod];
    //通过NSObject的方法
    [self extendedMethod];
}

//通过NSObject的方法去下载图片
- (void)extendedMethod{
    //通过NSObject分类方法
   [self performSelectorInBackground:@selector(downLoadImage) withObject:nil];
   
}

//通过NSThread类方法去下载图片
- (void)classMethod{
    //NSThread类方法
    [NSThread detachNewThreadSelector:@selector(downLoadImage) toTarget:self withObject:nil];
}

//通过NSThread对象方法去下载图片
- (void)objectMethod{
    //创建一个程序去下载图片
    NSThread *thread=[[NSThread alloc]initWithTarget:self selector:@selector(downLoadImage) object:nil];
    NSLog(@"downLoadButton:%@",[NSThread currentThread]);//主线程
    //开启线程
    [thread start];
}

//下载图片
- (void)downLoadImage{
    //请求图片资源
    NSURL *url=[NSURL URLWithString:@"http://pic7.nipic.com/20100515/2001785_115623014419_2.jpg"];
    //模拟下载延迟
    [NSThread sleepForTimeInterval:10];
    //将资源转换为二进制
    NSData *data=[NSData dataWithContentsOfURL:url];
    NSLog(@"downLoadImage:%@",[NSThread currentThread]);//在子线程中下载图片
    //在主线程更新UI
    [self performSelectorOnMainThread:@selector(updateImage:) withObject:data waitUntilDone:YES];
    
}

//更新imageView
- (void)updateImage:(NSData *)data{
    NSLog(@"updateImage:%@",[NSThread currentThread]);//在主线程中更新UI
    //将二进制数据转换为图片
    UIImage *image=[UIImage imageWithData:data];
    //设置image
    self.imageView.image=image;
}

以上就是对NSThread三种使用的一个简单的Demo
线程状态
  • 新建
    • 实例化线程对象
  • 就绪
    • 向线程对象发送 start 消息,线程对象被加入 可调度线程池 等待 CPU 调度
    • detachNewThreadSelector 方法和 performSelectorInBackground 方法会直接实例化一个线程对象并加入 可调度线程池
  • 运行
    • CPU 负责调度可调度线程池中线程的执行
    • 线程执行完成之前(死亡之前),状态可能会在就绪运行之间来回切换
    • 就绪运行之间的状态变化由 CPU 负责,程序员不能干预
  • 阻塞
    • 当满足某个预定条件时,可以使用休眠或锁阻塞线程执行
      • sleepForTimeInterval:休眠指定时长
      • sleepUntilDate:休眠到指定日期
      • @synchronized(self):互斥锁
  • 死亡
    • 正常死亡
      • 线程执行完毕
    • 非正常死亡
      • 当满足某个条件后,在线程内部自己中止执行(自杀)
      • 当满足某个条件后,在主线程给其它线程打个死亡标记(下圣旨),让子线程自行了断.(被逼着死亡)
线程的属性(name)
  • 随着我们以后开发中项目的日益增大,Bug也就会相对的越来越隐藏越来越难查找,这时候如果是线程问题造成的,那么我们就可以在使用的时候给每个线程起一个名字,这样一来当程序出问题了我们可以很快的找到问题所在的线程并处理它
线程的优先级(threadPriority)
  • 当多个线程同时存在的时候那么就会引出一个问题,到底哪个线程该先执行哪个线程应该最后执行,因此也就有了线程的优先级,线程优先级是用浮点数表示的,最高为1.0 最低为0.0 默认我们创建出来的线程为0.5.
  • 修改线程优先级我们可以提高某个线程供CPU调度的可能性,开发中很少有去修改线程优先级的
栈区大小(stackSize)
  • 默认情况下主线程和子线程在栈区大小都是512k
是否是主线程(isMainThread)
  • 开发中我们判断某条线程是否是主线程其实很简单,除了通过isMainThread属性,我们还可以直接在代码中打印出来[NSThread currentThread],其中会记录线程名称name和编号number,如果number为1 则为主线程
线程间的通讯
  • 其实从上面的Demo我们不难发现,线程间的通讯简单的来说就是把耗时的操作拿到子线程中执行(例如下载一些网络资源),最后返回主线程中更新UI.
GCD的使用
  • GCD(Grand Central Dispatch)是基于C语言开发的一套多线程开发机制,是完全面向过程的。
GCD 核心概念
  1. 任务添加到队列,并且指定执行任务的函数
  2. 任务使用 block 封装
    • 任务的 block 没有参数也没有返回值
  3. 执行任务的函数
    • 异步 dispatch_async
      • 不用等待当前语句执行完毕,就可以执行下一条语句
      • 会开启线程执行 block 的任务
      • 异步是多线程的代名词
    • 同步 dispatch_sync
      • 必须等待当前语句执行完毕,才会执行下一条语句
      • 不会开启线程
      • 在当前执行 block 的任务
  4. 队列 - 负责调度任务
    • 串行队列
      • 一次只能"调度"一个任务
      • dispatch_queue_create("queue", NULL);
    • 并发队列
      • 一次可以"调度"多个任务
      • dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    • 主队列
      • 专门用来在主线程上调度任务的队列
      • 不会开启线程
      • 主线程空闲时才会调度队列中的任务在主线程执行
      • dispatch_get_main_queue();
阶段性小结
  • 开不开线程由执行任务的函数决定
    • 异步开,异步是多线程的代名词
    • 同步不开
  • 开几条线程由队列决定
    • 串行队列开一条线程
    • 并发队列开多条线程,具体能开的线程数量由底层线程池决定
      • iOS 8.0 之后,GCD 能够开启非常多的线程
      • iOS 7.0 以及之前,GCD 通常只会开启 5~6 条线程
- 队列的选择
  • 多线程的目的:将耗时的操作放在后台执行!

  • 串行队列,只开一条线程,所有任务顺序执行

    • 如果任务有先后执行顺序的要求
    • 效率低 -> 执行慢 -> "省电"
    • 有的时候,用户其实不希望太快!例如使用 3G 流量,"省钱"
  • 并发队列,会开启多条线程,所有任务不按照顺序执行

    • 如果任务没有先后执行顺序的要求
    • 效率高 -> 执行快 -> "费电"
    • WIFI,包月
实际开发中,线程数量如何决定?
  • WIFI 线程数 6
  • 3G / 4G 移动开发的时候,2~3条,再多会费电费钱!
同步 & 异步
  • 同步:
    • 在当前线程中执行,必须等待当前语句执行完毕,才会执行下一条语句
    • 不在当前线程中执行,不用等待当前语句执行完毕,就可以执行下一条语句
NSThread-Demo
//点击屏幕调用
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    NSLog(@"touchesBegan开始执行");
    //同步调用
    [self start];
    
    //异步调用
    //[self performSelectorInBackground:@selector(start) withObject:nil];
    NSLog(@"touchesBegan执行结束");
}

//开始执行
- (void)start{
    NSLog(@"start开始执行");
    NSLog(@"start当前线程:%@",[NSThread currentThread]);
    //延迟5秒调用
    [NSThread sleepForTimeInterval:5];
    NSLog(@"start执行完毕");
}

执行以上代码,发现当同步的时候执行的结果为:
2015-11-26 18:17:29.621 GCD同步异步[1267:159851] touchesBegan开始执行
2015-11-26 18:17:29.621 GCD同步异步[1267:159851] start开始执行
2015-11-26 18:17:29.621 GCD同步异步[1267:159851] start当前线程:<NSThread: 0x7fec12e01780>{number = 1, name = main}
2015-11-26 18:17:34.627 GCD同步异步[1267:159851] start执行完毕
2015-11-26 18:17:34.627 GCD同步异步[1267:159851] touchesBegan执行结束

执行以上代码,发现当异步的时候执行的结果为:
2015-11-26 18:23:10.846 GCD同步异步[1282:163977] touchesBegan开始执行
2015-11-26 18:23:10.847 GCD同步异步[1282:163977] touchesBegan执行结束
2015-11-26 18:23:10.848 GCD同步异步[1282:164254] start开始执行
2015-11-26 18:23:10.848 GCD同步异步[1282:164254] start当前线程:<NSThread: 0x7fa778f0f0f0>{number = 2, name = (null)}
2015-11-26 18:23:15.849 GCD同步异步[1282:164254] start执行完毕
结论: 同步是从上到下有顺序的执行,异步则不是
GCD-Demo
//点击屏幕的时候调用
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    //异步
    //[self asyncMethod];
    //同步
    [self syncMethod];
    
}

//异步的时候调用
- (void)asyncMethod{
    //获取全局队列
    dispatch_queue_t queue=dispatch_get_global_queue(0, 0);
    //创建任务
    void(^task)()=^{
        NSLog(@"asyncMethod:%@",[NSThread currentThread]);
    };
    //执行任务
    dispatch_async(queue, task);
    
    NSLog(@"end");
}

//同步的时候调用
- (void)syncMethod{
    //获取全局队列
    dispatch_queue_t queue=dispatch_get_global_queue(0, 0);
    //创建任务
    void(^task)()=^{
        NSLog(@"syncMethod:%@",[NSThread currentThread]);
    };
    //执行任务
    dispatch_sync(queue, task);
    
    NSLog(@"end");
}

执行以上代码,发现当同步的时候执行的结果为:
2015-11-26 18:40:55.419 GCD中的同步和异步[1361:177648] syncMethod:<NSThread: 0x7fa7d14036b0>{number = 1, name = main}
2015-11-26 18:40:55.419 GCD中的同步和异步[1361:177648] end
2015-11-26 18:40:55.667 GCD中的同步和异步[1361:177648] syncMethod:<NSThread: 0x7fa7d14036b0>{number = 1, name = main}
2015-11-26 18:40:55.667 GCD中的同步和异步[1361:177648] end
2015-11-26 18:40:55.926 GCD中的同步和异步[1361:177648] syncMethod:<NSThread: 0x7fa7d14036b0>{number = 1, name = main}
2015-11-26 18:40:55.927 GCD中的同步和异步[1361:177648] end

执行以上代码,发现当异步的时候执行的结果为:
2015-11-26 18:41:36.351 GCD中的同步和异步[1370:178492] asyncMethod:<NSThread: 0x7fc941e02a70>{number = 2, name = (null)}
2015-11-26 18:41:36.351 GCD中的同步和异步[1370:178425] end
2015-11-26 18:41:36.576 GCD中的同步和异步[1370:178425] end
2015-11-26 18:41:36.576 GCD中的同步和异步[1370:178492] asyncMethod:<NSThread: 0x7fc941e02a70>{number = 2, name = (null)}
2015-11-26 18:41:36.810 GCD中的同步和异步[1370:178425] end
2015-11-26 18:41:36.810 GCD中的同步和异步[1370:178492] asyncMethod:<NSThread: 0x7fc941e02a70>{number = 2, name = (null)}

和NSThread对比可以发现
  • 所有的代码写在一起的,让代码更加简单,易于阅读和维护
    • NSThread 通过 @selector 指定要执行的方法,代码分散
    • GCD 通过 block 指定要执行的代码,代码集中
  • 使用 GCD 不需要管理线程的创建/销毁/复用的过程!程序员不用关心线程的生命周期
  • 如果要开多个线程 NSThread 必须实例化多个线程对象
  • NSThread 靠 NSObject 的分类方法实现的线程间通讯,GCD 靠 block
使用GCD下载图片
//点击下载图片
- (IBAction)downLoadButton:(id)sender {
    //获取全局队列
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
       //异步下载图片
        NSURL *url=[NSURL URLWithString:@"http://pic7.nipic.com/20100515/2001785_115623014419_2.jpg"];
        //将资源转换为二进制
        NSData *data=[NSData dataWithContentsOfURL:url];
        //将二进制转化为图片
        UIImage *image=[UIImage imageWithData:data];

        //获取主队列,更新UI
        dispatch_async(dispatch_get_main_queue(), ^{
            //给图片控件赋值
            self.imageView.image=image;
        });
    });
}

串行队列
串行队列特点:
  • 以先进先出的方式,顺序调度队列中的任务执行
  • 无论队列中指定的任务函数是同步还是异步,都会等待前一个任务执行完毕以后,再调度后面的任务
队列创建
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);

dispatch_queue_t queue = dispatch_queue_create("queue", NULL);
串行队列同步&异步Demo
//点击屏幕的时候调用

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    //同步执行
    //[self syncMethod];
    //异步执行
    [self asyncMethod];
}
//同步
- (void)syncMethod{
    //创建队列
    dispatch_queue_t queue=dispatch_queue_create("同步", DISPATCH_QUEUE_SERIAL);
    //执行任务
    
    for (int i=0; i<6; i++) {
        NSLog(@"1--->%d",i);
        
        dispatch_sync(queue, ^{
            NSLog(@"2--->%d---%@",i,[NSThread currentThread]
                  );
        });
    }
    NSLog(@"end");
}
//异步
- (void)asyncMethod{
    //创建队列
    dispatch_queue_t queue=dispatch_queue_create("异步", DISPATCH_QUEUE_SERIAL);
    //执行任务
    
    for (int i=0; i<6; i++) {
        NSLog(@"1--->%d",i);
        
        dispatch_async(queue, ^{
            NSLog(@"2--->%d---%@",i,[NSThread currentThread]
                  );
        });
    }
    NSLog(@"end");
}

串行队列 同步执行结果:
2015-11-26 19:12:20.876 串行队列的同步和异步[1520:204175] 1--->0
2015-11-26 19:12:20.877 串行队列的同步和异步[1520:204175] 2--->0---<NSThread: 0x7fe952c05820>{number = 1, name = main}
2015-11-26 19:12:20.877 串行队列的同步和异步[1520:204175] 1--->1
2015-11-26 19:12:20.877 串行队列的同步和异步[1520:204175] 2--->1---<NSThread: 0x7fe952c05820>{number = 1, name = main}
2015-11-26 19:12:20.877 串行队列的同步和异步[1520:204175] 1--->2
2015-11-26 19:12:20.877 串行队列的同步和异步[1520:204175] 2--->2---<NSThread: 0x7fe952c05820>{number = 1, name = main}
2015-11-26 19:12:20.878 串行队列的同步和异步[1520:204175] 1--->3
2015-11-26 19:12:20.878 串行队列的同步和异步[1520:204175] 2--->3---<NSThread: 0x7fe952c05820>{number = 1, name = main}
2015-11-26 19:12:20.878 串行队列的同步和异步[1520:204175] 1--->4
2015-11-26 19:12:20.878 串行队列的同步和异步[1520:204175] 2--->4---<NSThread: 0x7fe952c05820>{number = 1, name = main}
2015-11-26 19:12:20.878 串行队列的同步和异步[1520:204175] 1--->5
2015-11-26 19:12:20.878 串行队列的同步和异步[1520:204175] 2--->5---<NSThread: 0x7fe952c05820>{number = 1, name = main}
2015-11-26 19:12:20.878 串行队列的同步和异步[1520:204175] end

串行队列 异步执行结果:
2015-11-26 19:12:41.923 串行队列的同步和异步[1529:204751] 1--->0
2015-11-26 19:12:41.924 串行队列的同步和异步[1529:204751] 1--->1
2015-11-26 19:12:41.924 串行队列的同步和异步[1529:204829] 2--->0---<NSThread: 0x7fc99970bbe0>{number = 2, name = (null)}
2015-11-26 19:12:41.924 串行队列的同步和异步[1529:204751] 1--->2
2015-11-26 19:12:41.924 串行队列的同步和异步[1529:204829] 2--->1---<NSThread: 0x7fc99970bbe0>{number = 2, name = (null)}
2015-11-26 19:12:41.924 串行队列的同步和异步[1529:204751] 1--->3
2015-11-26 19:12:41.924 串行队列的同步和异步[1529:204829] 2--->2---<NSThread: 0x7fc99970bbe0>{number = 2, name = (null)}
2015-11-26 19:12:41.925 串行队列的同步和异步[1529:204751] 1--->4
2015-11-26 19:12:41.925 串行队列的同步和异步[1529:204829] 2--->3---<NSThread: 0x7fc99970bbe0>{number = 2, name = (null)}
2015-11-26 19:12:41.925 串行队列的同步和异步[1529:204751] 1--->5
2015-11-26 19:12:41.925 串行队列的同步和异步[1529:204829] 2--->4---<NSThread: 0x7fc99970bbe0>{number = 2, name = (null)}
2015-11-26 19:12:41.925 串行队列的同步和异步[1529:204751] end
2015-11-26 19:12:41.925 串行队列的同步和异步[1529:204829] 2--->5---<NSThread: 0x7fc99970bbe0>{number = 2, name = (null)}

并发队列
并发队列特点:
  • 有多个线程,操作进来之后它会将这些队列安排在可用的处理器上,同时保证先进来的任务优先处理。
  • 以先进先出的方式,并发调度队列中的任务执行
  • 如果当前调度的任务是同步执行的,会等待任务执行完成后,再调度后续的任务
  • 如果当前调度的任务是异步执行的,同时底层线程池有可用的线程资源,会再新的线程调度后续任务的执行
队列创建
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);

并行队列同步&异步Demo
//点击屏幕的时候调用
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    //同步
    //[self syncMethod];
    //异步
    [self asyncMethod];
}

//异步
- (void)asyncMethod{

    //并发队列
    dispatch_queue_t queue=dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    //创建任务
    for(int i=0; i<6 ;i++ ){
        dispatch_async(queue, ^{
            NSLog(@"async--->%d---%@",i,[NSThread currentThread]);
        });
    }
    NSLog(@"end");
}

//同步
- (void)syncMethod{

    //并发队列
    dispatch_queue_t queue=dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    //创建任务
    for(int i=0; i<6 ;i++ ){
        dispatch_sync(queue, ^{
            NSLog(@"sync--->%d---%@",i,[NSThread currentThread]);
        });
    }
    NSLog(@"end");
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

并行队列 同步执行结果:
2015-11-26 21:17:19.820 并发队列的同步与异步[1632:233685] sync--->0---<NSThread: 0x7fd762400f70>{number = 1, name = main}
2015-11-26 21:17:19.820 并发队列的同步与异步[1632:233685] sync--->1---<NSThread: 0x7fd762400f70>{number = 1, name = main}
2015-11-26 21:17:19.821 并发队列的同步与异步[1632:233685] sync--->2---<NSThread: 0x7fd762400f70>{number = 1, name = main}
2015-11-26 21:17:19.821 并发队列的同步与异步[1632:233685] sync--->3---<NSThread: 0x7fd762400f70>{number = 1, name = main}
2015-11-26 21:17:19.821 并发队列的同步与异步[1632:233685] sync--->4---<NSThread: 0x7fd762400f70>{number = 1, name = main}
2015-11-26 21:17:19.821 并发队列的同步与异步[1632:233685] sync--->5---<NSThread: 0x7fd762400f70>{number = 1, name = main}
2015-11-26 21:17:19.822 并发队列的同步与异步[1632:233685] end

并行队列 异步执行结果:
2015-11-26 21:17:45.000 并发队列的同步与异步[1641:234351] end
2015-11-26 21:17:45.000 并发队列的同步与异步[1641:234532] async--->3---<NSThread: 0x7fa823c9a910>{number = 5, name = (null)}
2015-11-26 21:17:45.000 并发队列的同步与异步[1641:234425] async--->0---<NSThread: 0x7fa823f0e720>{number = 3, name = (null)}
2015-11-26 21:17:45.000 并发队列的同步与异步[1641:234423] async--->2---<NSThread: 0x7fa823e079f0>{number = 4, name = (null)}
2015-11-26 21:17:45.000 并发队列的同步与异步[1641:234533] async--->4---<NSThread: 0x7fa823e000b0>{number = 6, name = (null)}
2015-11-26 21:17:45.000 并发队列的同步与异步[1641:234424] async--->1---<NSThread: 0x7fa823d4d4e0>{number = 2, name = (null)}
2015-11-26 21:17:45.001 并发队列的同步与异步[1641:234534] async--->5---<NSThread: 0x7fa823f0a290>{number = 7, name = (null)}

全局队列
  • 是系统为了方便程序员开发提供的,其工作表现与并发队列一致
主队列
  • 专门用来在主线程上调度任务的队列
  • 不会开启线程
  • 以先进先出的方式,在主线程空闲时才会调度队列中的任务在主线程执行
  • 如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度
//点击屏幕的时候调用
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    //全局队列同步执行
    [self global_queue_sync];
    //全局队列异步执行
    //[self global_queue_async];
    //主队列同步执行
    //[self main_queue_sync];
    //主队列异步执行
    //[self main_queue_async];
}
//主队列同步执行
- (void)main_queue_sync{
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    for (int i = 0; i < 10; ++i) {
        dispatch_sync(queue, ^{
            NSLog(@"main_queue_sync%@---->%d", [NSThread currentThread], i);
        });
    }
    NSLog(@"end");
}

//主队列异步执行
- (void)main_queue_async{
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    for (int i = 0; i < 10; ++i) {
        dispatch_async(queue, ^{
            NSLog(@"main_queue_async%@---->%d", [NSThread currentThread], i);
        });
    }
    NSLog(@"end");
}

//全局队列异步执行
- (void)global_queue_async{
    //获取全局队列
    dispatch_queue_t q = dispatch_get_global_queue(0, 0);
    
    //执行任务
    for (int i = 0; i < 10; ++i) {
        dispatch_async(q, ^{
            NSLog(@"global_queue_async%@---->%d", [NSThread currentThread], i);
        });
    }
    NSLog(@"end");
}

//全局队列同步执行
- (void)global_queue_sync{
    //获取全局队列
    dispatch_queue_t q = dispatch_get_global_queue(0, 0);
    
    //执行任务
    for (int i = 0; i < 10; ++i) {
        dispatch_sync(q, ^{
            NSLog(@"global_queue_sync%@---->%d", [NSThread currentThread], i);
        });
    }
    NSLog(@"end");
}

全局队列 同步执行结果:
2015-11-26 22:00:12.568 全局队列与主队列[1895:266991] global_queue_sync<NSThread: 0x7fb871d069e0>{number = 1, name = main}---->0
2015-11-26 22:00:12.569 全局队列与主队列[1895:266991] global_queue_sync<NSThread: 0x7fb871d069e0>{number = 1, name = main}---->1
2015-11-26 22:00:12.569 全局队列与主队列[1895:266991] global_queue_sync<NSThread: 0x7fb871d069e0>{number = 1, name = main}---->2
2015-11-26 22:00:12.569 全局队列与主队列[1895:266991] global_queue_sync<NSThread: 0x7fb871d069e0>{number = 1, name = main}---->3
2015-11-26 22:00:12.569 全局队列与主队列[1895:266991] global_queue_sync<NSThread: 0x7fb871d069e0>{number = 1, name = main}---->4
2015-11-26 22:00:12.569 全局队列与主队列[1895:266991] global_queue_sync<NSThread: 0x7fb871d069e0>{number = 1, name = main}---->5
2015-11-26 22:00:12.569 全局队列与主队列[1895:266991] global_queue_sync<NSThread: 0x7fb871d069e0>{number = 1, name = main}---->6
2015-11-26 22:00:12.569 全局队列与主队列[1895:266991] global_queue_sync<NSThread: 0x7fb871d069e0>{number = 1, name = main}---->7
2015-11-26 22:00:12.569 全局队列与主队列[1895:266991] global_queue_sync<NSThread: 0x7fb871d069e0>{number = 1, name = main}---->8
2015-11-26 22:00:12.569 全局队列与主队列[1895:266991] global_queue_sync<NSThread: 0x7fb871d069e0>{number = 1, name = main}---->9
2015-11-26 22:00:12.570 全局队列与主队列[1895:266991] end

全局队列 异步执行结果:
2015-11-26 22:01:01.565 全局队列与主队列[1908:268194] end
2015-11-26 22:01:01.566 全局队列与主队列[1908:268233] global_queue_async<NSThread: 0x7f97fb607e80>{number = 2, name = (null)}---->1
2015-11-26 22:01:01.566 全局队列与主队列[1908:268234] global_queue_async<NSThread: 0x7f97fb61f710>{number = 4, name = (null)}---->2
2015-11-26 22:01:01.566 全局队列与主队列[1908:268256] global_queue_async<NSThread: 0x7f97fb63bec0>{number = 5, name = (null)}---->3
2015-11-26 22:01:01.566 全局队列与主队列[1908:268257] global_queue_async<NSThread: 0x7f97fb7127d0>{number = 6, name = (null)}---->4
2015-11-26 22:01:01.566 全局队列与主队列[1908:268232] global_queue_async<NSThread: 0x7f97fb70bde0>{number = 3, name = (null)}---->0
2015-11-26 22:01:01.566 全局队列与主队列[1908:268233] global_queue_async<NSThread: 0x7f97fb607e80>{number = 2, name = (null)}---->5
2015-11-26 22:01:01.566 全局队列与主队列[1908:268234] global_queue_async<NSThread: 0x7f97fb61f710>{number = 4, name = (null)}---->6
2015-11-26 22:01:01.566 全局队列与主队列[1908:268258] global_queue_async<NSThread: 0x7f97fb442510>{number = 7, name = (null)}---->7
2015-11-26 22:01:01.566 全局队列与主队列[1908:268256] global_queue_async<NSThread: 0x7f97fb63bec0>{number = 5, name = (null)}---->8
2015-11-26 22:01:01.567 全局队列与主队列[1908:268257] global_queue_async<NSThread: 0x7f97fb7127d0>{number = 6, name = (null)}---->9

主队列 同步执行结果:
主队列和主线程相互等待会造成死锁,程序直接卡死
主队列 异步执行结果:
2015-11-26 22:02:09.057 全局队列与主队列[1935:269576] end
2015-11-26 22:02:09.058 全局队列与主队列[1935:269576] main_queue_async<NSThread: 0x7fadd850a320>{number = 1, name = main}---->0
2015-11-26 22:02:09.058 全局队列与主队列[1935:269576] main_queue_async<NSThread: 0x7fadd850a320>{number = 1, name = main}---->1
2015-11-26 22:02:09.058 全局队列与主队列[1935:269576] main_queue_async<NSThread: 0x7fadd850a320>{number = 1, name = main}---->2
2015-11-26 22:02:09.058 全局队列与主队列[1935:269576] main_queue_async<NSThread: 0x7fadd850a320>{number = 1, name = main}---->3
2015-11-26 22:02:09.058 全局队列与主队列[1935:269576] main_queue_async<NSThread: 0x7fadd850a320>{number = 1, name = main}---->4
2015-11-26 22:02:09.059 全局队列与主队列[1935:269576] main_queue_async<NSThread: 0x7fadd850a320>{number = 1, name = main}---->5
2015-11-26 22:02:09.059 全局队列与主队列[1935:269576] main_queue_async<NSThread: 0x7fadd850a320>{number = 1, name = main}---->6
2015-11-26 22:02:09.059 全局队列与主队列[1935:269576] main_queue_async<NSThread: 0x7fadd850a320>{number = 1, name = main}---->7
2015-11-26 22:02:09.059 全局队列与主队列[1935:269576] main_queue_async<NSThread: 0x7fadd850a320>{number = 1, name = main}---->8
2015-11-26 22:02:09.087 全局队列与主队列[1935:269576] main_queue_async<NSThread: 0x7fadd850a320>{number = 1, name = main}---->9

NSOperation
  • NSOperation有两个常用子类用于创建线程操作:NSInvocationOperation和NSBlockOperation,两种方式本质没有区别,但是是后者使用Block形式进行代码组织,使用相对方便。
NSOperation-Demo
//当点击屏幕的时候调用
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    //创建url
    NSURL *url=[NSURL URLWithString:@"http://pic7.nipic.com/20100515/2001785_115623014419_2.jpg"];
    //将资源转换为二进制
    NSData *data=[NSData dataWithContentsOfURL:url];
    //创建operation
    NSInvocationOperation *operation=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(downLoadImage:) object:data];
    //执行操作
    [operation start];
}

- (void)downLoadImage:(NSData *)data{
    //将二进制转化为图片
    UIImage *image=[UIImage imageWithData:data];
    //显示图片
    self.imageView.image=image;
    
}

将任务添加到队里中执行
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{

    //创建队列
    NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
    //创建任务
    NSInvocationOperation *operation=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(downLoadImage) object:nil];
        NSLog(@"1--->%@",[NSThread currentThread]);
    //将任务添加到队列中
    [operationQueue addOperation:operation];
}

//子线程中下载图片
- (void)downLoadImage{
    //创建url
    NSURL *url=[NSURL URLWithString:@"http://pic7.nipic.com/20100515/2001785_115623014419_2.jpg"];
    //将资源转换为二进制
    NSData *data=[NSData dataWithContentsOfURL:url];
    NSLog(@"2--->%@",[NSThread currentThread]);
    //将二进制转化为tup
    UIImage *image=[UIImage imageWithData:data];
    [self performSelectorOnMainThread:@selector(updateImage:) withObject:image waitUntilDone:YES];
}

//主线程中更新图片
- (void)updateImage:(UIImage *)image{
     NSLog(@"3--->%@",[NSThread currentThread]);
    self.imageView.image=image;
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

总结:无论使用何种方式进行多线程开发,都应该遵循一定的规则(尽可能将比较耗时比如去网络下载资源的操作放到子线程中执行),注重程序的执行效率以及用户的体验合理的使用多线程(线程虽好,可不要乱用噢).

以上Demo下载路径:https://github.com/chengaojian**

以上就是多线程中常用的一些介绍,由于时间比较仓促,很多例子都是现写,如果代码中存在错误,还请指出,谢谢.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容