多线程简介

1、关于线程的概念

学习多线程,是为了将一些耗时操作放到后台,以提高用户体验。

  • 进程
    进程可以理解成是系统中正在运行的一个应用程序。
    进程是系统进行资源分配和调度的基本单位。
    每个进程是独立的,每个进程均是运行在其专用且受保护的内存空间里。
  • 线程
    一个进程要想执行任务,必须得有线程(每个进程至少要有一条线程)
    线程是进程的基本执行单元,一个进程(程序)的所有任务都在线程中执行

也就是说,进程只是在内存中开辟了一个内存空间中而已,但是线程才是正真执行任务的

  • 线程的串行
    如果要在一个线程中执行多个任务,那么只能一个一个的按照顺序来执行这些任务,也就是说在同一时间内,1个线程只能执行一个任务
    因此,也可认为线程是进程中的一条执行路径。

  • 多线程
    1个进程中可以开启多条线程,每条线程可以并发(同时)执行不同的任务 ,多线程可以提高程序的执行效率,尤其是在下载的地方常用。
    类比:进程——车间 ,线程——车间工人

  • 多线程的原理
    同一时间,CPU只能处理一条线程,只有1条线程在工作(执行),但是CPU执行的速度是非常快的,能够快速的在多条线程时间调度(切换),切换到哪个线程,哪个线程就会执行一点。
    如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象(其实只是在不同的线程之间切换而已)

  • 多线程的优点
    能适当提高程序的执行效率
    能适当提高资源利用率(CPU、内存利用率)

  • 多线程的缺点
    开启线程需要占用一定的内存空间(默认情况下,主线程占用1M,子线程占用512KB),如果开启大量的线程,会占用大量的内存空间,降低程序的性能
    线程越多,CPU在调度线程上的开销就越大
    程序设计更加复杂:比如线程之间的通信、多线程的数据共享

  • 什么是主线程
    一个iOS程序运行后,默认会开启1条线程,称为“主线程”或“UI线程”

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

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

2、线程的状态与生命周期
屏幕快照 2018-11-25 下午3.48.08.png

下面分别阐述线程生命周期中的每一步
新建:
实例化线程对象
就绪:
向线程对象发送start消息,线程对象被加入可调度线程池等待CPU调度。
运行:
CPU 负责调度可调度线程池中线程的执行。线程执行完成之前,状态可能会在就绪和运行之间来回切换。就绪和运行之间的状态变化由CPU负责,程序员不能干预。
阻塞:
当满足某个预定条件时,可以使用休眠或锁,阻塞线程执行。sleepForTimeInterval(休眠指定时长),sleepUntilDate(休眠到指定日期),@synchronized(self):(互斥锁)。
死亡:
正常死亡,线程执行完毕。非正常死亡,当满足某个条件后,在线程内部中止执行/在主线程中止线程对象
当死亡后,线程便从内存中消失了,不能重新被调用
线程的exit和cancel
[NSThread exit]:一旦强行终止线程,后续的所有代码都不会被执行。
[thread cancel]取消:并不会直接取消线程,只是给线程对象添加 isCancelled 标记。
注意:
线程的各种状态以及其转换的条件
线程在创建之后,只有在可调度线程池中,才能被调用,
当就绪、运行的状态下,线程存在于可调度线程池
当阻塞状态是,线程存在于内存中
当死亡后,线程便从内存中消失了,不能重新被调用

3、多线程的四种解决方案
屏幕快照 2018-11-24 下午5.53.03.png
  • <1>pthread 的用法
    pthread是C语言实现的方案,一般很少用
#import "ViewController.h"
#import <pthread.h>//需要导入头文件

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //使用pthread创建线程 : pthread_create
    
    // pthread_create的参数说明:
    //参数1:线程变量
    //参数2:线程属性
    //参数3:线程要执行的函数(要在这个子线程中执行的任务)
    //参数4:执行任务需要的参数
    
    pthread_t threadId;//声明一个线程变量,第一个参数
    id str=@"hello"; //第四个参数,id需要转换成void *类型的,因为run方法里面是使用这种类型的参数的,所以使用使用__bridge 侨联
    pthread_create(&threadId, NULL, run, (__bridge void *)str);
}

