0x01 Step 4a - Continue
CScheduler 实现
// Start the lightweight task scheduler thread
CScheduler::Function serviceLoop = boost::bind(&CScheduler::serviceQueue, &scheduler);
threadGroup.create_thread(boost::bind(&TraceThread<CScheduler::Function>, "scheduler", service
首先通过bind
函数将函数绑定到对象,在前面http://blog.csdn.net/pure_lady/article/details/77675915#t2已经介绍过bind
的基本用法,同时也介绍了当bind
的第一个参数为类中的成员函数时,后面的第一个参数必须是类的对象,这里的第一句代码就是绑定类成员函数serviceQueue
到函数对象serviceLoop
,然后通过线程组threadGroup
创建新的线程,线程的执行函数为bind
返回的函数对象,其中TraceThread
的定义如下,
// src/util.h line 299
/**
* .. and a wrapper that just calls func once
*/
template <typename Callable> void TraceThread(const char* name, Callable func)
{
std::string s = strprintf("bitcoin-%s", name);
RenameThread(s.c_str());
try
{
LogPrintf("%s thread start\n", name);
func();
LogPrintf("%s thread exit\n", name);
}
catch (const boost::thread_interrupted&)
{
LogPrintf("%s thread interrupt\n", name);
throw;
}
catch (const std::exception& e) {
PrintExceptionContinue(&e, name);
throw;
}
catch (...) {
PrintExceptionContinue(nullptr, name);
throw;
}
}
该函数通过template <typename Callable>
来定义后面传入函数的类型,所以在实际调用时传入的函数类型为CScheduer::Function
,实例对象为serviceLoop
,第一个参数为“scheduler”
。该函数的作用是重命名线程并将调用函数执行一次,也就是serviceLoop
函数,实际上也就是CScheduler
中的serviceQueue
函数,再来看看这个函数是怎么实现的,
void CScheduler::serviceQueue()
{
boost::unique_lock<boost::mutex> lock(newTaskMutex);
++nThreadsServicingQueue;
// newTaskMutex is locked throughout this loop EXCEPT
// when the thread is waiting or when the user's function
// is called.
while (!shouldStop()) {
try {
if (!shouldStop() && taskQueue.empty()) {
reverse_lock<boost::unique_lock<boost::mutex> > rlock(lock);
// Use this chance to get a tiny bit more entropy
RandAddSeedSleep();
}
while (!shouldStop() && taskQueue.empty()) {
// Wait until there is something to do.
newTaskScheduled.wait(lock);
}
// Wait until either there is a new task, or until
// the time of the first item on the queue:
// wait_until needs boost 1.50 or later; older versions have timed_wait:
#if BOOST_VERSION < 105000
while (!shouldStop() && !taskQueue.empty() &&
newTaskScheduled.timed_wait(lock, toPosixTime(taskQueue.begin()->first))) {
// Keep waiting until timeout
}
#else
// Some boost versions have a conflicting overload of wait_until that returns void.
// Explicitly use a template here to avoid hitting that overload.
while (!shouldStop() && !taskQueue.empty()) {
boost::chrono::system_clock::time_point timeToWaitFor = taskQueue.begin()->first;
if (newTaskScheduled.wait_until<>(lock, timeToWaitFor) == boost::cv_status::timeout)
break; // Exit loop after timeout, it means we reached the time of the event
}
#endif
// If there are multiple threads, the queue can empty while we're waiting (another
// thread may service the task we were waiting on).
if (shouldStop() || taskQueue.empty())
continue;
Function f = taskQueue.begin()->second;
taskQueue.erase(taskQueue.begin());
{
// Unlock before calling f, so it can reschedule itself or another task
// without deadlocking:
reverse_lock<boost::unique_lock<boost::mutex> > rlock(lock);
f();
}
} catch (...) {
--nThreadsServicingQueue;
throw;
}
}
--nThreadsServicingQueue;
newTaskScheduled.notify_one();
}
该函数首先定义了一个unique_lock
来保证整个函数是线程安全的,接着是一个while
循环,循环中通过shouldStop
判断是否该终止循环,shouldStop
的实现如下,
bool shouldStop() const { return stopRequested || (stopWhenEmpty && taskQueue.empty()); }
如果stopRequested
被设置为true
或者队列为空且stopWhenEmpty
被设置为true
,那么shouldStop
就返回true
表示循环该停止了,这也是多线程中简单的通过变量来控制其他线程终止的方式。回到serviceQueue
函数,首先通过一个条件语句判断,如果当前没有任务,那么随机的Sleep
一段时间,来增加系统的随机熵;这一步完成后,如果依然没有任务,那么就通过wait
函数进行阻塞等待唤醒,这里newTaskScheduled
变量类型又是我们之前介绍过的condition_variable
。等到有新的任务了,也就是taskQueue
不为空了,注意这里涉及到的任务包含两种类型:(1)等待delta
时间后,只执行一次;(2)等待delta
时间后,每隔delta
时间后都执行一次。这两种类型分别通过scheduleFromNow()
和scheduleEvery()
函数进行调用。所以每个任务都包含两个变量,实现时通过pair<time_point, Function>
来进行插入,所以源码这里发现有新的任务添加后,首先获取新任务的第一个变量也就是时间,进行等待,实现时根据boost
不同的版本有不同的等待方式;等待完之后,获取任务的第二个参数,也就是要执行的函数,并从任务队列中删除该任务,此时所有的共享变量(taskQueue
)都已经访问完了,所以在执行调用函数之前,可以先解锁,而该函数已经从列队中删除了,所以也就只可能执行一次。
注册后台处理信号
GetMainSignals().RegisterBackgroundSignalScheduler(scheduler);
- 1
这句代码中的GetMainSignals()
返回一个CMainSignals
类型的静态变量g_signals
,然后调用这个类中的一个成员函数RegisterBackgroundSingalScheduler()
,通过这个函数将CMainSignals
类中的unique_ptr<MainSignalsInstance>
指针类型的成员变量m_internals
赋值为一个新建的对象,这个新建的对象类型为结构体MainSignalsInstance
,这个结构体包含了许多的信号,
struct MainSignalsInstance {
boost::signals2::signal<void (const CBlockIndex *, const CBlockIndex *, bool fInitialDownload)> UpdatedBlockTip;
boost::signals2::signal<void (const CTransactionRef &)> TransactionAddedToMempool;
boost::signals2::signal<void (const std::shared_ptr<const CBlock> &, const CBlockIndex *pindex, const std::vector<CTransactionRef>&)> BlockConnected;
boost::signals2::signal<void (const std::shared_ptr<const CBlock> &)> BlockDisconnected;
boost::signals2::signal<void (const CBlockLocator &)> SetBestChain;
boost::signals2::signal<void (const uint256 &)> Inventory;
boost::signals2::signal<void (int64_t nBestBlockTime, CConnman* connman)> Broadcast;
boost::signals2::signal<void (const CBlock&, const CValidationState&)> BlockChecked;
boost::signals2::signal<void (const CBlockIndex *, const std::shared_ptr<const CBlock>&)> NewPoWValidBlock;
// We are not allowed to assume the scheduler only runs in one thread,
// but must ensure all callbacks happen in-order, so we end up creating
// our own queue here :(
SingleThreadedSchedulerClient m_schedulerClient;
explicit MainSignalsInstance(CScheduler *pscheduler) : m_schedulerClient(pscheduler) {}
};
从这些信号的名称可以看出都是与节点本地区块链运行相关的操作,在后面也必然会有相应的触发,这里只是设置好相应的变量。
启动RPCServer、HTTPServer
/* Start the RPC server already. It will be started in "warmup" mode
* and not really process calls already (but it will signify connections
* that the server is there and will be ready later). Warmup mode will
* be disabled when initialisation is finished.
*/
if (gArgs.GetBoolArg("-server", false))
{
uiInterface.InitMessage.connect(SetRPCWarmupStatus);
if (!AppInitServers(threadGroup))
return InitError(_("Unable to start HTTP server. See debug log for details."));
}
-server
:接收命令行命令和JSON-RPC
命令。
如果在命令行中指定了-server
命令,那么就表示要启动相关的server
服务来处理client
发送过来的命令,启动之前首先先给InitMessage
信号添加一个新的执行函数,该函数实现如下
void SetRPCWarmupStatus(const std::string& newStatus)
{
LOCK(cs_rpcWarmup);
rpcWarmupStatus = newStatus;
}
rpcWarmupStatus
是一个静态string
类型的全局变量,所以该函数的作用就是将新的参数值赋给rpcWarmupStatus
变量。
AppInitServers
接下来调用AppInitServers
来初始化各种服务器,实现如下
bool AppInitServers(boost::thread_group& threadGroup)
{
RPCServer::OnStarted(&OnRPCStarted);
RPCServer::OnStopped(&OnRPCStopped);
RPCServer::OnPreCommand(&OnRPCPreCommand);
if (!InitHTTPServer())
return false;
if (!StartRPC())
return false;
if (!StartHTTPRPC())
return false;
if (gArgs.GetBoolArg("-rest", DEFAULT_REST_ENABLE) && !StartREST())
return false;
if (!StartHTTPServer())
return false;
return true;
}
首先调用RPCServer
类中的三个函数分别连接三个函数到三个信号槽,而这三个函数又分别做了一些信号连接工作,实现如下,
void OnRPCStarted()
{
uiInterface.NotifyBlockTip.connect(&RPCNotifyBlockChange);
}
void OnRPCStopped()
{
uiInterface.NotifyBlockTip.disconnect(&RPCNotifyBlockChange);
RPCNotifyBlockChange(false, nullptr);
cvBlockChange.notify_all();
LogPrint(BCLog::RPC, "RPC stopped.\n");
}
void OnRPCPreCommand(const CRPCCommand& cmd)
{
// Observe safe mode
std::string strWarning = GetWarnings("rpc");
if (strWarning != "" && !gArgs.GetBoolArg("-disablesafemode", DEFAULT_DISABLE_SAFEMODE) &&
!cmd.okSafeMode)
throw JSONRPCError(RPC_FORBIDDEN_BY_SAFE_MODE, std::string("Safe mode: ") + strWarning);
}
OnRPCStarted
负责将RPCNotifyBlockChange
连接到NotifyBlockTip
信号上;而OnRPCStopped
负责将连接解除,并做一些其他的清除工作;最后一个OnRPCPreCommand
检查在安全模式下是否有警告消息,如果有那么就抛出相应的异常。
接下来几个函数分别初始化和启动几个不同的server:
- InitHTTPServer():初始化http server.
- StartRPC():启动RPC服务
- StartHTTPRPC():启动HTTP RPC服务
- StartREST():启动REST
- StartHTTPServer():启动HTTP server.