比特币源码研读之二十一
0 引子
今天是大年三十,除夕之夜,明天就是狗年了,预祝大家新年快乐,狗年旺起来!
自从进入区块链行业之后就发现根本停不下来,放假在家也把电脑带回来了,每天都要学习区块链知识,看区块链新闻,因为区块链的世界实在是太快了,不想落后,所以今天就算是过大年也没休息,坚持学习。作为区块链程序猿,技术的学习是必不可少的,尤其是比特币的源码,我觉得是必经之路,因为基本上所有的区块链项目都是在比特币的架构基础上发展和改进的。所以我也一直在研读比特币的源码,尽自己所能覆盖比特币的所有源码。
我们今天将继续深入AppInitMain函数,本文将主要分析两个与线程相关的内容:脚本验证线程与任务调度线程内容。本文主要涉及的源码文件包括:
src/bitcond.cpp、src/init.h、src/init.cpp、src/util.h、src/util.cpp、src/validation.h、src/validation.cpp、src/scheduler.h、src/scheduler.cpp、src/interpreter.h、src/interpreter.cpp
一、脚本验证线程
此处的脚本验证线程代码如下:
LogPrintf("Using %u threads for scriptverification\n", nScriptCheckThreads);
if(nScriptCheckThreads) {
for(int i=0; i
threadGroup.create_thread(&ThreadScriptCheck);
}
此处代码中的nScriptCheckThreads我们已在《比特币源码研读之十三》中的“二、验证脚本线程数”进行了详细说明,而后面的线程组threadGroup创建脚本验证线程的代码也有相应的简要说明。具体如下:
通过线程组创建nScriptCheckThreads个数量的脚本验证线程,线程处理函数为ThreadScriptCheck,其定义于src/validation.h中,实现于src/validation.cpp中,在该函数中通过脚本验证队列管理脚本验证线程,其具体运行方式我们将AppInitMain函数的研读中详细说明。
我们现在来看下src/validation.h中的ThreadScriptCheck函数,其在src/validation.h中的定义如下:
/** Run an instance of the script checking thread */
void ThreadScriptCheck();
通过其注释我们可以看出该函数为运行一个脚本验证线程的实例,其实现代码位于src/validation.cpp中,代码如下;
void ThreadScriptCheck() {
RenameThread("bitcoin-scriptch");
scriptcheckqueue.Thread();
}
在该函数中首先通过RenameThread函数定义了运行脚本验证线程的名字:bitcoin-scriptch。然后通过脚本验证队列对象scriptcheckqueue启动脚本验证线程。scriptcheckqueue在ThreadScriptCheck函数之上进行了定义:
staticCCheckQueue scriptcheckqueue(128);
第一次看到这个定义,大家有可能会误以为这是一个数组,然而并不是的。因为CCheckQueue是个验证队列模板类,其定义位于src/checkqueue.h中,template classCCheckQueue,其注释如下;
/**
* Queue forverifications that have to be performed.
* Theverifications are represented by a type T, which must provide an
* operator(),returning a bool.
*
* One thread(the master) is assumed to push batches of verifications
* onto thequeue, where they are processed by N-1 worker threads. When
* the masteris done adding work, it temporarily joins the worker pool
* as an N'thworker, until all jobs are done.
*/
通过以上注释我们可以知道该队列是用于执行验证的队列,验证类为typename T,类T必须实现返回bool值的operator()函数。我们待会再看下CScriptCheck是否提供了operator()函数。同时,该队列的实现过程是:
(1)主线程(Master)会将验证批量地分配到队列中,然后这些队列的任务由N-1个工作线程处理;
(2)当主线程完成了任务添加后,它也暂时作为第N个工人加入到工人池队列中,知道所有任务都完成才退出。
staticCCheckQueue scriptcheckqueue(128);中的128是给CCheckQueue模板中的nBatchSizeIn赋值的,用于限制每批的处理任务数,即脚本验证个数。
我们再来看下CScriptCheck类,其定义于validation.h中,其定义如下;
从该类的注释我们可以看出其为脚本验证的封装,并且包含了输出交易的信息,也就是说主要是对输出交易的脚本验证。
分析完模板类和脚本验证类后,我们具体来看下线程的执行代码,即从scriptcheckqueue.Thread();调用开始,Thread()函数位于CCheckQueue模板中,该函数的代码很简单,运行了私有函数Loop();,从Loop函数的注释我们可以看出脚本验证就是在此处运行的,此函数中大部分代码是在进行脚本验证任务的筛选与判断,而真正进行脚本验证则是在Loop函数的最下面代码运行的,如图所示:
该部分代码中,循环遍历验证脚本,并运行check()函数,check为CScriptCheck类的实例对象,check()函数实际运行的是CScriptCheck::operator()()函数,该函数代码如下:
bool CScriptCheck::operator()() {
constCScript &scriptSig = ptxTo->vin[nIn].scriptSig;
constCScriptWitness *witness = &ptxTo->vin[nIn].scriptWitness;
if(!VerifyScript(scriptSig, scriptPubKey, witness, nFlags,CachingTransactionSignatureChecker(ptxTo, nIn, amount, cacheStore, *txdata),&error)) {
returnfalse;
}
returntrue;
}
在脚本验证函数中,我们可以看到验证函数为VerifyScript函数,该函数在src\script\interpreter.h、src\script\interpreter.cpp中声明与定义,在该函数中对交易脚本进行了验证,验证过程中根据不同脚本类型进行了验证,因为脚本对于比特币交易来说至关重要,所以具体过程我们将在后面专门写几篇文章详细描述脚本概念、脚本定义、脚本执行以及脚本验证的源码分析文章。
此处即完成了脚本验证线程创建与启动线程代码的分析。
二、任务调度线程
下面我们将进入轻量级任务调度线程的初始化代码部分,代码如下:
// Start the lightweight task scheduler thread
CScheduler::Function serviceLoop =boost::bind(&CScheduler::serviceQueue, &scheduler);threadGroup.create_thread(boost::bind(&TraceThread,"scheduler", serviceLoop));
关于任务调度,我们首先来看任务调度类CScheduler,该类在src/scheduler中定义,在该类文件的最上方有其注释以及使用示例:
其注释说明了任务调度类的作用是用于后台运行任务,这些任务是周期性或随后的某个时刻一次性运行的。
通过其使用示例可以看出我们可以在任务调度函数中定义某个任务从当前时刻后的某个执行时间,完成任务定义后我们可以将该任务放入线程中等待执行,而这些任务的调度是通过serviceQueue函数来处理的。
三、结论
从上面分析我们可以很清楚地明白我们现在分析的这两行任务分析代码的含义,即启动任务调度队列,并将其放入线程中,然后启动线程等待任务的加入,随后根据任务的运行时间,正确调度并执行任务。
以上就是今天的比特币源码研读记录,主要对脚本验证线程和任务调度线程进行了分析,下一篇文章我们将继续分析后面的代码,将这条主线延续下去。
区块链研习社比特币源码研读班 菜菜子
本文由【区块链研习社】优质内容计划支持,更多关于区块链的深度好文,请点击【区块链研习社】简书专栏:http://www.jianshu.com/c/b17f09dc2831