//用作第3个属性参数
void *run(void *prama){   //void类型的不用return,但是void * 是一定要return的,void * 相当于oc 中的id
    NSString *str=(__bridge NSString *)(prama);
    for(int i=0;i<4;i++){
        NSLog(@"%@  %@" ,[NSThread currentThread],str);
    }
    return NULL;
}

@end

打印结果:
===============================================
pthread[1620:149861] <NSThread: 0x600001ca47c0>{number = 3, name = (null)}  hello
pthread[1620:149861] <NSThread: 0x600001ca47c0>{number = 3, name = (null)}  hello
pthread[1620:149861] <NSThread: 0x600001ca47c0>{number = 3, name = (null)}  hello
pthread[1620:149861] <NSThread: 0x600001ca47c0>{number = 3, name = (null)}  hello

number=1表示在主线程,number = 3表示当前线程是子线程
  • <2>NSThread的用法
    相较pthread来说,NSThread使用起来更简单快捷,但是无法对线程进行更详细的设置
#pragma mark -创建第一个线程
-(void)test1{
    //实例化一个线程对象,该方法有返回值,返回线程对象
    NSThread *thread=[[NSThread alloc] initWithTarget:self selector:@selector(run1) object:nil];
    //启动线程,在新开的线程中执行run方法
    [thread start];
}

-(void)run1{
    for (int i=0; i<4; i++) {
        NSLog(@"当前线程:%@,序号:%d",[NSThread currentThread],i);
    }
}



#pragma mark -创建第二个线程
-(void)test2{
    //创建线程,该方法没有返回值
    [NSThread detachNewThreadSelector:@selector(run2:) toTarget:self withObject:@"test2"];
}

-(void)run2:(NSString *)str{
    for (int i=0; i<4; i++) {
        NSLog(@"当前线程:%@,序号:%d,参数:%@",[NSThread currentThread],i,str);
    }
}



#pragma mark -创建第三个线程
-(void)test3{
    //创建线程,该方法没有返回值
    [self performSelectorInBackground:@selector(run3:) withObject:@"test3"];
}

-(void)run3:(NSString *)str{
    for (int i=0; i<4; i++) {
        NSLog(@"当前线程:%@,序号:%d,参数:%@",[NSThread currentThread],i,str);
    }
}



#pragma mark -创建第四个线程:设置线程的属性
-(void)test4{
    NSThread *newThread=[[NSThread alloc] initWithTarget:self selector:@selector(run4:) object:@"test4"];
    newThread.name=@"threadA";//线程的名字
    newThread.threadPriority=0.1;//线程优先级, 是一个浮点数, 0.0~1.0, 默认值是0.5, 数值越大,调度的优先级高, 优先级必须很多次的时候才能体现出来
    [newThread start];
}

-(void)run4:(NSString *)str{
    for (int i=0; i<4; i++) {
        NSLog(@"%@ -----%d----%@",[NSThread currentThread],i ,str);
    }
}
@end

