Google Test(GTest)使用方法和源码解析——死亡测试技术分析和应用
死亡测试是为了判断一段逻辑是否会导致进程退出而设计的。这种场景并不常见,但是GTest依然为我们设计了这个功能。我们先看下其应用实例。(转载请指明出于breaksoftware的csdn博客)
死亡测试技术应用
我们可以使用TEST声明并注册一个简单的测试特例。其实现内部才是死亡测试相关代码运行的地方。GTest为我们提供了如下的宏用于组织测试逻辑
Fatal assertion | Nonfatal assertion | Verifies |
ASSERT_DEATH(statement, regex); | EXPECT_DEATH(statement, regex); | statement crashes with the given error |
ASSERT_DEATH_IF_SUPPORTED(statement, regex); | EXPECT_DEATH_IF_SUPPORTED(statement, regex); | if death tests are supported, verifies that statement crashes with the given error; otherwise verifies nothing |
ASSERT_EXIT(statement, predicate, regex); | EXPECT_EXIT(statement, predicate, regex); | statement exits with the given error and its exit code matches predicate |
宏中的statement是测试逻辑的表达式,它可以是个函数,可以是个对象的方法,也可以是几个表达式的组合,比如
EXPECT_DEATH({ int n = 4; n = 5;},"");
regex是一个正则表达式,它用于匹配stderr输出的内容。如果匹配上了,则测试成功,否则测试失败。比如
void Foo() {std::cerr<<"Failed Foo";_exit(0);
}
EXPECT_DEATH(Foo(),".*Foo");
EXPECT_DEATH(Foo(),".*FAAA");
第5行的局部测试匹配上了测试预期,而第6行没有。
注意下正则表达式这个功能只支持linux系统,windows上不支持,所以windows上我们对此参数传空串。我们看个完整的例子
void Foo() {std::cerr<<"Fail Foo";_exit(0);
}TEST(MyDeathTest, Foo) {EXPECT_EXIT(Foo(), ::testing::ExitedWithCode(0), ".*Foo");
}
注意下我们测试用例名——MyDeathTest。GTest强烈建议测试用例名以DeathTest结尾。这是为了让死亡测试在所有其他测试之前运行。
死亡测试技术分析
死亡测试非常依赖于系统的实现。本文并不打算把每个系统都覆盖到,我将以windows系统上的实现详细讲解其过程。在Linux上实现的思路基本和windows上相同,只是在一些系统实现上存在差异导致GTest具有不同的属性。
先概括的讲一下windows上实现的过程
- 测试实体中准备启动新的进程,进程路径就是本进程可执行文件路径
- 子进程传入了标准输入输出句柄
- 启动子进程时传入类型筛选,即指定执行该测试用例
- 监听子进程的输出
- 判断子进程退出模式
子进程的执行过程是:
- 执行父进程指定的测试特例
- 运行死亡测试宏中的表达式
- 如果没有crash,则根据情况选择退出模式
我们来看下EXPECT_DEATH的实现,其最终将调用到GTEST_DEATH_TEST_宏中
# define GTEST_DEATH_TEST_(statement, predicate, regex, fail) \GTEST_AMBIGUOUS_ELSE_BLOCKER_ \if (::testing::internal::AlwaysTrue()) { \const ::testing::internal::RE& gtest_regex = (regex); \::testing::internal::DeathTest* gtest_dt; \if (!::testing::internal::DeathTest::Create(#statement, >est_regex, \__FILE__, __LINE__, >est_dt)) { \goto GTEST_CONCAT_TOKEN_(gtest_label_, __LINE__); \} \if (gtest_dt != NULL) { \::testing::internal::scoped_ptr< ::testing::internal::DeathTest> \gtest_dt_ptr(gtest_dt); \switch (gtest_dt->AssumeRole()) { \case ::testing::internal::DeathTest::OVERSEE_TEST: \if (!gtest_dt->Passed(predicate(gtest_dt->Wait()))) { \goto GTEST_CONCAT_TOKEN_(gtest_label_, __LINE__); \} \break; \case ::testing::internal::DeathTest::EXECUTE_TEST: { \::testing::internal::DeathTest::ReturnSentinel \gtest_sentinel(gtest_dt); \GTEST_EXECUTE_DEATH_TEST_STATEMENT_(statement, gtest_dt); \gtest_dt->Abort(::testing::internal::DeathTest::TEST_DID_NOT_DIE); \break; \} \default: \break; \} \} \} else \GTEST_CONCAT_TOKEN_(gtest_label_, __LINE__): \fail(::testing::internal::DeathTest::LastMessage())
第5行我们声明了一个DeathTest*指针,这个类暴露了一个静态方法用于创建对象。可以说它是一个接口类,我们看下它重要的部分定义
enum TestRole { OVERSEE_TEST, EXECUTE_TEST };// An enumeration of the three reasons that a test might be aborted.enum AbortReason {TEST_ENCOUNTERED_RETURN_STATEMENT,TEST_THREW_EXCEPTION,TEST_DID_NOT_DIE};// Assumes one of the above roles.virtual TestRole AssumeRole() = 0;// Waits for the death test to finish and returns its status.virtual int Wait() = 0;// Returns true if the death test passed; that is, the test process// exited during the test, its exit status matches a user-supplied// predicate, and its stderr output matches a user-supplied regular// expression.// The user-supplied predicate may be a macro expression rather// than a function pointer or functor, or else Wait and Passed could// be combined.virtual bool Passed(bool exit_status_ok) = 0;// Signals that the death test did not die as expected.virtual void Abort(AbortReason reason) = 0;
TestRole就是角色,我们父进程角色是OVERSEE_TEST,子进程的角色是EXECUTE_TEST。因为父子进程都将进入这个测试特例逻辑,所以要通过角色标记来区分执行逻辑。AbortReason枚举中类型表达了测试终止的原因。
AssumeRole是主要是父进程启动子进程的逻辑。Wait是父进程等待子进程执行完毕,并尝试读取子进程的输出。
DeathTest::Create方法最终会进入DefaultDeathTestFactory::Create方法
bool DefaultDeathTestFactory::Create(const char* statement, const RE* regex,const char* file, int line,DeathTest** test) {UnitTestImpl* const impl = GetUnitTestImpl();const InternalRunDeathTestFlag* const flag =impl->internal_run_death_test_flag();const int death_test_index = impl->current_test_info()->increment_death_test_count();if (flag != NULL) {if (death_test_index > flag->index()) {DeathTest::set_last_death_test_message("Death test count (" + StreamableToString(death_test_index)+ ") somehow exceeded expected maximum ("+ StreamableToString(flag->index()) + ")");return false;}if (!(flag->file() == file && flag->line() == line &&flag->index() == death_test_index)) {*test = NULL;return true;}}
此处通过获取flag变量,得知当前运行的是子进程还是父进程。如果flag不是NULL,则是子进程,它主要做些输出的工作;如果是父进程,则进入下面代码
# if GTEST_OS_WINDOWSif (GTEST_FLAG(death_test_style) == "threadsafe" ||GTEST_FLAG(death_test_style) == "fast") {*test = new WindowsDeathTest(statement, regex, file, line);}# elseif (GTEST_FLAG(death_test_style) == "threadsafe") {*test = new ExecDeathTest(statement, regex, file, line);} else if (GTEST_FLAG(death_test_style) == "fast") {*test = new NoExecDeathTest(statement, regex);}# endif // GTEST_OS_WINDOWS
可见Windows上死亡测试最终将由WindowsDeathTest代理,而linux系统根据传入参数不同而选择不同的类。它们都是DeathTest的派生类。为什么linux系统上支持参数选择,这要从系统暴露出来的接口和系统实现来说。windows系统上进程创建只要调用CreateProcess之类的函数就可以了,这个函数调用后,子进程就创建出来了。而linux系统上则要调用fork或者clone之类,这两种函数执行机制也不太相同。fork是标准的子进程和父进程分离执行,所以threadsafe对应的ExecDeathTest类在底层调用的是fork,从而可以保证是安全的。但是clone用于创建轻量级进程,即创建的子进程与父进程共用线性地址空间,只是它们的堆栈不同,这样不用执行父子进程分离,执行当然会快些,所以这种方式对应的是fast——NoExecDeathTest。
我们看下WindowsDeathTest::AssumeRole()的实现
// The AssumeRole process for a Windows death test. It creates a child
// process with the same executable as the current process to run the
// death test. The child process is given the --gtest_filter and
// --gtest_internal_run_death_test flags such that it knows to run the
// current death test only.
DeathTest::TestRole WindowsDeathTest::AssumeRole() {const UnitTestImpl* const impl = GetUnitTestImpl();const InternalRunDeathTestFlag* const flag =impl->internal_run_death_test_flag();const TestInfo* const info = impl->current_test_info();const int death_test_index = info->result()->death_test_count();if (flag != NULL) {// ParseInternalRunDeathTestFlag() has performed all the necessary// processing.set_write_fd(flag->write_fd());return EXECUTE_TEST;}
这段代码的注释写的很清楚,父进程将向子进程传递什么样的参数。
和之前一样,需要获取flag,如果不是NULL,则是子进程,设置写入句柄,并返回自己角色。如果是父进程则执行下面逻辑
// WindowsDeathTest uses an anonymous pipe to communicate results of// a death test.SECURITY_ATTRIBUTES handles_are_inheritable = {sizeof(SECURITY_ATTRIBUTES), NULL, TRUE };HANDLE read_handle, write_handle;GTEST_DEATH_TEST_CHECK_(::CreatePipe(&read_handle, &write_handle, &handles_are_inheritable,0) // Default buffer size.!= FALSE);set_read_fd(::_open_osfhandle(reinterpret_cast<intptr_t>(read_handle),O_RDONLY));write_handle_.Reset(write_handle);event_handle_.Reset(::CreateEvent(&handles_are_inheritable,TRUE, // The event will automatically reset to non-signaled state.FALSE, // The initial state is non-signalled.NULL)); // The even is unnamed.GTEST_DEATH_TEST_CHECK_(event_handle_.Get() != NULL);const std::string filter_flag =std::string("--") + GTEST_FLAG_PREFIX_ + kFilterFlag + "=" +info->test_case_name() + "." + info->name();const std::string internal_flag =std::string("--") + GTEST_FLAG_PREFIX_ + kInternalRunDeathTestFlag +"=" + file_ + "|" + StreamableToString(line_) + "|" +StreamableToString(death_test_index) + "|" +StreamableToString(static_cast<unsigned int>(::GetCurrentProcessId())) +// size_t has the same width as pointers on both 32-bit and 64-bit// Windows platforms.// See http://msdn.microsoft.com/en-us/library/tcxf1dw6.aspx."|" + StreamableToString(reinterpret_cast<size_t>(write_handle)) +"|" + StreamableToString(reinterpret_cast<size_t>(event_handle_.Get()));char executable_path[_MAX_PATH + 1]; // NOLINTGTEST_DEATH_TEST_CHECK_(_MAX_PATH + 1 != ::GetModuleFileNameA(NULL,executable_path,_MAX_PATH));std::string command_line =std::string(::GetCommandLineA()) + " " + filter_flag + " \"" +internal_flag + "\"";DeathTest::set_last_death_test_message("");CaptureStderr();// Flush the log buffers since the log streams are shared with the child.FlushInfoLog();// The child process will share the standard handles with the parent.STARTUPINFOA startup_info;memset(&startup_info, 0, sizeof(STARTUPINFO));startup_info.dwFlags = STARTF_USESTDHANDLES;startup_info.hStdInput = ::GetStdHandle(STD_INPUT_HANDLE);startup_info.hStdOutput = ::GetStdHandle(STD_OUTPUT_HANDLE);startup_info.hStdError = ::GetStdHandle(STD_ERROR_HANDLE);PROCESS_INFORMATION process_info;GTEST_DEATH_TEST_CHECK_(::CreateProcessA(executable_path,const_cast<char*>(command_line.c_str()),NULL, // Retuned process handle is not inheritable.NULL, // Retuned thread handle is not inheritable.TRUE, // Child inherits all inheritable handles (for write_handle_).0x0, // Default creation flags.NULL, // Inherit the parent's environment.UnitTest::GetInstance()->original_working_dir(),&startup_info,&process_info) != FALSE);child_handle_.Reset(process_info.hProcess);::CloseHandle(process_info.hThread);set_spawned(true);return OVERSEE_TEST;
这段逻辑创建了父进程和子进程通信的匿名管道和事件句柄,这些都通过命令行参数传递给子进程。
我们再看下父进程等待的过程
int WindowsDeathTest::Wait() {if (!spawned())return 0;// Wait until the child either signals that it has acquired the write end// of the pipe or it dies.const HANDLE wait_handles[2] = { child_handle_.Get(), event_handle_.Get() };switch (::WaitForMultipleObjects(2,wait_handles,FALSE, // Waits for any of the handles.INFINITE)) {case WAIT_OBJECT_0:case WAIT_OBJECT_0 + 1:break;default:GTEST_DEATH_TEST_CHECK_(false); // Should not get here.}// The child has acquired the write end of the pipe or exited.// We release the handle on our side and continue.write_handle_.Reset();event_handle_.Reset();ReadAndInterpretStatusByte();
它等待子进程句柄或者完成事件。一旦等到,则在ReadAndInterpretStatusByte中读取子进程的输出
void DeathTestImpl::ReadAndInterpretStatusByte() {char flag;int bytes_read;// The read() here blocks until data is available (signifying the// failure of the death test) or until the pipe is closed (signifying// its success), so it's okay to call this in the parent before// the child process has exited.do {bytes_read = posix::Read(read_fd(), &flag, 1);} while (bytes_read == -1 && errno == EINTR);if (bytes_read == 0) {set_outcome(DIED);} else if (bytes_read == 1) {switch (flag) {case kDeathTestReturned:set_outcome(RETURNED);break;case kDeathTestThrew:set_outcome(THREW);break;case kDeathTestLived:set_outcome(LIVED);break;case kDeathTestInternalError:FailFromInternalError(read_fd()); // Does not return.break;default:GTEST_LOG_(FATAL) << "Death test child process reported "<< "unexpected status byte ("<< static_cast<unsigned int>(flag) << ")";}} else {GTEST_LOG_(FATAL) << "Read from death test child process failed: "<< GetLastErrnoDescription();}GTEST_DEATH_TEST_CHECK_SYSCALL_(posix::Close(read_fd()));set_read_fd(-1);
}
这段代码可以用于区分子进程的退出状态。如果子进程crash了,则读取不到数据,进入第14行。
子进程则是执行完表达式后调用Abort返回相应错误。GTEST_DEATH_TEST_剩下的实现,把这个过程表达的很清楚
if (gtest_dt != NULL) { \::testing::internal::scoped_ptr< ::testing::internal::DeathTest> \gtest_dt_ptr(gtest_dt); \switch (gtest_dt->AssumeRole()) { \case ::testing::internal::DeathTest::OVERSEE_TEST: \if (!gtest_dt->Passed(predicate(gtest_dt->Wait()))) { \goto GTEST_CONCAT_TOKEN_(gtest_label_, __LINE__); \} \break; \case ::testing::internal::DeathTest::EXECUTE_TEST: { \::testing::internal::DeathTest::ReturnSentinel \gtest_sentinel(gtest_dt); \GTEST_EXECUTE_DEATH_TEST_STATEMENT_(statement, gtest_dt); \gtest_dt->Abort(::testing::internal::DeathTest::TEST_DID_NOT_DIE); \break; \} \default: \break; \} \} \
相关文章:
java学习笔记11--Annotation
java学习笔记11--Annotation Annotation:在JDK1.5之后增加的一个新特性,这种特性被称为元数据特性,在JDK1.5之后称为注释,即:使用注释的方式加入一些程序的信息。 java.lang.annotation Annotation接口是所有的Annotat…

GoogleLog(GLog)源码分析
GLog是Google开发的一套日志输出框架。由于其具有功能强大、方便使用等特性,它被众多开源项目使用。本文将通过分析其源码,解析Glog实现的过程。 该框架的源码在https://github.com/google/glog上可以获取到。本文将以目前最新的0.3.3版本源码为范例进行…

Ajax Toolkit 控件学习系列(13) ——FilteredTextBoxExtender 控制输入
这个控件的作用是对TextBox所要输入的内容进行过滤控制。按照自己需要过滤,可以自定义,再或者使用定义好的方式。看效果。效果不是很突出,说明下,就是只能输入大写字母和数字。因为加了限制,但是具体有什么高深的应用呢…
Uber最新开源Manifold,助力机器学习开发者的可视化与调试需求
所有参与投票的 CSDN 用户都参加抽奖活动群内公布奖项,还有更多福利赠送作者 | Lezhi Li译者 | 凯隐编辑 | Jane出品 | AI科技大本营(ID:rgznai100)【导语】2019 年 1 月,Uber 推出了 Manifold,一款与模型无…

jQuery对象和DOM对象使用说明
1.jQuery对象和DOM对象第一次学习jQuery,经常分辨不清哪些是jQuery对象,哪些是 DOM对象,因此需要重点了解jQuery对象和DOM对象以及它们之间的关系.DOM对象,即是我们用传统的方法(javascript)获得的对象,jQuery对象即是用jQuery类库…

[WPF疑难]避免窗口最大化时遮盖任务栏
[WPF疑难]避免窗口最大化时遮盖任务栏 周银辉 WPF窗口最大化时有个很不好的现象是:如果窗口的WindowStyle被直接或间接地设置为None后(比如很多情况下你会覆盖默认的窗体样式,即不采用Windows默认的边框和最大化最等按钮,来打造个…

Google Mock(Gmock)简单使用和源码分析——简单使用
初识Gmock是之前分析GTest源码时,它的源码和GTest源码在同一个代码仓库中(https://github.com/google/googletest)。本文我将以目前最新的Gmock1.7版本为范例,分析其实现原理。(转载请指明出于breaksoftware的csdn博客…
浪潮刘军:为什么说计算力是AI时代“免费的午餐”?
出品 | AI科技大本营(ID:rgznai100)产业AI、元脑生态是浪潮集团2019年度的两大关键词。作为一家以计算力为核心生产力的企业,浪潮还一直强调人工智能计算是未来最重要的计算力,而无论产业AI、元脑生态都构筑于计算的基础设施之上。…

Journey源码分析四:url路由
2019独角兽企业重金招聘Python工程师标准>>> 在入口函数main()的default分支中,对路由进行了注册,现在分析下。 ##main()中路由注册相关代码 源码: httpRouter : httptreemux.New() // Blog and pages as http server.InitializeBlog(httpRou…
“天河二号”总工程师杜云飞谈星光超算应用平台设计
整理 | 夕颜出品 | AI科技大本营(ID:rgznai100)【导读】12 月 21-22 日,OpenI/O 启智开发者大会在深圳召开。在大会上, 国家超级计算广州中心总工程师、“天河二号”总工程师杜云飞发表了题为《星光超算应用平台》的主题报告&…

Google Mock(Gmock)简单使用和源码分析——源码分析
源码分析 通过《Google Mock(Gmock)简单使用和源码分析——简单使用》中的例子,我们发现被mock的相关方法在mock类中已经被重新实现了,否则它们也不会按照我们的期待的行为执行。我们通过阅读源码,来分析整个过程的实现逻辑。(转载…

远程控制软件VNC教程和对内网机器控制的实现
网络遥控技术是指由一部计算机(主控端)去控制另一部计算机(被控端),而且当主控端在控制端时,就如同用户亲自坐在被控端前操作一样,可以执行被控端的应用程序,及使用被控端的系统资源…

GRUB2相关概念
GNU GRUB,简称“GRUB”,是一个来自GNU项目的启动引导程序。GRUB是多启动规范的实现,它允许用户可以在计算机内同时拥有多个操作系统,并在计算机启动时选择希望运行的操作系统。GRUB可用于选择操作系统分区上的不同内核,…
朴素、Select、Poll和Epoll网络编程模型实现和分析——朴素模型
做Linux网络开发,一般绕不开标题中几种网络编程模型。网上已有很多写的不错的分析文章,它们的基本论点是差不多的。但是我觉得他们讲的还不够详细,在一些关键论点上缺乏数据支持。所以我决定好好研究这几个模型。(转载请指明出于b…
支付宝账单出来后,除了总消费,你看到你的学习支出了吗?
当支付宝的2019年年度账单出来的时候很多人的第一反应就是我怎么花了这么多钱不少人都有这样的困惑忙忙碌碌一年到头,到底得到了什么?而这一切又和自己最开始的规划一样吗?其实从账单上可以看出你在2019年花费了哪些大头居家生活、穿衣打扮还…

体验Windows 7的Superbar
随着PDC 2008上Windows 7 M3 6801的发布,这个微软的下一代操作系统也渐渐浮出了水面。对于我们这些普通的PC用户而言,Windows 7相对于Windows Vista最显而易见的改变,无疑就是著名的Superbar任务栏了。说起它相比于原来的任务栏变化ÿ…

Linux 安装图形界面及远程连接
#可查询哪些组件是否已经安装(可用来对照组件名称) yum grouplistyum groupinstall X Window System -y #安装GNOME桌面环境 yum groupinstall GNOME Desktop Environment -y #安装KDE桌面环境 yum groupinstall KDE (K Desktop Environment)卸载 卸载GNOME桌面环境…
朴素、Select、Poll和Epoll网络编程模型实现和分析——Select模型
在《朴素、Select、Poll和Epoll网络编程模型实现和分析——朴素模型》中我们分析了朴素模型的一个缺陷——一次只能处理一个连接。本文介绍的Select模型则可以解决这个问题。(转载请指明出于breaksoftware的csdn博客) 和朴素模型一样,我们首先…
微软开源NAS算法Petridish,提高神经网络迁移能力
作者 | Jesus Rodriguez译者 | Rachel编辑 | 夕颜出品 | AI科技大本营(ID:rgznai100) 【导读】神经架构搜索(Neural architecture search, NAS)是深度学习中最热门的趋势之一。NAS方法针对特定问题和数据…

[转]g++ 编译多个相关文件
三个文件,一个头文件,两个程序文件 /*d.h */#include <iostream>usingnamespacestd; classDataset { public: intgetdata(); }; /*d.cpp */#include "d.h"intDataset::getdata() { return1231; } /*out.cpp */#include <ios…

POJ--2391--Ombrophobic Bovines【分割点+Floyd+Dinic优化+二分法答案】最大网络流量
联系:http://poj.org/problem?id2391 题意:有f个草场,每一个草场当前有一定数目的牛在吃草,下雨时它能够让一定数量的牛在这里避雨,f个草场间有m条路连接,每头牛通过一条路从一点到还有一点有一定的时间花…
25年了,我总结出这些信息提取的经验教训
作者 | Ehud Reiter译者 | 夕颜出品 | AI科技大本营(ID:rgznai100)【导读】近日,本文作者阿伯丁大学计算科学系教授 Ehud Reiter 及其带领的阅读小组读了一篇让他们印象深刻的论文——由 Ralph Grishman 发表的《信息提取 25 年》(…
朴素、Select、Poll和Epoll网络编程模型实现和分析——Poll模型
在《朴素、Select、Poll和Epoll网络编程模型实现和分析——Select模型》中,我们分析了它只能支持1024个连接同时处理的原因。但是在有些需要同时处理更多连接的情况下,1024个连接往往是不够的,也就是不能够高并发。那么这个时候我们就可以采用…

flashcom中远程共享对象SharedObject的用法
觉得这篇文章比较好,转载回来。学习fcs也有差不多一个月了,感觉最有特色的东西还是SharedObject.SharedObject有不少东西,本地操作就不说了(相信很多人没接触fcs也用过);就说说远程共享对象吧.基本的应用流程是:my_nc new NetConnection(); my_nc.connect("rt…

Hive-1.2.0学习笔记(一)安装配置
鲁春利的工作笔记,好记性不如烂笔头下载hive:http://hive.apache.org/index.htmlHive是基于Hadoop的一个数据仓库工具,提供了SQL查询功能,能够将SQL语句解析成MapReduce任务对存储在HDFS上的数据进行处理。MySQ安装Hive有三种运行…

邮件安全隐患及其防范技术研究
邮件安全隐患及其防范技术研究<?xml:namespace prefix o ns "urn:schemas-microsoft-com:office:office" />陈小兵【antian365.com】摘要电子邮件是Internet上使用最为频繁和广泛的服务,在给人们带来便利的同时,亦带来令人担忧的邮件…
必看!52篇深度强化学习收录论文汇总 | AAAI 2020
所有参与投票的 CSDN 用户都参加抽奖活动群内公布奖项,还有更多福利赠送来源 | 深度强化学习实验室(ID:Deep-RL)作者 | DeepRLAAAI 2020 共收到的有效论文投稿超过 8800 篇,其中 7737 篇论文进入评审环节,最终收录数量…
朴素、Select、Poll和Epoll网络编程模型实现和分析——Epoll模型
在阅读完《朴素、Select、Poll和Epoll网络编程模型实现和分析——Select模型》和《朴素、Select、Poll和Epoll网络编程模型实现和分析——Poll模型》两篇文章后,我们发现一个问题,不管select函数还是poll函数都不够智能,它们只能告诉我们成功…

Scala 深入浅出实战经典 第88讲:Scala中使用For表达式实现map、flatMap、filter
高级函数 map,flatMap,filter用for循环的实现。package com.dt.scala.forexpressionobject For_Advanced {def main(args: Array[String]) {}def map[A, B](list: List[A], f: A > B): List[B] for(element <- list) yield f(element)def flatMap[A, B](list: List[A], f…
抛弃Python,我们为什么用Go编写机器学习架构?
所有参与投票的 CSDN 用户都参加抽奖活动群内公布奖项,还有更多福利赠送作者 | Caleb Kaiser译者 | 弯月,编辑 | 郭芮来源 | CSDN(ID:CSDNnews)如今,众所周知Python是机器学习项目中最流行的语言。尽管R、C…