Class RedisServer{public:int Init(); //初始化void Launch(); //启动线程static void onRedisTask(void* para); //启动线程的入口public:bool is_started = false;};
在程序启动的过程中,首先需要完成RedisService的创建和初始化,然后启动任务。当RedisService完成数据的加载,则修改is_started变量为true,主线程中的示意代码如下。
......RedisServer* pService = new RedisServer;if(pService && pService ->Init() == 0)pService ->Launch();elsereturn;while(!pService ->is_started); //等待Redis初始化完成//其他业务线程再初始化,启动…….......
上面的场景和设计比较简单,但是也适用于很多的业务系统。那么我们要思考一下,能不能提取设计一个小的通用化组件,将来可以应对一对多的初始化,也可以应对多对一的初始化场景呢?答案是肯定可以的,接下来我们先简单介绍如何实现。
class CEvent{public:CEvent() {g_count = 0;};~CEvent() = default;void Require(){__sync_add_and_fetch(&(g_count), 1);};void Release(){__sync_add_and_fetch(&(g_count), -1);};void Wait(){ while (g_count > 0); };private:int g_count;};
在上面的类CEvent中,其实就是通过Require方法进行原子操作加1。通过Release方法进行原子减1操作。__sync_add_and_fetch方法是一种CAS的原子操作,关于CAS的原子命令,后面我将单独写一篇文章介绍,并介绍无锁队列的实现。那么如何使用CEvent类呢?我们一起来看看使用。
class TaskService_A{public:int Init(Event& event){event.Require(); //请求一次//执行其他事情....};voidvoid Run(){do_Before();event.Release(); //释放一次while(true){ .... }do_after();}}
上面的线程中,在Init的时候先调用CEvent对象的Require方法,将计数器加1,等初始化完成,启动线程进入while循环之前,调用CEvent对象的Release方法减1。接下来,我们看看如何在mian线程中将这样使用CEvent类。
//main线程中......CEvent event;//启动线程ATaskService_A* pTaskA = new TaskService_A;pTaskA->Init(event);pTaskA->Launch();//启动线程BTaskService_A*pTaskB = new TaskService_A;pTaskB->Init(event);pTaskB->Launch();.....省略其他代码.....event.wait(); //等待其他任务初始化完成
主线程中其实是阻塞在这里等待计数器为0,说明其他线程完成了初始化操作,然后主线程将向下执行。
上面的套路是不是很简单,熟悉吗?这就是锁的机制,当申请锁的时候,其实是引用加1,释放锁的时候引用减一。同样的设计思路,让我想到了Linux内核中的RCU机制,所谓RCU机制,就是在内核中的链表增删改节点时候,给读线程设置了保护期,当最后一个线程退出时,将真正的删改节点,其实也是这样的思路去实现的。
另外,既然谈到了多线程,在多线程下,回收线程也是一个很重要的事情,linux下有waitpid等函数回收操作。c++中也有Joinable函数,等待线程回收。那么我们也可以通过上面CEvenet的方式,主线程最终阻塞在event.wait方法上,等其他线程收到退出信号,退出后减掉1次,最终也可以实现线程的回收,明白这种设计思路即可,在实际开发中,还可以设置超时时间,其实就是在初始化的时候记录下时间戳,最终等待的时候,wait方法中不断比较超时即可。
原理和设计思想都是通用的,这才是我们成长的基本功,也希望大家多积累,总结并完善自己的知识体系。全文毕!感谢大家阅读!




