比特币源码分析:多线程检查脚本

区块链技术巴比特2018-03-23 16:31:59  阅读 -评论 0  阅读原文

比特币源码分析:多线程检查脚本

多线程脚本检查启动

多线程脚本检查启动代码:

bool AppInitMain(Config &config, boost::thread_group &threadGroup, CScheduler &scheduler) { ... if (nScriptCheckThreads) { for (int i = 0; i < nScriptCheckThreads - 1; i++) { threadGroup.create_thread(&ThreadScriptCheck); } } ... } static CCheckQueue scriptcheckqueue(128); void ThreadScriptCheck() { RenameThread("bitcoin-scriptch"); scriptcheckqueue.Thread(); }

在 AppInitMain 中根据选项,创建多个线程。 此处使用了boost的线程库,在绑定的线程函数ThreadScriptCheck中,调用一个全局状态的任务队列scriptcheckqueue。每个线程都去该队列中去任务,当队列中无任务可执行时,线程被条件变量阻塞。

任务队列

任务队列代码:

template class CCheckQueue { private: boost::mutex mutex; boost::condition_variable condWorker; boost::condition_variable condMaster; std::vector queue; int nIdle; int nTotal; bool fAllOk; unsigned int nTodo; bool fQuit; unsigned int nBatchSize; bool Loop(bool fMaster = false); public: //! Create a new check queue CCheckQueue(unsigned int nBatchSizeIn) : nIdle(0), nTotal(0), fAllOk(true), nTodo(0), fQuit(false), nBatchSize(nBatchSizeIn) {} void Thread() { Loop(); } bool Wait() { return Loop(true); } void Add(std::vector &vChecks) { boost::unique_lock lock(mutex); for (T &check : vChecks) { queue.push_back(std::move(check)); } nTodo += vChecks.size(); if (vChecks.size() == 1) { condWorker.notify_one(); } else if (vChecks.size() > 1) { condWorker.notify_all(); } } bool IsIdle() { boost::unique_lock lock(mutex); return (nTotal == nIdle && nTodo == 0 && fAllOk == true); } ~CCheckQueue() {} } bool CCheckQueue::Loop(bool fMaster = false){ boost::condition_variable &cond = fMaster ? condMaster : condWorker; std::vector vChecks; vChecks.reserve(nBatchSize); unsigned int nNow = 0; bool fOk = true; do { { boost::unique_lock lock(mutex); // first do the clean-up of the previous loop run (allowing us // to do it in the same critsect) if (nNow) { fAllOk &= fOk; nTodo -= nNow; if (nTodo == 0 && !fMaster) // We processed the last element; inform the master it // can exit and return the result condMaster.notify_one(); } else { nTotal++; } while (queue.empty()) { if ((fMaster || fQuit) && nTodo == 0) { nTotal--; bool fRet = fAllOk; // reset the status for new work later if (fMaster) fAllOk = true; return fRet; } nIdle++; cond.wait(lock); nIdle--; } nNow = std::max( 1U, std::min(nBatchSize, (unsigned int)queue.size() / (nTotal + nIdle + 1))); vChecks.resize(nNow); for (unsigned int i = 0; i < nNow; i++) { vChecks[i].swap(queue.back()); queue.pop_back(); //将放到局部队列中的任务清除 } fOk = fAllOk; } // execute work; 执行本线程刚分到的工作。 for (T &check : vChecks) { if (fOk) fOk = check(); } vChecks.clear(); } while (true); }

使用解读:

- boost::mutex mutex;: 互斥锁保护内部的状态
- boost::condition_variable condWorker;: 在没有工作时,工作线程阻塞条件变量
- boost::condition_variable condMaster;: 在没有工作时,master线程阻塞条件变量
- std::vector queue;: 要处理元素的队列
- int nIdle;: 空闲的工作线程数量(包含主线程)
- int nTotal;: 总的工作线程的数量,包含主线程
- bool fAllOk;: 临时评估结果
- unsigned int nTodo;: 还有多少验证任务没有完成,包括不在排队的,但仍在工作线程自己的批次中的任务数量
- bool fQuit;: 是否需要退出
- unsigned int nBatchSize;: 每个批次最大的元素处理数量

队列中使用了模板类,执行的验证任务由T标识,T都必须提供一个重载的operator()方法,并且反回一个bool。 默认为主线程push 批量任务到队列中,其他的工作线程去处理这些任务,当主线程push完任务后,也去处理这些任务,直到任务队列全部处理完毕。 上述是队列的实现:主要的任务处理是在Loop()函数中; 该队列会进行两种调用,来处理队列中的任务:

1. 添加任务后:自动唤醒阻塞的工作线程去处理添加的任务;细节请看:void Add(std::vector &vChecks)
2. 主线程添加完任务后,调用bool Wait(),也去处理队列中的任务,队列中的全部任务处理完后,主线程退出。 void Add():给类的内部队列批量添加任务,本次操作受锁保护,并更新所有的状态。

如果刚添加的任务数量为1,只唤醒一个工作线程去处理;否则,唤醒全部工作线程。

采用RAII机制去操作任务队列

RAII机制(Resource Acquisition Is Initialization)是Bjarne Stroustrup首先提出的。要解决的是这样一个问题:

在C++中,如果在这个程序段结束时需要完成一些资源释放工作,那么正常情况下自然是没有什么问题,但是当一个异常抛出时,释放资源的语句就不会被执行。 于是 [Bjarne Stroustrup] 就想到确保能运行资源释放代码的地方就是在这个程序段(栈帧)中放置的对象的析构函数了,因为 stack winding 会保证它们的析构函数都会被执行。

将初始化和资源释放都移动到一个包装类中的好处:

- 保证了资源的正常释放
- 省去了在异常处理中冗长而重复甚至有些还不一定执行到的清理逻辑,进而确保了代码的异常安全。
- 简化代码体积。

template class CCheckQueueControl { private: CCheckQueue *pqueue; bool fDone; public: CCheckQueueControl(CCheckQueue *pqueueIn) : pqueue(pqueueIn), fDone(false) { if (pqueue != nullptr) { bool isIdle = pqueue->IsIdle(); assert(isIdle); } } bool Wait() { if (pqueue == nullptr) return true; bool fRet = pqueue->Wait(); fDone = true; return fRet; } void Add(std::vector &vChecks) { if (pqueue != nullptr) pqueue->Add(vChecks); } ~CCheckQueueControl() { if (!fDone) Wait(); } };

该类主要是用来管理 CCheckQueue对象;采用RAII机制,保证每次析构该类的对象时,CCheckQueue中的任务队列被全部处理。 用来构建该对象的任务队列只能是nil, 或者队列中无任务。 因为创建的该对象在析构时会调用任务队列的wait()方法去处理完队列中所有的任务,然后退出。 方法解释:

- bool Wait()处理完队列中的所有任务后,该方法退出,并返回这些任务的处理结果
- void Add()向 CCheckQueue 中添加任务,唤醒子线程去处理
- ~CCheckQueueControl()对象析构时,调用wait()方法保证了该队列中的所有任务都被处理

CCheckQueue的使用

在块来的时候激活主链使用使用了检查队列:

static bool ConnectBlock(const Config &config, const CBlock &block, CValidationState &state, CBlockIndex *pindex, CCoinsViewCache &view, const CChainParams &chainparams, bool fJustCheck = false) { ... CCheckQueueControl control(fScriptChecks ? &scriptcheckqueue : nullptr); ... for (size_t i = 0; i < block.vtx.size(); i++) { ... if (!tx.IsCoinBase()) { Amount fee = view.GetValueIn(tx) - tx.GetValueOut(); nFees += fee.GetSatoshis(); // Don't cache results if we're actually connecting blocks (still // consult the cache, though). bool fCacheResults = fJustCheck; std::vector vChecks; if (!CheckInputs(tx, state, view, fScriptChecks, flags, fCacheResults, fCacheResults, PrecomputedTransactionData(tx), &vChecks)) { return error("ConnectBlock(): CheckInputs on %s failed with %s", tx.GetId().ToString(), FormatStateMessage(state)); } control.Add(vChecks); } ... } ... }

ConnectBlock将该区块链接到当前激活链上,并更新UTXO集合。 在该方法中:使用了全局对象scriptcheckqueue去构造了一个临时的管理对象,并通过该管理对象来操作全局任务队列,用来添加任务,以及执行任务。当该临时的管理对象析构时,会调用wait()方法,加入任务处理,处理完所有任务后,该对象析构完成。

CScriptCheck(根据每个交易输入构造的检查任务)

CScriptCheck源代码:

class CScriptCheck { private: CScript scriptPubKey; Amount amount; const CTransaction *ptxTo; unsigned int nIn; uint32_t nFlags; bool cacheStore; ScriptError error; PrecomputedTransactionData txdata; public: CScriptCheck() : amount(0), ptxTo(0), nIn(0), nFlags(0), cacheStore(false), error(SCRIPT_ERR_UNKNOWN_ERROR), txdata() {} CScriptCheck(const CScript &scriptPubKeyIn, const Amount amountIn, const CTransaction &txToIn, unsigned int nInIn, uint32_t nFlagsIn, bool cacheIn, const PrecomputedTransactionData &txdataIn) : scriptPubKey(scriptPubKeyIn), amount(amountIn), ptxTo(&txToIn), nIn(nInIn), nFlags(nFlagsIn), cacheStore(cacheIn), error(SCRIPT_ERR_UNKNOWN_ERROR), txdata(txdataIn) {} bool operator()(); void swap(CScriptCheck &check) { scriptPubKey.swap(check.scriptPubKey); std::swap(ptxTo, check.ptxTo); std::swap(amount, check.amount); std::swap(nIn, check.nIn); std::swap(nFlags, check.nFlags); std::swap(cacheStore, check.cacheStore); std::swap(error, check.error); std::swap(txdata, check.txdata); } ScriptError GetScriptError() const { return error; } };

代码解释:

- CScript scriptPubKey; 锁定脚本(即该验证交易的某个引用输出对应的锁定脚本)
- Amount amount; 上述锁定脚本对应的金额(即花费的UTXO的金额)
- const CTransaction *ptxTo; 正在花费的交易,即要检查的交易
- unsigned int nIn; 要检查该交易的第几个输入;
- uint32_t nFlags; 检查标识
- ScriptError error; 验证出错的原因
- bool operator()(); 此处重载了()运算符,执行脚本检查操作;

声明:链世界登载此文仅出于分享区块链知识,并不意味着赞同其观点或证实其描述。文章内容仅供参考,不构成投资建议。投资者据此操作,风险自担。此文如侵犯到您的合法权益,请联系我们100@7234.cn

    参与讨论 (0 人参与讨论)

    相关推荐

    区块链投资趋势报告:巨头入场布局行业趋于成熟

    区块链投资趋势报告:巨头入场布局行业趋于成熟

    来自:https://mp.weixin.qq.com/s?__biz=MzI4NzIxOTY1NA==&amp;mid=2650632639&amp;idx=1&amp;sn=e6d1c29731d992a80410aaee82ec3ea6&amp;chksm=f3d8db16c4af520097e4a64a71b1d4743ac326b9f027

    重新发明货币

    重新发明货币

    一、货币的演化过程 先简单回顾一下人类货币的演化过程,大概有以下阶段: a. 1.0版本:自然货币(贝壳、牲口、金银……) 这个阶段,货币基于一般等价物的稀有性或者实用性,货币不可能出现人为操纵的超发。 b. 2.0版本:早期纸币、银票到本位纸币 当贸易量越来越大,实物货币太不方便了,而且大家发现其实并不在意货币本身有什么价值,在意的只是这么多的货币能不能交换到足够的物品,于是纸币这种信用货

    从比特币交易看欧洲央行虚拟货币分类

    从比特币交易看欧洲央行虚拟货币分类

      互联网对传统社会的颠覆从未停止,在其完成对信息流、商流、物流、资金流的初步改造之后,或将以虚拟货币的形式打破现有货币体系   4月18日,在中国极客张沈鹏创办的比特币交易平台(42BTC.com)上,比特币对人民币的平均交易价为576元。当天,该平台完成了100个比特币的交易量。仅仅过去一周,4月25日上午,比特币对人民币的平均交易价已达到906元。据42BTC网站统计:在过去的32个月

    欧洲央行-比特币报告

    3.1 比特币 3.1.1 基本特征          比特币可能是最成功的,也可能是最有争议的虚拟货币方案,由日本程序员中本聪(译者注:事实上,中本聪是不是日本人,甚至是不是单个人无从考证)在2009年设计并实现。该计划基于一个类似于BitTorrent的P2P网络。BitTorrent是互联网上著名的共享文件协议,应用在电影,游戏和音乐领域。比特币在全球层面上运作,可用于各类货币交易(虚

    彻底玩转比特币地址和私匙

    彻底玩转比特币地址和私匙

    比特币地址和私匙是所有比特币初学者面对的一大难题,再加上那一串超长的字符串,让人更是摸不到头脑。 现在编者以问答的形式,带你一步步的揭开比特币地址和私匙的面纱。 还不知道什么是比特币地址和私匙的同学请点这里 问题一、比特币钱包由什么组成? 答 我们知道,比特币地址和私匙组成了比特币钱包,而私匙则决定了比特币地址上比特币的归属。 地址和私匙 问题二、如果只记得私匙我们还能还原比特币地址么? 答

    用GO语言实现比特币算法

    用GO语言实现比特币算法

    本节的这个例子展示一点点高精度数学包math/big、一点点散列包hash、一点点加密包crypto,还有一点点测试包testing的知识。这里不介绍bitcoin协议和算法——尽管它们很有趣,而是试图指出,Go对多种操作系统的支持,是实现这种跨平台应用的理想语言。 位钱(bitcoin)是一种使用加密手段制作的分布式电子货币。它最初于1998年由Wei Dai提出,并由中本聪(Satoshi

    详解比特币的找零机制

    详解比特币的找零机制

    比特币的找零机制一直让人有些迷惑,明明只向一个地址发送了比特币为什么 blockchain 上面的显示的有时是1个地址对多个地址,有时是多个地址对1个地址,有时又显示多个地址对多个地址? 为什么比特币资深用户要提醒大家当比特币钱包交易100次以上时再次交易后要重新备份钱包,恢复以前的钱包备份有可能会遭遇损失? 是的,这一切都是因为比特币的找零(Change)机制。本文参考 Bitcoin的维

    玩转比特币客户端之一:C盘转移和加速下载

    玩转比特币客户端之一:C盘转移和加速下载

    C盘空间不足?交易数据下载速度太慢?别着急,乐享比特币教你轻松玩转比特币官方客户端。 所有新人开始接触比特币时做的第一件事情大多数是安装比特币的官方客户端。 安全起见大家最好直接访问官方发布渠道sourceforge的地址进行下载:http://sourceforge.net/projects/bitcoin/files/Bitcoin/ 该网页列出了各版本的官方比特币客户端,目前

    麦妖榜
    更新日期 2019-06-16
    排名用户贡献值
    1BitettFan23992
    2等待的宿命23809
    3六叶树20309
    4区块大康18606
    5天下无双16192
    6linjm122715948
    7牛市来了15758
    8lizhen00215077
    9让时间淡忘14475
    10冷风大q11188
    返回顶部 ↑