在日常开发中,我们会遇到很多场景,不同的场景有不同的设计实现思路,其实很多时候看似简单的原理,反而很有价值。就像解决活锁的问题那样,最后解决方案是设置不同的超时时间即可。
当服务启动后,需要等待某个线程完成初始化,这种场景比较场景,类似于生产者消费者模型的事件通知。 但是这种场景下,只需要一次信号通知即可。当然了,这种实现方式比较多,我们先用一种最简单的方式去模拟实现一下。
1、案例
这里假设有个RedisServer的类,这个类是一个线程,将初始化并完成全局参数的加载。然后启动其他业务线程开始正常运行,如下面的示例代码中,Launch方法将启动线程,线程的入口是本类中的onRedisTask方法。
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();
else
return;
qhile(!pService ->is_started);
.......
上面的场景和设计比较简单,但是也适用于很多的业务系统。
2、组件设计
那么我们要思考一下,能不能提取设计一个小的通用化组件,将来可以应对 一对多的初始化,也可以应对多对一的初始化场景呢?答案是肯定可以的,接下来我们先简单介绍如何实现。
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方法进行原子减一操作。关于CAS的原子命令,后面我将单独 写篇文章介绍。那么如何使用CEvent类呢?我们一起来看看。
class TaskService_A
{
public:
int Init(Event& event)
{
event.Require();
};
void Run()
{
do_Before();
event.Release();
while(true){ .... }
do_after();
}
}
在mian线程中将这样使用,请看下面的参考代码。
......
CEvent event;
TaskService_A* pTaskA = new TaskService_A;
pTaskA->Init(event);
pTaskA->Launch();
TaskService_A*pTaskB = new TaskService_A;
pTaskB->Init(event);
pTaskB->Launch();
.....省略其他代码.....
event.wait();
上面的套路是不是很简单,熟悉吗?这就是锁的机制,当申请锁的时候,其实是引用加1,释放锁的时候引用减一。
3、总结
另外,既然谈到了多线程,在多线程下,回收线程也是一个很重要的事情,linux下有waitpid等函数回收操作。c++中也有Joinable函数,等待线程回收。那么我们也可以通过上面CEvenet的方式,主线程最终阻塞在event.wait方法上,等其他线程收到退出信号,退出后减掉1次,最终也可以实现线程的回收,明白这种设计思路即可,在实际开发中,还可以设置超时时间,其实就是在初始化的时候记录下时间戳,最终等待的时候,wait方法中不断比较超时即可。
正所谓,原理和设计思想都是通用的,这才是我们成长的基本功,也希望大家多积累,总结并完善自己的知识体系。
全文毕!感谢大家阅读!
|