NSThread的常用方法:

   //实例化一个线程对象  start方法启动线程
    NSThread *thread=[[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
    [thread start];
    
    //创建线程后自动启动线程
    [NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
    
    //隐式创建并启动线程
    [self performSelectorInBackground:@selector(run) withObject:nil];

    //线程的名字
    - (void)setName:(NSString *)n;
    - (NSString *)name;

    // 获得主线程,判断是否为主线程
    + (NSThread *)mainThread;
    - (BOOL)isMainThread; 
    + (BOOL)isMainThread; 
    
    //获得当前线程
    NSThread *current = [NSThread currentThread];
    
    //线程的调度优先级(调度优先级的取值范围是0.0 ~ 1.0,默认0.5,值越大,优先级越高,自己开发时,建议一般不要修改优先级)
    + (double)threadPriority;
    + (BOOL)setThreadPriority:(double)p;
    - (double)threadPriority;
    - (BOOL)setThreadPriority:(double)p;
  • <3>GCD的用法
/**
 *  串行队列的同步执行
 *  同步执行:不会开启新线程,在原来的线程(主线程)中一个一个的执行
 */
-(void)gcdTest1{
    //1.创建一个串行队列    参数1:队列标签  参数2:队列属性  SERIAL连续的意思
    dispatch_queue_t queue=dispatch_queue_create("itcast", DISPATCH_QUEUE_SERIAL);
    //2.执行任务
    //一般只要是使用同步执行,串行队列对添加的同步任务,会立马执行,所以先打印”线程“信息,然后打印"完成"信息
    for (int i=0; i<4; i++) {
        dispatch_sync(queue, ^{
            NSLog(@"线程:%@,序号:%d",[NSThread currentThread],i);
        });
    }
    NSLog(@"完成 %@",[NSThread currentThread]);
}

打印结果
======================================
线程:<NSThread: 0x60000339a940>{number = 1, name = main},序号:0
线程:<NSThread: 0x60000339a940>{number = 1, name = main},序号:1
线程:<NSThread: 0x60000339a940>{number = 1, name = main},序号:2
线程:<NSThread: 0x60000339a940>{number = 1, name = main},序号:3
完成 <NSThread: 0x600003855400>{number = 1, name = main}
/**
 *  串行队列的异步执行
 *  异步执行:会开启新线程,在新线程中执行,因为是串行,在队列中一个一个的取任务,所以你开启更多个新线程也没有意义,就只开启一个新线程
 */
-(void)gcdTest2{
    //1.串行队列
    dispatch_queue_t queue=dispatch_queue_create("itcast", DISPATCH_QUEUE_SERIAL);
    //2.异步执行
    //因为是异步执行,又是串行一个一个的去执行,所以会先执行主任务里面的"完成"信息,然后再执行异步任务“线程”信息
    for (int i=0; i<4; i++) {
        dispatch_async(queue, ^{
            NSLog(@"线程:%@,序号:%d",[NSThread currentThread],i);
        });
    }
    NSLog(@"完成 %@",[NSThread currentThread]);
}

打印结果
======================================
完成 <NSThread: 0x600001a06fc0>{number = 1, name = main}
线程:<NSThread: 0x6000013c1780>{number = 3, name = (null)},序号:0
线程:<NSThread: 0x6000013c1780>{number = 3, name = (null)},序号:1
线程:<NSThread: 0x6000013c1780>{number = 3, name = (null)},序号:2
线程:<NSThread: 0x6000013c1780>{number = 3, name = (null)},序号:3
/**
 *  并发队列的异步执行
 *  异步执行:一定会开辟新线程的,并发的会开启多个线程
 */
-(void)gcdTest3{
    //1.创建一个并行队列    参数1:队列标签  参数2:队列属性  CONCURRENT并发的意思
    dispatch_queue_t queue=dispatch_queue_create("cz", DISPATCH_QUEUE_CONCURRENT);
    //2.执行
    //因为是异步执行,又是并发执行,所以打印“线程”信息的顺序结果不确定,不过先打印出主线程的“完成”信息
    for (int i=0; i<4; i++) {
        dispatch_async(queue, ^{
            NSLog(@"线程:%@,序号:%d",[NSThread currentThread],i);
        });
    }
    NSLog(@"完成 %@",[NSThread currentThread]);
}

打印结果
======================================
线程信息顺序不确定
/**
 *并发队列的同步执行
 *同步执行:不会开启线程,在原线程中执行任务,虽然是并行,但只有一个线程,还是一条一条的按照顺序执行
 */
-(void)gcdTest4{
    //1.创建一个并行队列
    dispatch_queue_t queue=dispatch_queue_create("zc", DISPATCH_QUEUE_CONCURRENT);
    //2、同步就不会开启新的线程,在原来的线程里面一个一个的执行
    for (int i=0; i<4; i++) {
        dispatch_sync(queue, ^{
            NSLog(@"线程:%@,序号:%d",[NSThread currentThread],i);
        });
    }
    NSLog(@"完成 %@",[NSThread currentThread]);
}

打印结果
======================================
线程:<NSThread: 0x60000338e900>{number = 1, name = main},序号:0
线程:<NSThread: 0x60000338e900>{number = 1, name = main},序号:1
线程:<NSThread: 0x60000338e900>{number = 1, name = main},序号:2
线程:<NSThread: 0x60000338e900>{number = 1, name = main},序号:3
完成 <NSThread: 0x60000338e900>{number = 1, name = main}
/**
 * 主队列的异步执行
 * 主队列:专门负责在主线程上调度任务,不会在子线程上调度任务
 * 主队列的特点:不允许开启新线程,主队列的权限大
 * 异步执行:会开启新线程,但是在主队列中无法开启新线程
 *
 *结果:没有问题,等到test5在主线程上执行结束之后,里面的任务才开始放到主线程上执行,"完成"打印出来,才开始执行定义的异步任务任务
 */
-(void)gcdTest5{
    //1.获得主队列 -> 程序启动 -> 至少有一个主线程 -> 一开始就创建主队列
    dispatch_queue_t queue=dispatch_get_main_queue();
    //2.异步执行任务:把任务放到主队列,但是不需要马上执行
    for (int i=0; i<4; i++) {
        NSLog(@"调度前,序号:%d",i);
        dispatch_async(queue, ^{
            NSLog(@"线程:%@,序号:%d",[NSThread currentThread],i);
        });
        NSLog(@"睡会,序号:%d",i);
        [NSThread sleepForTimeInterval:1.0];
    }
    NSLog(@"完成 %@",[NSThread currentThread]);
}

打印结果
======================================
调度前,序号:0
睡会,序号:0
调度前,序号:1
睡会,序号:1
调度前,序号:2
睡会,序号:2
调度前,序号:3
睡会,序号:3
完成 <NSThread: 0x600002a80000>{number = 1, name = main}
线程:<NSThread: 0x600002a80000>{number = 1, name = main},序号:0
线程:<NSThread: 0x600002a80000>{number = 1, name = main},序号:1
线程:<NSThread: 0x600002a80000>{number = 1, name = main},序号:2
线程:<NSThread: 0x600002a80000>{number = 1, name = main},序号:3
/**
 *  主队列的同步执行
 *  同步执行:是需要马上执行的,不会等待
 *  结果:只有一个主队列,所以主队列上的gcdTest6方法一直占据着,但是又因为是同步的,所以一直在等着下面的任务执行,但是任务有等着gcdTest6执行,所以会造成死锁
 */
-(void)gcdTest6{
    dispatch_queue_t queue=dispatch_get_main_queue();
    for (int i=0; i<10; i++) {
        NSLog(@"调度前,序号:%d",i);
        dispatch_sync(queue, ^{
            NSLog(@"线程:%@,序号:%d",[NSThread currentThread],i);
        });
        NSLog(@"睡会,序号:%d",i);
        [NSThread sleepForTimeInterval:1.0];
    }
    NSLog(@"完成%@",[NSThread currentThread]);
}

打印结果
======================================
卡死啦
#pragma mark - 线程之间的通信

/**
线程之间的通信
*/
dispatch_async(dispatch_get_global_queue(0, 0), ^{// 异步执行
      //耗时操作放在这里
        
      dispatch_async(dispatch_get_main_queue(), ^{//回到主线程
          //一般在主线程处理UI
      });
 });
#pragma mark - gcd的延迟方法

    //方法一:NSObject的延迟方法
    [self performSelector:@selector(run) withObject:nil afterDelay:2.0];
    
    //方法二:GCD的延迟方法
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        
    });
#pragma mark - gcd的调度组

/**
     队列组
     分别异步执行多个任务,要等到这些任务全部都执行完毕后,才回到主线程
     这样的时候一般会用到队列组
     */
    
    //实例化一个调度组
    dispatch_group_t group=dispatch_group_create();
    //创建一个队列
    dispatch_queue_t queue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    //把任务添加到队列queue中
    dispatch_group_async(group, queue, ^{
        NSLog(@"下载小说A %@",[NSThread currentThread]);
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"下载小说B %@",[NSThread currentThread]);
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"下载小说C %@",[NSThread currentThread]);
    });
    
    //一:获得调度组里面的所有异步任务完成的通知
    dispatch_group_notify(group, queue, ^{
        //这里是异步的,无法知道他在哪个线程里执行,可能会开辟一个新的线程,也可能在原来的哪个线程里执行
        NSLog(@"下载完成,请观看--%@",[NSThread currentThread]);
    });
    
    //二:指定一个queue,所以可以跨队列通信,这样就可以在主线程里进行更新UI等操作
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        //在主线程,完成下列操作,所以线程的number=1
        NSLog(@"下载完成 -%@",[NSThread currentThread]);
    });
