一 nodeos工作線程
nodeso節點的工作線程包括:一個主線程,一個信號處理線程和兩個線程池。
-
主線程:main函數啓動線程,該線程執行完程序初始化工作後,會調用app().io_service.run(), 啓動boost::asio::io_service的異步io服務,通過異步io方式完成節點塊生產,交易處理等主要業務工作。
- 信號處理線程:子線程,通過異步io服務,接收系統信號並處理。
-
線程池,線程池啓動的工作線程數可通過啓動參數配置。
- controller線程池: 異步執行塊block_state創建,塊中交易驗證時的交易解簽名計算。
- 生產者插件線程池:負責異步執行交易解簽名計算。
採用默認配置(每個線程池2個工作線程),nodeos節點總線程數是6個,通過pstree命令可查看:
nodeos有一個主線程pid=32385,該主線程有5個子線程,32386~32390。
二 主線程
main函數執行線程:main函數最後調用app().exec(),啓動io_service服務。
app()是application實例,application中定義了io_service對象io_serv:
class application
{...
std::shared_ptr<boost::asio::io_service> io_serv;
}
application::application():my(new application_impl()){
io_serv = std::make_shared<boost::asio::io_service>();
}
boost::asio::io_service& get_io_service() { return *io_serv; }
//啓動io服務
void application::exec() {
io_serv->run();
shutdown();
}
void application::quit() {
my->_is_quiting = true;
io_serv->stop();
}
- 其它插件通過get_io_service()函數,獲取io_serv,進行異步io投遞。
- 程序退出時,會調用quit()函數,結束io服務。
nodeos節點交易處理,出塊,塊驗證等主要業務操作都是在該線程執行的,因爲eos中交易不支持並行處理,所以application中的io_serv是不允許在除主線程之外的其它線程中重複執行io_serv.run()操作的。
看到application::exec()中的代碼,有些人可能會有疑問:asio::io_service監聽的io端口都完成(沒有待監聽的io端口)時,io_serv->run()就會退出。這裏io_serv->run()只調用了一次,沒有循環調用,不會退出麼?
nodeos投遞到application::io_serv的io處理handle函數,會重複投遞該io端口。所以在handle處理函數完成時,io_serv中總會有待完成io存在,io_serv->run()就不會退出,下面以producer_plugin中的_timer爲例,看一下這個投遞過程:
producer_plugin::producer_plugin()
: my(new producer_plugin_impl(app().get_io_service())){
my->_self = this;
};
class producer_plugin_impl {
producer_plugin_impl(boost::asio::io_service& io):_timer(io),
_transaction_ack_channel(app().get_channel<compat::channels::transaction_ack>()){}
boost::asio::deadline_timer _timer;
};
producer_plugin()在構造時傳入app().get_io_service()構造了producer_plugin_impl:: _timer; 在節點的生產循環函數中可以看到, _timer的handle函數中會調用schedule_production_loop()函數,而在該函數中,又會調用 _timer.asyncwait()重複投遞 _timer到ioservice中:
三 信號處理線程
- 創建io_service對象sig_io_serv;
- 將SIGINT信號投遞到io_service對象,並綁定信號處理函數,當系統發送SIGINT信號時,會觸發該處理函數。
- 創建sig_thread信號處理線程,在該線程中調用sig_io_serv->run(),等待信號觸發,調用相應處理函數。
- 信號處理線程只處理SIGINT,SIGTERM,SIGPIPE這三個系統信號,一旦收到信號,會調用退出操作,使nodeos退出。
四 controller線程池
4.1 定義及創建
struct controller_impl {
...
boost::asio::thread_pool thread_pool;
}
controller_impl( const controller::config& cfg, controller& s ):self(s),
chain_id( cfg.genesis.compute_chain_id() ),
read_mode( cfg.read_mode ),
...
thread_pool( cfg.thread_pool_size )
{...}
- controller線程池定義在controller_impl中;
- 根據配置參數中設定的thread_pool_size創建相應數量的工作線程。
4.2 異步任務
執行塊相關操作時,較耗時且與排序無關的動作都會投遞到controller線程池執行。eos目前投遞到該線程池執行的操作有兩個:
1 塊交易驗證時的解簽名操作;
2 塊狀態block_state數據創建操作(兩輪共識計算都在這裏完成)。
4.2.1 解簽名操作
節點收到塊,會調用apply_block()函數執行塊中交易,進行塊驗證。期間會投遞交易解簽名計算到controller線程池:
解簽名投遞函數create_signing_keys_futrue():
- 在線程池thread_pool中異步執行解簽名函數,異步解簽名的執行結果,會放入交易的signing_keys_future中。
- 真正解簽名算法是trn.get_signature_keys(),解出的公鑰放到recovered_pub_keys中。
4.2.2 block_state創建
五 生產者線程池
5.1 定義及創建
生產者線程池定義在producer_plugin_impl中,執行插件初始化函數plugin_initialize()時會根據配置參數創建工作線程。
class producer_plugin_impl : public std::enable_shared_from_this<producer_plugin_impl> {
public:
void schedule_production_loop();//生產處理循環
//線程池定義
fc::optional<boost::asio::thread_pool> _thread_pool;//異步線程池
...
}
void producer_plugin::plugin_initialize(const boost::program_options::variables_map& options)
{
auto thread_pool_size = options.at( "producer-threads" ).as<uint16_t>();
//線程池創建
my->_thread_pool.emplace( thread_pool_size );
}
5.2 異步任務
生產者線程池負責的工作任務有兩個:
1. 爲接收到的投遞交易進行異步解簽名計算;
2. 等待解簽名計算完成,將交易投遞到主線程的異步io服務中處理。
這兩個工作任務都在同一個函數中投遞執行:
該函數在節點收到其它節點/客戶端投遞的交易時被調用:
-
投遞異步解簽名計算任務到 _threadpool線程池( 生產者線程池),計算結果放到
trx->signing_keys_future中。
- 投遞異步任務到 _thread_pool線程池,該任務等待將異步解簽名計算結束的交易投遞到主線程的io_service中執行。
5.3 線程池關閉
當調用插件shutdown函數時,會執行線程池關閉動作,前程池中的工作線程退出。