iOS多线程汇总(NSOperation、GCD、NSThread、p_thread))

简介

iOS的多线程api一共有4套,从底层往上,分别是:

  • p_thread :iOS基于unix操作系统,遵循POSIX标准,因此,最底层的多线程接口就是p_thread了。
  • NSThread:基本上可以看成是p_thead的封装。
  • GCD:可以理解为一种线程池化技术的实现。
  • NSOperaion:GCD的封装,使用面向对象的方式管理任务和线程,提供了一些方便的api,代码可读性更高。

一:NSOperation

为什么要使用 NSOperation、NSOperationQueue?

  1. 可添加完成的代码块,在操作完成后执行。
  2. 添加操作之间的依赖关系,方便的控制执行顺序。
  3. 设定操作执行的优先级。
  4. 可以很方便的取消一个操作的执行。
  5. 使用 KVO 观察对操作执行状态的更改:isExecuteing、isFinished、isCancelled
1:NSOpration
基本使用

NSOperation 表示任务,它本身是一个抽象类,如果要封装操作,需要使用它的两个子类,NSInvocationOperationNSBlockOperation ,其中 NSInvocationOperation 使用SEL的方式执行任务,而NSBlockOperation则通过block执行任务。

- (void)useBlockOperation{
    NSBlockOperation* op = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"Invocation operation finished %@",[NSThread currentThread]);
    }];
    [op start];
}

- (void)useInvocationOperation{
    NSInvocationOperation* op = [[NSInvocationOperation alloc] initWithTarget:self
                                                               selector:@selector(invocationRun)
                                                               object:nil];
    [op start];
    
}
-(void)invocationRun{
    [NSThread sleepForTimeInterval:1.0];
    NSLog(@"Invocation operation finished %@",[NSThread currentThread]);
}

operation 作为任务,默认并不会开辟更多的线程,因此在哪个线程调用start,就在哪个线程执行。
但是,如果给 NSBlockOperation添加多个任务,新添加的任务会开辟其他线程,也可以自定义并发的NSOperation,但个人不是太建议这么用,并发就应该交给 NSOperationQueue

- (void)useBlockOperation{
    NSBlockOperation* op = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"Invocation operation finished %@",[NSThread currentThread]);
    }];

	//这个新添加的任务会开辟新的线程(这个估计是苹果为了方便做的api,后续讨论队列时,不包括这种情况)
    [op addExecutionBlock:^{
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"Invocation operation finished %@",[NSThread currentThread]);
    }];
    [op start];
}
自定义NSOperation的子类

我们也可以自己定义NSOpetaion的子类,好处是在子类中,可以使用成员变量来保存函数执行的一些结果,在涉及某些递归函数或者函数调用比较复杂时,定义子类会比较好用。
使用方法比较简单,继承NSOperation后,重写main方法就好。

@interface TTOperation : NSOperation{
}
@end

@implementation TTOperation
-(void)main{
    [NSThread sleepForTimeInterval:1.0];
    NSLog(@"tt operation finished %@",[NSThread currentThread]);
}
@end
2:NSOperatoinQueue

队列有两种:
主队列:所有操作都会放到主线程串行执行。
自定义队列:放到子队列执行,可以串行,也可以并行。
默认创建的队列是并行的,代码如下:

- (void)addOperationToQueue {
    // 创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    // 添加任务
    // 添加 NSInvocationOperation
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationRun) object:nil];
    
    // 添加 NSBlockOperation
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:0.5];
            NSLog(@"block operation finished 3 %@",[NSThread currentThread]);
        }
    }];
    
    // 使用快捷添加方式
    [queue addOperationWithBlock:^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"block operation finished 3 %@",[NSThread currentThread]);
    }];
    // 使用 addOperation: 添加所有操作到队列中
    [queue addOperation:op1];
    [queue addOperation:op2];
}
3:基本操作

(1)设置最大并发数:
-1时为不限制,1为串行,大于1时为并发队列。这点用于控制网络请求的并发、任务负载均衡、挺有用的。

 NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 1;	//1就是串行了

(2)操作依赖:

NSOperation* op1 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"op1 finish");
    }];
    NSOperation* op2 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2.0];
        NSLog(@"op1 finish");
    }];
    NSOperation* op3 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:1.5];
        NSLog(@"op1 finish");
    }];
    NSOperation* op4 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"all finish");
    }];
    [op4 addDependency:op1];
    [op4 addDependency:op2];
    [op4 addDependency:op3];
    
    NSOperationQueue* opq = [[NSOperationQueue alloc] init];
    [opq addOperation:op1];
    [opq addOperation:op2];
    [opq addOperation:op3];
    [opq addOperation:op4];