#pragma mark - gcd的一次性执行

  /**GCD实现代码只执行一次
    使用dispatch_once能保证某段代码在程序运行过程中只被执行1次。可以用来设计单例。
     */
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSLog(@"程序运行过程中我只执行了一次!");
    });
  • <4>NSOperation的用法
    NSOperation是将GCD进行封装起来使用的
#pragma mark - NSInvocationOperation的使用

-(void)opTest1{
    //1、创建操作
    NSOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run1:) object:@"opTest1"];
    //2、执行任务:这样直接在当前线程中执行,没有意义
    [op start];
}

-(void)run1:(id)obj{
    NSLog(@"当前线程:%@,参数:%@",[NSThread currentThread],obj);
}

/**
 打印结果
 ==========================================
 当前线程:<NSThread: 0x600001a0e940>{number = 1, name = main},参数:opTest1
 */



-(void)opTest2{
    //1、创建主队列,mainQueue跟GCD里面的主队列一样
    NSOperationQueue *queue = [NSOperationQueue mainQueue];
    //2、创建操作
    for (int i=0; i<4; i++) {
        NSOperation *op=[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run2:) object:@"opTest2"];
        [queue addOperation:op];
    }
    NSLog(@"完成%@",[NSThread currentThread]);
}

-(void)run2:(id)obj{
    NSLog(@"当前线程:%@,参数:%@",[NSThread currentThread],obj);
}

