常驻线程
在实际的项目中,根据需求我们可能需要在后台常驻一个线程做一些事情。而对于常驻线程
搜索下的话会有很多解决方案,但是大多数都是提到使用NSThread
和RunLoop
来实现的。而在本篇中介绍另外一种实现方法,那就是采用信号量
的方式来实现。什么是信号量?我这里简单的解释下
信号量
主要是用在多线程的场景,一个线程等待信号直到接收到信号继续运行,另外一个线程发送信号通知等待的线程继续运行。
信号量
的运行方式正好跟我们需要的常驻线程
的需求是契合的。在没有任务的时候让该线程处于等待状态,等有任务了,那么发送一个信号让常驻线程
执行我们的任务。
下面是实现的代码:
/**
常驻线程
*/
@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
。
doAction:
:这个方法接收一个dispatch_block_t
的参数,代表实际需要让常驻线程执行的任务。cancel
:终止该常驻线程。为了安全起见,这里采用的是标记位的方式。另外要注意到,cancel方法里面额外发送了一个信号,这个信号的作用类似于
发送了一个终止任务的信号
。
从上面的代码中可以看出,常驻线程
的原理是很简单的,而且在实现上也很简单。并且你会发现,这里面的任务其实是串行
执行的。
Runloop
Runloop
的原理我就不多介绍了,掘金里面有很多分析文章,讲的也很透彻,甚至有些直接把源码都贴出来分析。
从上面的常驻线程
的代码中可以看出来,run
方法的内部就是一个while
循环,循环
等待信号,当接收到信号后立马执行任务。执行完任务后继续等待信号。这样一个流程其实是跟Runloop
的工作原理是差不多的。Runloop
也是一个等待信号>收到信号>执行回调>继续等待信号,这样一个流程。
事实上,这样一套代码,在其他语言中比如C、C++上差不多就是这样实现。
如果进一步的发散下呢?GCD
的任务调度的实现是不是也类似这样呢?事实上,我曾今在写C的时候,就是使用上面的代码实现多线程的任务调度功能。