在这里插入图片描述
想象一个场景,同时下载多个任务,完成后通知用户下载完成,这个方法就很实用了。
(3)优先级

NSOperation* op = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"log upload");
    }];
    [op setQueuePriority:NSOperationQueuePriorityLow];

如果任务1依赖于任务2,那么任务2完成之前,任务1处于阻塞状态,阻塞不属于优先级的考虑范围。但如果一个队列的并发数为5,同时有10个任务需要进行,那么优先级低的任务会排在后面。
这点在做日志上报之类功能的时候,尤其有用,毕竟不希望这类任务妨碍到用户正常使用,优先级一般是最低的。

(4)线程间通信

NSOperationQueue* opq = [[NSOperationQueue alloc] init];
    [opq addOperationWithBlock:^{
        [NSThread sleepForTimeInterval:1.0];
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            NSLog(@"回调主线程");
        }];
    }];

从这一点就可以看出来,NSOperation确实是对GCD的封装。
(5)线程同步和线程安全
NSOperation没有自己的锁api,所以使用任意一个锁的api都是可以的,比如 NSLock、@synchronized、NSRecursiveLock等等都可以。

4: 生命周期控制和自定义并发NSOperation

NSOperation有三种状态,isReady -> isExecuting -> isFinish
如果想执行完成后,再调用某个方法,比如刷新页面,可以添加如下方法。

[op setCompletionBlock:^{
        NSLog(@"completion");
    }];

NSOperation支持的KVO属性有:

  • isCancelled
  • isConcurrent (自定义必须重写)
  • isExecuting (自定义必须重写)
  • isFinished (自定义必须重写)
  • isReady
  • dependencies
  • queuePriority
  • completionBlock
    如果要自定义并发NSOperation,要重写 startmainisConcurrent方法,属性中需要包含 isExecutingisFinish
@interface MyOperation : NSOperation {
    BOOL        executing;
    BOOL        finished;
}
- (void)completeOperation;
@end
 
@implementation MyOperation
- (id)init {
    self = [super init];
    if (self) {
        executing = NO;
        finished = NO;
    }
    return self;
}
 
- (BOOL)isConcurrent {
    return YES;
}
 
- (BOOL)isExecuting {
    return executing;
}
 
- (BOOL)isFinished {
    return finished;
}
@end
- (void)start {
    [self.lock lock];
   // 在开始任务之前要测试一下是否取消
   if ([self isCancelled])
   {
      // 如果是已经取消了,必需要把Finish设为YES
      [self willChangeValueForKey:@"isFinished"];
      finished = YES;
      [self didChangeValueForKey:@"isFinished"];
      return;
   }
 
   // 如果没有取消,就继续运行代码
   [self willChangeValueForKey:@"isExecuting"];
   [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
   executing = YES;
   [self didChangeValueForKey:@"isExecuting"];
   [self.lock unlock];
}
- (void)main {
   @try {
 
       // 写你业务代码
 
       [self completeOperation];
   }
   @catch(...) {
      // Do not rethrow exceptions.
   }
}
- (void)completeOperation {
     [self.lock lock];
    [self willChangeValueForKey:@"isFinished"];
    [self willChangeValueForKey:@"isExecuting"];
 
    executing = NO;
    finished = YES;
 
    [self didChangeValueForKey:@"isExecuting"];
    [self didChangeValueForKey:@"isFinished"];
    self.completionBlock();
     [self.lock unlock];
}

二:GCD

1:基本操作

本质上,它就是一个iOS的线程池,全c实现,因此能够实现的功能比NSOperation要多,但是,对于复杂操作,没有NSOperation的实现那么清晰,容易理解。
GCD的性能比NSOperation好,优点如下:

  • GCD 可用于多核的并行运算;
  • GCD 会自动利用更多的 CPU 内核(比如双核、四核);
  • GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程);
  • 程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码。

GCD的队列分为 串行队列并发队列,串行队列中的任务一个个执行,并发的则是同步执行。
常用队列有两个,一个是主队列,这是一个串行队列,里面只包含一个主线程,一个是全局并发队列。

//并发
dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);
//串行
dispatch_queue_t serialQueue = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);
//全局
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();

GCD的任务分为 同步任务异步任务,同步任务会阻塞当前线程,异步则不会。

// 同步
dispatch_sync(queue, ^{
});
// 异步
dispatch_async(queue, ^{
});

这里有一个经典的死锁问题:阻塞当前线程的同时又想调用当前线程。

dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"我是死锁");
  });

同步、异步,串行、并行 组合有许多种,没必要刻意去对比,记住基本含义就能推导出组合效果。比如串行队列加异步,串行队列中任务一个个执行,异步只是说,不阻塞当前线程(发起异步任务的线程)而已,所以最终的结果,就只有一个线程执行所有任务。
GCD线程通信和前面说的NSOperation差不多,毕竟NSOperation就是GCD的封装。

