ios-多线程(NSThread,GCD,NSOperation

2017-08-10  本文已影响0人  忽然之间_1988丶

线程:

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

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

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

[blockOP addExecutionBlock:^{
        NSLog(@"当前线程1---->%@",[NSThread currentThread]);
}];
[blockOP start];

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

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

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}

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

其他属性、方法

属性:
@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; // 删除依赖
属性:
@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; // 等待所有队列中的任务执行完成,会阻塞线程(在等待时,其他线程仍然可以往队列中添加任务)

线程同步

  1. @synchronized 关键字加锁
  2. NSLock 对象锁
  3. NSCondition
  4. NSConditionLock 条件锁
  5. NSRecursiveLock 递归锁
  6. pthread_mutex 互斥锁(C语言)
  7. dispatch_semaphore 信号量实现加锁(GCD)
  8. OSSpinLock

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

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        @synchronized (self) {
            NSLog(@"做你想做的事");
        }
 });
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_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/

上一篇下一篇

猜你喜欢

热点阅读