/**
 打印结果
 ==========================================
完成<NSThread: 0x600001159400>{number = 1, name = main}
当前线程:<NSThread: 0x600001159400>{number = 1, name = main},参数:opTest2
当前线程:<NSThread: 0x600001159400>{number = 1, name = main},参数:opTest2
当前线程:<NSThread: 0x600001159400>{number = 1, name = main},参数:opTest2
当前线程:<NSThread: 0x600001159400>{number = 1, name = main},参数:opTest2
 */



-(void)opTest3{
    //1、创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    //2、创建多个任务,并把多个操作放到队列中
    for (int i=0; i<4; i++) {
        NSOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run3:) object:@"opTest3"];
        [queue addOperation:op];
    }
    NSLog(@"完成%@",[NSThread currentThread]);
}

-(void)run3:(id)obj{
    NSLog(@"当前线程:%@,参数:%@",[NSThread currentThread],obj);
}

/**
 打印结果
 ==========================================
完成<NSThread: 0x60000287e900>{number = 1, name = main}
当前线程:<NSThread: 0x6000038cd7c0>{number = 5, name = (null)},参数:opTest3
当前线程:<NSThread: 0x6000038cd780>{number = 3, name = (null)},参数:opTest3
当前线程:<NSThread: 0x6000038c4a80>{number = 4, name = (null)},参数:opTest3
当前线程:<NSThread: 0x6000038cd840>{number = 6, name = (null)},参数:opTest3
 */
#pragma mark - NSBlockOperation的使用

-(void)opTest4{
    //1、创建队列
    NSOperationQueue *queue=[[NSOperationQueue alloc] init];
    //2、创建多个任务,并多个操作放到队列中
    for (int i=0; i<4; i++) {
        NSOperation *op=[NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"当前线程:%@,序号:%d",[NSThread currentThread],i);
        }];
        [queue addOperation:op];
    }
    NSLog(@"完成%@",[NSThread currentThread]);
}

