iOS:常驻线程

1,358 阅读3分钟

常驻线程

在实际的项目中,根据需求我们可能需要在后台常驻一个线程做一些事情。而对于常驻线程搜索下的话会有很多解决方案,但是大多数都是提到使用NSThreadRunLoop来实现的。而在本篇中介绍另外一种实现方法,那就是采用信号量的方式来实现。什么是信号量?我这里简单的解释下

信号量主要是用在多线程的场景,一个线程等待信号直到接收到信号继续运行,另外一个线程发送信号通知等待的线程继续运行。

信号量的运行方式正好跟我们需要的常驻线程的需求是契合的。在没有任务的时候让该线程处于等待状态,等有任务了,那么发送一个信号让常驻线程执行我们的任务。

下面是实现的代码:

/**
 常驻线程
 */
@interface ResidentThread : NSObject
-(void)doAction:(dispatch_block_t)action;
-(void)cancel;
@end

@implementation ResidentThread{
    NSMutableArray *actions;
    NSThread *thread;
    dispatch_semaphore_t sem;
    bool cancel;
}
-(id)init{
    self = [super init];
    actions = [NSMutableArray array];
    // 创建信号量
    sem = dispatch_semaphore_create(0);
    // 创建一个新线程
    thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
    [thread start];
    return self;
}

-(void)run{
    while (true) {
        // 等待信号量
        dispatch_semaphore_wait(sem, -1);
        // 收到信号
        // 如果线程已经取消了,那么退出循环
        if(cancel){
            break;
        }
        // 开始执行任务
        dispatch_block_t block = [actions firstObject];
        if(block){
            [actions removeObject:block];
            block();
        }
        
    }
}

// 执行某个任务
-(void)doAction:(dispatch_block_t)action{
    if(!cancel){ // 如果线程已经cancel了,那么直接忽略
        // 将任务放入数组
        [actions addObject:[action copy]];
        // 发送信号
        dispatch_semaphore_signal(sem);
    }
}

// 终止常驻线程
-(void)cancel{
    cancel = YES;
    // 线程取消后,清空所有的回调
    [actions removeAllObjects];
    // 相当于发送一个终止任务的信号
    dispatch_semaphore_signal(sem);
}
@end

上面代码中使用了GCD的信号量,本来想使用semaphore.h头文件中的信号量API的,但是这里面的API已经被苹果DEPRECATED了,就算你强制使用,那么也无法初始化信号量。

下面分析代码: ResidentThread对外总共提供了两个方法,分别是doAction:cancel

  1. doAction::这个方法接收一个dispatch_block_t的参数,代表实际需要让常驻线程执行的任务。
  2. cancel:终止该常驻线程。

    为了安全起见,这里采用的是标记位的方式。另外要注意到,cancel方法里面额外发送了一个信号,这个信号的作用类似于发送了一个终止任务的信号

从上面的代码中可以看出,常驻线程的原理是很简单的,而且在实现上也很简单。并且你会发现,这里面的任务其实是串行执行的。

Runloop

Runloop的原理我就不多介绍了,掘金里面有很多分析文章,讲的也很透彻,甚至有些直接把源码都贴出来分析。

从上面的常驻线程的代码中可以看出来,run方法的内部就是一个while循环,循环等待信号,当接收到信号后立马执行任务。执行完任务后继续等待信号。这样一个流程其实是跟Runloop的工作原理是差不多的。Runloop也是一个等待信号>收到信号>执行回调>继续等待信号,这样一个流程。

事实上,这样一套代码,在其他语言中比如C、C++上差不多就是这样实现。

如果进一步的发散下呢?GCD的任务调度的实现是不是也类似这样呢?事实上,我曾今在写C的时候,就是使用上面的代码实现多线程的任务调度功能。