任務排程器
Bitcoin 程式啟動後,有一個專門的執行緒做任務排程, 這些任務根據指定的時刻,執行對應的函式:
bool AppInitMain()
{
.......
// Start the lightweight task scheduler thread
CScheduler::Function serviceLoop = boost::bind(&CScheduler::serviceQueue, &scheduler);
threadGroup.create_thread(boost::bind(&TraceThread<CScheduler::Function>, "scheduler", serviceLoop));
.......
}
複製程式碼
排程器類主要是實現了一個生產者消費者的任務佇列,只是這個任務佇列是用 std::multimap 實現的,map 的key表達某一時刻,map的值表達:那一時刻要執行的函式,內部使用條件變數和鎖來保護multimap ,還有幾個bool 條件:
class CScheduler
{
public:
CScheduler();
~CScheduler();
typedef std::function<void(void)> Function;
void schedule(Function f, boost::chrono::system_clock::time_point t=boost::chrono::system_clock::now());
void scheduleFromNow(Function f, int64_t deltaMilliSeconds);
void scheduleEvery(Function f, int64_t deltaMilliSeconds);
void serviceQueue();
void stop(bool drain=false);
size_t getQueueInfo(boost::chrono::system_clock::time_point &first,
boost::chrono::system_clock::time_point &last) const;
bool AreThreadsServicingQueue() const;
private:
std::multimap<boost::chrono::system_clock::time_point, Function> taskQueue;
boost::condition_variable newTaskScheduled;
mutable boost::mutex newTaskMutex;
int nThreadsServicingQueue;
bool stopRequested;
bool stopWhenEmpty;
bool shouldStop() const { return stopRequested || (stopWhenEmpty && taskQueue.empty()); }
};
複製程式碼
CScheduler的client 通過呼叫schedule 往內部multimap新增一個條目;
scheduleFromNow 和scheduleEvery 內部都是呼叫schedule 方法實現;
這三個方法屬於生產者要生產任務的方法, 任務的消費者呼叫serviceQueue等待取走任務, 然後執行。
目前整個程式有一個全域性的CScheduler例項:
static CScheduler scheduler;
複製程式碼
這個例項對應只有一個消費者執行緒, 即唯一的後臺排程器執行緒。
class SingleThreadedSchedulerClient
主要用途是,藉助CScheduler型別,保障被新增到內部連結串列的任務,被序列執行:
class SingleThreadedSchedulerClient {
private:
CScheduler *m_pscheduler;
CCriticalSection m_cs_callbacks_pending;
std::list<std::function<void (void)>> m_callbacks_pending;
bool m_are_callbacks_running = false;
void MaybeScheduleProcessQueue();
void ProcessQueue();
public:
explicit SingleThreadedSchedulerClient(CScheduler *pschedulerIn) : m_pscheduler(pschedulerIn) {}
void AddToProcessQueue(std::function<void (void)> func);
void EmptyQueue();
size_t CallbacksPending();
};
複製程式碼
使用例子
基本的使用例子:
#include <scheduler.h>
#include <boost/bind.hpp>
#include <boost/thread.hpp>
#include <boost/test/unit_test.hpp>
#include <iostream>
static void doN(){
std::cout << "output now
";
}
static void doE(){
for(int i = 0; i < 10; i++){
std::cout << "i = " << i << `
`;
}
std::cout << `
`;
}
BOOST_AUTO_TEST_SUITE(sche_tests)
BOOST_AUTO_TEST_CASE(sche)
{
CScheduler s;
s.scheduleFromNow(doN, 1000);
s.scheduleEvery(doE, 1000);
boost::thread t(boost::bind(&CScheduler::serviceQueue, &s));
boost::this_thread::sleep_for(boost::chrono::seconds{5});
t.interrupt();
t.join();
}
BOOST_AUTO_TEST_CASE(singlethread)
{
CScheduler s;
SingleThreadedSchedulerClient sc (&s);
for(int i = 1; i <11; i++){
auto f = [=]{
std::cout << "thread " << boost::this_thread::get_id() << " print arg: " << i << `
`;
};
sc.AddToProcessQueue(f);
}
boost::thread t(boost::bind(&CScheduler::serviceQueue, &s));
boost::this_thread::sleep_for(boost::chrono::seconds{1});
t.interrupt();
t.join();
}
BOOST_AUTO_TEST_SUITE_END()
複製程式碼
程式啟動後, 全域性物件連線管理器connman初始化後, connman 的Start 方法最後,通過scheduler 執行緒安排了一個定時任務: 每隔15分鐘, 把connman 物件內部成員,banmap_t 型別的 setBanned, CAddrMan 型別的addrman 序列化到本地檔案banlist.dat 和 peers.dat。
//init.cpp
if (!connman.Start(scheduler, connOptions)) {
return false;
}
//net.cpp
bool CConnman::Start(CScheduler& scheduler, const Options& connOptions)
{
...............
scheduler.scheduleEvery(std::bind(&CConnman::DumpData, this), DUMP_ADDRESSES_INTERVAL * 1000);
}
複製程式碼
如果錢包功能編譯使能, 會讓scheduler 執行緒安排每隔500毫秒重新整理錢包狀態。
//init.cpp
#ifdef ENABLE_WALLET
StartWallets(scheduler);
#endif
//wallet/init.cpp
void StartWallets(CScheduler& scheduler) {
for (CWalletRef pwallet : vpwallets) {
pwallet->postInitProcess(scheduler);
}
}
//wallet/wallet.cpp
void CWallet::postInitProcess(CScheduler& scheduler)
{
ReacceptWalletTransactions();
if (!CWallet::fFlushScheduled.exchange(true)) {
scheduler.scheduleEvery(MaybeCompactWalletDB, 500);
}
}
複製程式碼
PeerLogicValidation 物件的建構函式內部, scheduler 執行緒安排每45秒執行CheckForStaleTipAndEvictPeer函式主要做兩件事:
- 關掉多餘的外出tcp 連線
- 根據當前時間,檢查當前節點的blockchain 的tip 是否有可能過時了,建立額外的連線同步跟上
PeerLogicValidation::PeerLogicValidation(CConnman* connmanIn, CScheduler &scheduler) : connman(connmanIn), m_stale_tip_check_time(0) {
// Initialize global variables that cannot be constructed at startup.
recentRejects.reset(new CRollingBloomFilter(120000, 0.000001));
const Consensus::Params& consensusParams = Params().GetConsensus();
// Stale tip checking and peer eviction are on two different timers, but we
// don`t want them to get out of sync due to drift in the scheduler, so we
// combine them in one function and schedule at the quicker (peer-eviction)
// timer.
static_assert(EXTRA_PEER_CHECK_INTERVAL < STALE_CHECK_INTERVAL, "peer eviction timer should be less than stale tip check timer");
scheduler.scheduleEvery(std::bind(&PeerLogicValidation::CheckForStaleTipAndEvictPeers, this, consensusParams), EXTRA_PEER_CHECK_INTERVAL * 1000);
}
void PeerLogicValidation::CheckForStaleTipAndEvictPeers(const Consensus::Params &consensusParams)
{
if (connman == nullptr) return;
int64_t time_in_seconds = GetTime();
EvictExtraOutboundPeers(time_in_seconds);
if (time_in_seconds > m_stale_tip_check_time) {
LOCK(cs_main);
// Check whether our tip is stale, and if so, allow using an extra
// outbound peer
if (TipMayBeStale(consensusParams)) {
LogPrintf("Potential stale tip detected, will try using extra outbound peer (last tip update: %d seconds ago)
", time_in_seconds - g_last_tip_update);
connman->SetTryNewOutboundPeer(true);
} else if (connman->GetTryNewOutboundPeer()) {
connman->SetTryNewOutboundPeer(false);
}
m_stale_tip_check_time = time_in_seconds + STALE_CHECK_INTERVAL;
}
}
複製程式碼
以上就是bitoin 裡面CScheduler類的主要使用場景。
本文由 Copernicus團隊 喻建
編寫,轉載無需授權!