/**
 打印结果
=======================================================
完成<NSThread: 0x600001eb5400>{number = 1, name = main}
当前线程:<NSThread: 0x6000031bd040>{number = 4, name = (null)},序号:2
当前线程:<NSThread: 0x6000031bcf80>{number = 6, name = (null)},序号:1
当前线程:<NSThread: 0x6000031a48c0>{number = 3, name = (null)},序号:0
当前线程:<NSThread: 0x6000031a6340>{number = 5, name = (null)},序号:3
*/



-(void)opTest5{
    //1、创建主队列,mainQueue跟GCD里面的主队列一样
    NSOperationQueue *queue=[NSOperationQueue mainQueue];
    //2、生成多个操作,并添加到队列中
    for (int i=0; i<4; i++) {
        NSBlockOperation *op=[NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"当前线程:%@,序号:%d",[NSThread currentThread],i);
        }];
        [queue addOperation:op];
    }
    NSLog(@"完成%@",[NSThread currentThread]);
}

/**
 打印结果
先打印出“完成”信息,然后再打印线程信息
结果:在主队列上执行代码,只有一条线程,所以先执行完毕opTest4之后,再在主线程上执行新建的4个任务,一个一个执行
=======================================================
完成<NSThread: 0x600001f95400>{number = 1, name = main}
当前线程:<NSThread: 0x600001f95400>{number = 1, name = main},序号:0
当前线程:<NSThread: 0x600001f95400>{number = 1, name = main},序号:1
当前线程:<NSThread: 0x600001f95400>{number = 1, name = main},序号:2
当前线程:<NSThread: 0x600001f95400>{number = 1, name = main},序号:3
*/
#pragma mark -NSBlockOperation的更简单的使用

-(void)opTest6{
    //1.创建队列
    NSOperationQueue *queue=[[NSOperationQueue alloc] init];
    
    NSBlockOperation *op1=[NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"当前线程:%@----op1",[NSThread currentThread]);
    }];
    [queue addOperation:op1];
    
    //NSBlockOperation里面还有一个方法 addExecutionBlock 可以在操作里面再添加操作,其前提是:op1是NSBlockOperation类的对象
    [op1 addExecutionBlock:^{
        NSLog(@"当前线程:%@----op1可以再添加操作",[NSThread currentThread]);
    }];
}

/**
 打印结果
 ==================================================================
 当前线程:<NSThread: 0x60000266dd00>{number = 4, name = (null)}----op1可以再添加操作
 当前线程:<NSThread: 0x60000266dc40>{number = 3, name = (null)}----op1
 */


-(void)opTest7{
    //1.创建队列
    NSOperationQueue *queue=[[NSOperationQueue alloc] init];
    for (int i=0; i<4; i++) {
        //2、向队列添加任务
        [queue addOperationWithBlock:^{
            NSLog(@"当前线程:%@,序号:%d",[NSThread currentThread],i);
        }];
    }
}

/**
 打印结果
==================================================================
当前线程:<NSThread: 0x600001a42680>{number = 5, name = (null)},序号:3
当前线程:<NSThread: 0x600001a46b00>{number = 4, name = (null)},序号:1
当前线程:<NSThread: 0x600001a48600>{number = 6, name = (null)},序号:2
当前线程:<NSThread: 0x600001a42640>{number = 3, name = (null)},序号:0
*/
#pragma mark - NSBlockOperation线程间的通信
//如果要做线程间的通信,可以使用[NSOperationQueue mainQueue]拿到主队列,往主队列中添加操作(更新UI)

-(void)opTest8{
    NSOperationQueue *queue=[[NSOperationQueue alloc] init];
    [queue addOperationWithBlock:^{
        NSLog(@"耗时操作...%@",[NSThread currentThread]);
        
        //在主线程执行下列操作,比如更新UI
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            NSLog(@"更新UI  ...%@",[NSThread currentThread]);
        }];
    }];
}
#pragma mark -NSOperation的最大并发数

-(void)opTest9{
    //最大并发数并不是指线程数,而是指同一时间内只能有几个任务能同时进行
    NSOperationQueue *queue=[[NSOperationQueue alloc] init];
    queue.maxConcurrentOperationCount=2;
    
    for (int i=0; i<100; i++) {
        NSOperation *op=[NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"%@--%d",[NSThread currentThread],i);
            [NSThread sleepForTimeInterval:1.0];
        }];
        [queue addOperation:op];
    }
}
#pragma mark -NSOperation的挂起、取消全部操作