dispatch_async(dispatch_get_global_queue(0, 0), ^{
        //执行一些操作...
        dispatch_async(dispatch_get_main_queue(), ^{
            //回调主线程
        })
    });
2:队列组
//执行任务:可以看见,队列组执行一个任务需要一个队列组、一个队列
dispatch_group_async_f(dispatch_group_t group,
	dispatch_queue_t queue,
	void *_Nullable context,
	dispatch_function_t work);

//所有任务完成后,执行下面定义的block
dispatch_group_notify(dispatch_group_t group,
	dispatch_queue_t queue,
	dispatch_block_t block);

//队列组中的任务必须在指定时间内完成,时间一到,队列组中所有任务都将停止。
dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);

这里面用的比较多的就是 dispatch_group_notify 了,比如使用多线程下载一个图片,全部下载完成后,就可以使用 dispatch_group_notify 通知当前线程完成图片拼接。但是,如果block中发起的是一个异步操作,比如如下代码,dispatch_group_notify无法保证所有操作已经完成。因为对于一个函数来说,其中发起的异步操作是由其他线程执行的,异步操作之后的代码不会等待异步操作的完成。

dispatch_queue_t global = dispatch_get_global_queue(0, 0);
    dispatch_queue_t queue = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, queue, ^{
        dispatch_async(global, ^{
            [NSThread sleepForTimeInterval:1.0];
            NSLog(@"1");
        });
    });
    dispatch_group_async(group, queue, ^{
        dispatch_async(global, ^{
            [NSThread sleepForTimeInterval:1.0];
            NSLog(@"2");
        });
    });
    dispatch_group_async(group, queue, ^{
        dispatch_async(global, ^{
            [NSThread sleepForTimeInterval:1.0];
            NSLog(@"3");
        });
    });
    dispatch_group_notify(group, queue, ^{
        NSLog(@"finish");
    });

上面的代码。finish会在 1、2、3之前打印。
这时候就需要使用 dispatch_group_enterdispatch_group_leave

dispatch_queue_t global = dispatch_get_global_queue(0, 0);
    dispatch_queue_t queue = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, queue, ^{
        dispatch_group_enter(group);
        dispatch_async(global, ^{
            [NSThread sleepForTimeInterval:1.0];
            NSLog(@"1");
            dispatch_group_leave(group);
        });
    });
    dispatch_group_async(group, queue, ^{
        dispatch_group_enter(group);
        dispatch_async(global, ^{
            [NSThread sleepForTimeInterval:1.0];
            NSLog(@"2");
            dispatch_group_leave(group);
        });
    });
    dispatch_group_async(group, queue, ^{
        dispatch_group_enter(group);
        dispatch_async(global, ^{
            [NSThread sleepForTimeInterval:1.0];
            NSLog(@"3");
            dispatch_group_leave(group);
        });
    });
    dispatch_group_notify(group, queue, ^{
        NSLog(@"finish");
    });

notify方法会在所有 dispatch_group_leave 完成之后执行。

4:栅栏、延时执行、一次性代码、快速迭代

栅栏 : dispatch_barrier_async

dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [NSThread sleepForTimeInterval:1.0];
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [NSThread sleepForTimeInterval:1.0];
    });
    dispatch_barrier_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"barrier method");
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [NSThread sleepForTimeInterval:1.0];
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [NSThread sleepForTimeInterval:1.0];
    });

栅栏后的方法会等栅栏前的方法执行完成后再执行。
延迟执行:dispatch_after

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"after---%@",[NSThread currentThread]); 
    });

只执行一次的代码:dispatch_once

static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 只执行 1 次的代码(这里面默认是线程安全的)
    });

这个在单例中用的很多。
同一个任务加入队列中,执行6次:dispatch_apply

dispatch_apply(6, dispatch_get_global_queue(0, 0), ^(size_t i) {
        NSLog(@"index=%zd---thread=%@",i,[NSThread currentThread]);
    });
三:信号量

这个就不细说了,如果看过操作系统原理很容易理解。

dispatch_semaphore_t semaphore = dispatch_semaphore_create(10); //信号量创建
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);  //等待,p操作 -1
dispatch_semaphore_signal(semaphore);   //v操作 +1
3:NSThread

和前面两种不同,从NSThread这一层开始,就需要手动管理生命周期了。
作为p_thread,NSThread相对而言,提供的功能会比较多,过几天再写了,主要写一下runloop

NSThread* thread = [[NSThread alloc] initWithTarget:self selector:@selector(runloopTest) object:nil];
[thread start];
NSRunLoop* runloop = [NSRunLoop currentRunLoop];
[runloop addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];
[runloop run];
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值