美文网首页
ios-多线程(NSThread,GCD,NSOperation

ios-多线程(NSThread,GCD,NSOperation

作者: 忽然之间_1988丶 | 来源:发表于2017-08-10 15:45 被阅读0次

    线程:

    英文:Thread
    线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪阻塞运行三种基本状态。就绪状态是指线程具备运行的所有条件,逻辑上可以运行,在等待处理机;运行状态是指线程占有处理机正在运行;阻塞状态是指线程在等待一个事件(如某个信号量),逻辑上不可执行。每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。
    线程是程序中一个单一的顺序控制流程。进程内一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指运行中的程序的调度单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程。 -----百度百科

    ios中实现多线程的几种方式:

    • Pthreads(不用)
    • NSThread(用一部分,其中几个比较方便的方法)
    • GCD(常用)
    • NSOperation&NSOperationQueue(看需求)
    - Pthreads

    pthread 是 POSIX 多线程开发框架,是基于C 语言的跨平台框架。没用过,不了解,感兴趣的同学可以自己度娘下。

    - NSThread

    NSThread是基于Thread使用,轻量级的多线程编程方法,一个NSThread对象代表一个线程,需要手动管理线程的生命周期,处理线程同步等问题。所以一般只使用其中几个方法,方便调试线程。
    [NSThread isMainThread]; // 是否主线程
    [NSThread currentThread]; // 当前线程

    - GCD

    Apple基本c++开发的一套多线程处理技术,自动管理生命周期。

    任务与队列

    任务:你要执行的操作,GCD将任务放在block中。执行任务有2中方式,同步执行,异步执行。

    • 同步
      不具备开启线程的能力,会阻塞当前线程。
    • 异步
      具备开启新线程的能力,不会阻塞当前线程。

    队列:用来存放任务的队列,是一种特殊的线性表,采用FIFO(先进先出)的原则,则从顶部开始读取任务,从尾部加入任务到队列。在GCD中有3种队列:串行队列,并行队列,主队列(特殊的串行队列)。

    • 串行队列
      一个一个任务有序执行,上一个任务没执行完毕,下一个任务不会执行。
    • 并行队列
      同时执行多个任务,不用等待上一个任务执行完毕。
    • 主队列
      和串行队列一样,需要等待上一个任务执行完成,才能执行下一个任务。
    创建队列:
    • 全局并行队列(系统自带的,全局唯一)
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    • 自定义并行队列:
    dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
    
    • 自定义串行队列:
    dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
    
    • 主队列:
    dispatch_get_main_queue()
    
    创建任务:
    • 同步任务
    dispatch_sync(队列, ^{要执行的任务});
    
    • 异步任务
    dispatch_async(队列, ^{要执行的任务});
    

    基本使用:

    • 同步任务+串行队列(不会开启新线程,在当前线程中执行任务)
    dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(queue, ^{
            NSLog(@"当前线程----->%@",[NSThread currentThread]);
     });
    
    • 同步任务+并行队列(不会开启新线程,在当前线程中执行任务)
    dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSLog(@"当前线程----->%@",[NSThread currentThread]);
     });
    
    • 同步任务+主队列(不会开启新线程,在主线程中执行任务)
    dispatch_sync(dispatch_get_main_queue(), 0), ^{
            NSLog(@"当前线程----->%@",[NSThread currentThread]);
     });
    
    • 异步任务+串行队列(会开启一条新的线程,串行执行任务)
    dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
            NSLog(@"当前线程----->%@",[NSThread currentThread]);
     });
    
    • 异步任务+并行队列(会开启至少一条新的线程,并行执行任务)
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSLog(@"当前线程----->%@",[NSThread currentThread]);
     });
    
    • 异步任务+主队列(不会开启新的线程,会在主线程中执行任务)
    dispatch_async(dispatch_get_main_queue(), 0), ^{
            NSLog(@"当前线程----->%@",[NSThread currentThread]);
     });
    

    总结:
    1.同步任务都不会开启新的线程,所以会阻塞当前线程。
    2.主队列中的任务不会开启新的线程,会在主线程中执行。
    3.异步任务+串行队列,会开启一条新的线程,在新的线程串行执行任务。
    4.异步任务+并行多列,会开启至少一条新的线程,在新的线程中并发执行任务。

    线程阻塞:

    实例1:

    NSLog(@"当前线程----->%@",[NSThread currentThread]); // 会打印
    dispatch_sync(dispatch_get_main_queue(), ^{
         NSLog(@"当前线程----->%@",[NSThread currentThread]);  // 这句话永远不会打印,此时主线程已经阻塞了,你对界面的所有操作都没反应了。
    });
    NSLog(@"当前线程----->%@",[NSThread currentThread]); // 不会打印
    

    原因:
    1.dispatch_sync同步任务,不会开启新的线程,所以上面的代码是在主线程中执行的,也就会阻塞主线程,等待block中的任务完成。
    2.dispatch_get_main_queue()主队列,会把block中的任务放进主队列,也就是主线程中去执行,可是此时主线程已经阻塞了,block永远无法完成任务。所以就会一直阻塞主线程。

    实例2:

        // 自定义串行队列
        dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
        
        // 串行异步
        dispatch_async(queue, ^{  // @1
            NSLog(@"当前线程----->%@",[NSThread currentThread]); // 会打印
            dispatch_sync(queue, ^{ // 不会打印  @2
                 NSLog(@"当前线程1----->%@",[NSThread currentThread]);
            });
            NSLog(@"当前线程2----->%@",[NSThread currentThread]); // 不会打印
        });
        NSLog(@"当前线程3----->%@",[NSThread currentThread]); // 会打印 @3
    
    

    原因:
    1.上面的代码@1会开启一条新的线程,但因为是在串行队列中,所以会一个一个执行任务。我们假设开启的新线程叫“B”;
    2.打印完"当前线程"后,@2同步任务,不会开启新的线程,会阻塞当前线程,所以还是在"B"线程中执行任务,此时线程"B"已经阻塞了,@2会把block当中的任务放入"myQueue"中去执行,但是"myQueue"是串行的,所以必须等"myQueue"执行完上一个任务,而它执行的上一个任务就是当前block中的任务,也就是阻塞了的@2,@2永远执行不了,所以会一直阻塞。
    3.@3会打印,是因为它是在主线程中的。

    实例3:

        // 创建并行队列
        dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
        dispatch_async(queue, ^{ // @1
            NSLog(@"当前线程----->%@",[NSThread currentThread]);
            dispatch_sync(queue, ^{ // @2
                NSLog(@"当前线程1----->%@",[NSThread currentThread]);
            });
            NSLog(@"当前线程2----->%@",[NSThread currentThread]); // @3
        });
        NSLog(@"当前线程3----->%@",[NSThread currentThread]); // @4
        -----------------------------以上都会打印-----------------------------
    

    原因:
    1.@1会开启至少一条新的线程,并行执行任务。
    2.@2不会开启新的线程,在当前线程并行执行任务。会阻塞当前线程,但因为是并行队列中,所以会执行完@2,在执行@3.
    3.@3在主线程中执行,不受影响。

    队列组

        // 创建组
        dispatch_group_t group = dispatch_group_create();
        // 系统全局唯一并行队列
        //dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        
        // 自定义串行队列,按顺序执行
        dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
        
        dispatch_group_async(group, queue, ^{ // @1
            NSLog(@"当前线程----->%@",[NSThread currentThread]);
        });
        dispatch_group_async(group, queue, ^{ // @2
            NSLog(@"当前线程1----->%@",[NSThread currentThread]);
        });
        dispatch_group_async(group, queue, ^{ // @3
            NSLog(@"当前线程2----->%@",[NSThread currentThread]);
        });
        
        dispatch_group_notify(group, queue, ^{ // @4
            NSLog(@"当前线程3----->%@",[NSThread currentThread]);
        });
    
    • 并行队列
      @1,2,3会随机打印,最后打印@4
    • 串行队列
      @1<@2<@3<@4 按顺序打印

    单列

     static dispatch_once_t onceToken;
     dispatch_once(&onceToken, ^{
     });
    

    延时执行

        // 如果在串行、并行队列中执行,会开启线程。也就是说dispatch_after方法是异步执行的
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"当前线程----->%@",[NSThread currentThread]);
        });
    

    栅栏方法(分割任务)

        // dispatch_barrier_async 方法需使用自定义队列,不能使用系统全局队列
        dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
        //dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_async(queue, ^{ // @1
            NSLog(@"当前线程----->%@",[NSThread currentThread]);
        });
        
        dispatch_barrier_async(queue, ^{ // @2
            NSLog(@"当前线程1----->%@",[NSThread currentThread]);
        });
        
        dispatch_async(queue, ^{ // @3
            NSLog(@"当前线程2----->%@",[NSThread currentThread]);
        });
    

    注意:
    使用dispatch_barrier_async时:
    1.必须使用自定义队列,不能使用系统全局队列。
    dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT)


    NSOperation&NSOperationQueue

    NSOperation是Apple对GCD的封装,是面向对象的。NSOperation、NSOperationQueue分别对应GCD中的任务和队列。

    注意:NSOperation是个抽象类,不能直接使用,必须使用它的2个子类:NSInvocationOperation、NSBlockOperation。

    创建任务:

    • NSInvocationOperation
    NSInvocationOperation *invocationOP = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
    [invocationOP start];
    
    • NSBlockOperation
     NSBlockOperation *blockOP = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"当前线程---->%@",[NSThread currentThread]);
     }];
    
    [blockOP addExecutionBlock:^{
            NSLog(@"当前线程1---->%@",[NSThread currentThread]);
    }];
    [blockOP start];
    

    注意:
    1.addExecutionBlock方法可能开启新的线程,也可能在主线程中执行。
    2.addExecutionBlock方法调用必须在start方法之前,否则会报错。

    • 自定义任务:新建一个类继承NSOperation,需要重写main,cancel,finished,executing等方法。

    创建队列:(只有主队列,和其他队列,没有串行和并行区分)

    • NSOperationQueue(其他队列)
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    • 主队列
    [NSOperationQueue mainQueue];
    
    使用:
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.maxConcurrentOperationCount = 1; // 串行,默认为-1不限制,既并行
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"当前线程---->%@",[NSThread currentThread]);
     }];
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"当前线程1---->%@",[NSThread currentThread]);
     }];
    [queue addOperation:operation];
    [queue addOperation:operation1];
    

    注意:任务加入队列中,会自动执行,不需要调用start方法,否则会报错。

    依赖

    必须等A任务执行完毕之后在执行B任务。
    比如从网上开启一个线程下载图片,必须等图片下载完成之后在主线程加载图片,刷新UI,这个时候就可以用上依赖了。

        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        //queue.maxConcurrentOperationCount = 1; // 串行,默认为-1不限制
        
        NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"当前线程---->%@",[NSThread currentThread]);
        }];
        NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"当前线程1---->%@",[NSThread currentThread]);
        }];
        NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"当前线程2---->%@",[NSThread currentThread]);
        }];
        
        [operation addDependency:operation1]; // operation依赖operation1
        [operation2 addDependency:operation]; // 2operation依赖operation
        [queue addOperations:@[operation,operation1] waitUntilFinished:NO];
        [NSOperationQueue.mainQueue addOperation:operation2];
    

    2017-08-10 10:46:34.938 ZTSlidingVC[1273:222606] 当前线程1----><NSThread: 0x7fd9f3dd9510>{number = 2, name = (null)}
    2017-08-10 10:46:34.938 ZTSlidingVC[1273:222606] 当前线程----><NSThread: 0x7fd9f3dd9510>{number = 2, name = (null)}
    2017-08-10 10:46:34.948 ZTSlidingVC[1273:222557] 当前线程2----><NSThread: 0x7fd9f3d287a0>{number = 1, name = main}

    注意:依赖关系是可以跨队列的,如上面例子所示。

    其他属性、方法

    • NSOperation
    属性:
    @property (readonly, getter=isCancelled) BOOL cancelled; // 是否取消任务
    @property (readonly, getter=isExecuting) BOOL executing; // 是否正在执行
    @property (readonly, getter=isFinished) BOOL finished; // 是否完成任务
    方法:
    - (void)cancel; // 取消任务
    - (void)start; // 开始任务
    - (void)addDependency:(NSOperation *)op; // 添加依赖
    - (void)removeDependency:(NSOperation *)op; // 删除依赖
    
    • NSOperationQueue
    属性:
    @property (readonly) NSUInteger operationCount NS_AVAILABLE(10_6, 4_0); // 获取队列中的任务数量
    @property NSInteger maxConcurrentOperationCount; // 设置最大任务数
    @property (getter=isSuspended) BOOL suspended; // YES:暂停,NO:继续(对正在执行的任务无效,只是暂停调度新的任务执行)
    @property (class, readonly, strong, nullable) NSOperationQueue *currentQueue NS_AVAILABLE(10_6, 4_0); // 获取当前队列
    @property (class, readonly, strong) NSOperationQueue *mainQueue NS_AVAILABLE(10_6, 4_0); // 获取主队列
    方法:
    - (void)cancelAllOperations; // 取消所有任务
    - (void)waitUntilAllOperationsAreFinished; // 等待所有队列中的任务执行完成,会阻塞线程(在等待时,其他线程仍然可以往队列中添加任务)
    

    线程同步

    • 为什么需要线程同步:
      当多个线程同时访问一个统一资源,造成数据状态不一致,产生的数据混乱,安全等问题。
    • 实现线程同步的2种方式:
      1.加锁
    1. @synchronized 关键字加锁
    2. NSLock 对象锁
    3. NSCondition
    4. NSConditionLock 条件锁
    5. NSRecursiveLock 递归锁
    6. pthread_mutex 互斥锁(C语言)
    7. dispatch_semaphore 信号量实现加锁(GCD)
    8. OSSpinLock

    方法有点多,这就不一一介绍了,开发中也用不了这么多。这里就简单介绍一下1,2,7的使用把,需要其他更详细的的功能请自行goolge。

    • @synchronized 关键字加锁(性能较差,使用简单)
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            @synchronized (self) {
                NSLog(@"做你想做的事");
            }
     });
    
    • NSLock(性能一般)
    NSLock *lock = [[NSLock alloc] init];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            if ([lock tryLock]) { // 尝试加锁,如果失败了,并不会阻塞线程,只是立即返回NO
                NSLog(@"做你想做的事");
                [lock unlock]; // 记得解锁
            }
    });
    
    • dispatch_semaphore 信号量实现加锁(GCD,推荐使用此方法)

    dispatch_semaphore_create   创建一个semaphore
    dispatch_semaphore_signal   发送一个信号(计数器+1)
    dispatch_semaphore_wait    等待信号(信号量-1,如果信号量<=0,则一直等待,会阻塞线程)

    dispatch_semaphore_t dsema = dispatch_semaphore_create(2); // 创建信号量,后面的数字既最大并发量
        for(int i=0; i<10; i++){
            dispatch_semaphore_wait(dsema, DISPATCH_TIME_FOREVER);  // -1 DISPATCH_TIME_FOREVER会一直等待,直到信号量大于0。DISPATCH_TIME_NOW不等待,也就不能控制线程并发数了。
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                NSLog(@"------>%d  当前线程---->%@",i,[NSThread currentThread]);
                dispatch_semaphore_signal(dsema); // +1
            });
        }
    

    上面的列子,看起来创建了10个线程,其实同时只有2个线程在并发执行。

    2.使用串行队列

    参考:

    http://www.jianshu.com/p/0b0d9b1f1f19
    http://ksnowlv.github.io/blog/2014/09/07/ios-tong-bu-suo-xing-neng-dui-bi/

    相关文章

      网友评论

          本文标题:ios-多线程(NSThread,GCD,NSOperation

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