NSOperationQueue *queue=[[NSOperationQueue alloc] init];
//queue.operationCount : 操作队列里面的操作数量
//queue.suspended : 布尔类型,YES代表暂停,NO表示恢复队列,默认状态是0,也就是NO


//对应暂停、继续按钮
-(void)opTest10{
    //判断当前操作队列里面的操作数量。假如没有操作,这条队列里面就不要有暂停和继续的功能,假如有操作,我们就需要有暂停和继续的功能
    if(queue.operationCount==0){
        NSLog(@"没有操作");
        return;
    }
    
    //暂停和继续
    //suspended是布尔类型的,YES代表暂停,NO表示恢复队列,默认状态是0,也就是NO
    queue.suspended=!queue.suspended;
    if (queue.suspended) {
        NSLog(@"暂停");
    }else{
        NSLog(@"继续");
    }
}


//对应取消操作
-(void)opTest11{
    //取消队列的所有操作,取消操作并不会影响队列的挂起状态
    //这里需要知道的就是,取消的是队列里面的操作,但是线程中正在执行的是无法取消的
    [queue cancelAllOperations];
    NSLog(@"取消队列里面的所有操作");
}
#pragma mark -NSOperation的依赖关系

//1.下载一个小说的安装包,   2.解压缩,删除压缩包   3.更新UI
NSOperationQueue *queue=[[NSOperationQueue alloc] init];

-(void)dependecy{
    NSBlockOperation *op1=[NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"1.下载一个小说的压缩包,%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *op2=[NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"2.解压缩,删除压缩包 %@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *op3=[NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"3.更新UI %@",[NSThread currentThread]);
    }];
    
    //制定依赖关系,op2要等op1执行完毕,op3要等待op2执行完毕,但是这里的op3是在主线程中执行的,所以要等到Come here打印出来,才会打印op3的操作
    //依赖关系是可以跨队列的,(像按照顺序的在子线程执行op1,op2,然后在主线程上执行op3,这就是跨队列的)
    [op2 addDependency:op1];
    [op3 addDependency:op2];
    
  //WaitUntilFinished类似于GCD的调度组的通知,NO就不会等待队列里面的任务执行,直接打印Come here ,
  //要是是YES的话就会等待3个任务执行完毕再打印“Come here”,但是因为此处的op3是在主线程中执行,所以会在打印出“Come here”后才会执行op3
  //在queue中执行op1、op2  
  [queue addOperations:@[op1,op2] waitUntilFinished:YES];
    
    //在主线程执行op3,更新UI
    [[NSOperationQueue mainQueue] addOperation:op3];
    NSLog(@"Come here");
}
4、总结
  • <1>你理解的多线程?
    多线程就是说在一个进程(iOS中可以理解为应用程序)中,除了主线程之外,还可以开辟些其他进程,用于处理一些耗时操作。

  • <2>iOS的多线程方案有哪几种?
    四种:pthread、NSThread、GCD、NSOperation。
    其他三种都是在pthread的基础上实现的,一般使用GCD就足够了,当有很多高级用法的时候,才使用NSOperation。
    具体区别见上面详解。

  • <3> NSOperation 和 GCD 的区别
    (1)、GCD将任务通过block的方式添加到队列(队列分为:串行、并发(全局)),指定执行任务的方法(同步(阻塞)、异步)
    GCD中的线程通信是拿到主队列dispatch_main_queue,在主队列中更新UI
    (2)、NSOPeration将任务添加到队列(并发(全局)、异步)
    [NSOpeartionQueue mainQueue]主队列,任务添加到主队列中,就会在主线程执行

GCD中可以做到一次性执行dispatch_once_t、延迟执行、调度组,NSOPeration不好实现
NSOPeration提供了一些GCD不好实现的功能:最大并发数、暂停和继续、取消全部任务、依赖关系

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

推荐阅读更多精彩内容