当前位置: 首页 > news >正文

leveldb源码解析六——compact

compact分为manual_compaction、minor_compaction、major_compaction,统一由MaybeScheduleCompaction触发:

void DBImpl::MaybeScheduleCompaction() {mutex_.AssertHeld();if (background_compaction_scheduled_) {// Already scheduled} else if (shutting_down_.load(std::memory_order_acquire)) {// DB is being deleted; no more background compactions} else if (!bg_error_.ok()) {// Already got an error; no more changes} else if (imm_ == nullptr && manual_compaction_ == nullptr &&!versions_->NeedsCompaction()) {// No work to be done} else {background_compaction_scheduled_ = true;env_->Schedule(&DBImpl::BGWork, this);}
}

MaybeScheduleCompaction的调用时机有以下几种:
1、用户调用Get接口时,在查询过程中,可能会更新sstable的seek信息,触发compaction
2、用户在遍历db时,会更新sstable之间key重复的信息,触发compaction
3、用户调用Put接口时,当memtable写满之后,转化为imemtable,触发minor_compaction
4、每次做完compaction之后,会产生新的sstable,更新sstable相关信息,可能会再次出发compaction

minor_compaction

当有immetable时,就会触发minor_compaction:

void DBImpl::BackgroundCompaction() {...if (imm_ != nullptr) {CompactMemTable();return;}...
}
void DBImpl::CompactMemTable() {mutex_.AssertHeld();assert(imm_ != nullptr);// Save the contents of the memtable as a new TableVersionEdit edit;Version* base = versions_->current();base->Ref();// immetable转化为sstableStatus s = WriteLevel0Table(imm_, &edit, base);base->Unref();if (s.ok() && shutting_down_.load(std::memory_order_acquire)) {s = Status::IOError("Deleting DB during memtable compaction");}// Replace immutable memtable with the generated Tableif (s.ok()) {// 启用新的WALedit.SetPrevLogNumber(0);edit.SetLogNumber(logfile_number_);  // Earlier logs no longer neededs = versions_->LogAndApply(&edit, &mutex_);}if (s.ok()) {// Commit to the new state// 清理多余的文件imm_->Unref();imm_ = nullptr;has_imm_.store(false, std::memory_order_release);RemoveObsoleteFiles();} else {RecordBackgroundError(s);}
}

WriteLevel0Table通过遍历immetable,构建新的sstable,然后为新生产的sstable选择放置的level:
1、如果与level0的sstable有重叠,则放置在level0
2、如果level+1层sstable与该sstable key有重叠,那么放置在level层
3、如果level+2层sstable与sstable key的重叠范围大于阈值,那么放置在level层
1、2项是正确性问题,3项是为了防止后续再level层做compaction时,需要merge的KV对太多

major_compaction

major_compaction根据current_->compaction_score_、current_->file_to_compact_ 判断是否要进行,这两个值在下面情形中更新:
1、用户调用Get接口时,如果需要查询sstable,在查询到最高层level时,会记录下level的sstable,并且更新信息,每1MB数据允许查询1次,如果改sstable被seek的次数过多,就需要compaction,每1MB数据seek 1次时因为作者认为seek一次sstable的时间代价与compaction 1MB的时间代价相同
2、用户遍历DB时,会判断key重复的次数,超过阈值时,就会记录到current_->file_to_compact_中
3、每次compaction结束之后,会重新计算每层sstable是否需要再一次compaction,这个值记录在current_->file_to_compact_。
在compaction之前,需要选择compaction的sstable以及对应的level:对于current_->compaction_score_策略,需要知道对应的sstable,从记录上上次该level上结束的key开始,如果上次没有,则从最小的key开始找sstable,对于level0,由于key不重叠,需要找到所有与sstable重叠的sstable。
初步找到要compaction的sstable之后,需要尽可能的在该level或者level+1上找到与该sstable有重叠的sstable,一起做compaction:

Compaction* VersionSet::PickCompaction() {Compaction* c;int level;// We prefer compactions triggered by too much data in a level over// the compactions triggered by seeks.const bool size_compaction = (current_->compaction_score_ >= 1);const bool seek_compaction = (current_->file_to_compact_ != nullptr);if (size_compaction) {level = current_->compaction_level_;assert(level >= 0);assert(level + 1 < config::kNumLevels);c = new Compaction(options_, level);// Pick the first file that comes after compact_pointer_[level]for (size_t i = 0; i < current_->files_[level].size(); i++) {FileMetaData* f = current_->files_[level][i];if (compact_pointer_[level].empty() ||icmp_.Compare(f->largest.Encode(), compact_pointer_[level]) > 0) {c->inputs_[0].push_back(f);break;}}if (c->inputs_[0].empty()) {// Wrap-around to the beginning of the key spacec->inputs_[0].push_back(current_->files_[level][0]);}} else if (seek_compaction) {level = current_->file_to_compact_level_;c = new Compaction(options_, level);c->inputs_[0].push_back(current_->file_to_compact_);} else {return nullptr;}c->input_version_ = current_;c->input_version_->Ref();// Files in level 0 may overlap each other, so pick up all overlapping onesif (level == 0) {InternalKey smallest, largest;GetRange(c->inputs_[0], &smallest, &largest);// Note that the next call will discard the file we placed in// c->inputs_[0] earlier and replace it with an overlapping set// which will include the picked file.current_->GetOverlappingInputs(0, &smallest, &largest, &c->inputs_[0]);assert(!c->inputs_[0].empty());}SetupOtherInputs(c);return c;
}

接下来就是做compaction,先选择compaction的最小seq,保证那些snapshot的key不会被compact掉,然后对于选中的sstable做归并排序,并且merge掉那些被delete的key、seq小的key:

//进行compact
Status DBImpl::DoCompactionWork(CompactionState* compact) {const uint64_t start_micros = env_->NowMicros();int64_t imm_micros = 0;  // Micros spent doing imm_ compactionsLog(options_.info_log,  "Compacting %d@%d + %d@%d files",compact->compaction->num_input_files(0),compact->compaction->level(),compact->compaction->num_input_files(1),compact->compaction->level() + 1);//进行一些前置条件保障assert(versions_->NumLevelFiles(compact->compaction->level()) > 0);assert(compact->builder == NULL);assert(compact->outfile == NULL);// 设置compact的最小版本号if (snapshots_.empty()) {compact->smallest_snapshot = versions_->LastSequence();} else {compact->smallest_snapshot = snapshots_.oldest()->number_;}// Release mutex while we're actually doing the compaction work//compact需要的信息都已经生成完毕,此时可以放开锁,从而主线程可以继续修改versionset和当前versionmutex_.Unlock();Iterator* input = versions_->MakeInputIterator(compact->compaction);input->SeekToFirst();Status status;ParsedInternalKey ikey;std::string current_user_key;bool has_current_user_key = false;SequenceNumber last_sequence_for_key = kMaxSequenceNumber;for (; input->Valid() && !shutting_down_.Acquire_Load(); ) {// Prioritize immutable compaction work// 每一次for循环中都优先进行imemtable的compactif (has_imm_.NoBarrier_Load() != NULL) {const uint64_t imm_start = env_->NowMicros();mutex_.Lock();if (imm_ != NULL) {CompactMemTable();bg_cv_.SignalAll();  // Wakeup MakeRoomForWrite() if necessary}mutex_.Unlock();imm_micros += (env_->NowMicros() - imm_start);}Slice key = input->key();if (compact->compaction->ShouldStopBefore(key) &&compact->builder != NULL) {status = FinishCompactionOutputFile(compact, input);if (!status.ok()) {break;}}// Handle key/value, add to state, etc.bool drop = false;if (!ParseInternalKey(key, &ikey)) {// Do not hide error keyscurrent_user_key.clear();has_current_user_key = false;last_sequence_for_key = kMaxSequenceNumber;} else {if (!has_current_user_key ||user_comparator()->Compare(ikey.user_key,Slice(current_user_key)) != 0) {// First occurrence of this user keycurrent_user_key.assign(ikey.user_key.data(), ikey.user_key.size());has_current_user_key = true;last_sequence_for_key = kMaxSequenceNumber;}if (last_sequence_for_key <= compact->smallest_snapshot) {// Hidden by an newer entry for same user key// 这种情况下,说明last_sequence_for_key != kMaxSequenceNumber,即遍历过相同的key// (不满足上面一个if的条件),相同的key,如果小于最新的snapshot,即没有snapshot持有它// 则可以删除了drop = true;    // (A)} else if (ikey.type == kTypeDeletion &&ikey.sequence <= compact->smallest_snapshot &&compact->compaction->IsBaseLevelForKey(ikey.user_key)) {// For this user key:// (1) there is no data in higher levels (更高的level有数据,代表可能有更旧的数据,这里再删除会出错)// (2) data in lower levels will have larger sequence numbers// (3) data in layers that are being compacted here and have//     smaller sequence numbers will be dropped in the next//     few iterations of this loop (by rule (A) above).// Therefore this deletion marker is obsolete and can be dropped.drop = true;}last_sequence_for_key = ikey.sequence;}
#if 0Log(options_.info_log,"  Compact: %s, seq %d, type: %d %d, drop: %d, is_base: %d, ""%d smallest_snapshot: %d",ikey.user_key.ToString().c_str(),(int)ikey.sequence, ikey.type, kTypeValue, drop,compact->compaction->IsBaseLevelForKey(ikey.user_key),(int)last_sequence_for_key, (int)compact->smallest_snapshot);
#endifif (!drop) {// Open output file if necessaryif (compact->builder == NULL) {status = OpenCompactionOutputFile(compact);if (!status.ok()) {break;}}if (compact->builder->NumEntries() == 0) {compact->current_output()->smallest.DecodeFrom(key);}compact->current_output()->largest.DecodeFrom(key);compact->builder->Add(key, input->value());// Close output file if it is big enoughif (compact->builder->FileSize() >=compact->compaction->MaxOutputFileSize()) {status = FinishCompactionOutputFile(compact, input);if (!status.ok()) {break;}}}input->Next();}if (status.ok() && shutting_down_.Acquire_Load()) {status = Status::IOError("Deleting DB during compaction");}if (status.ok() && compact->builder != NULL) {status = FinishCompactionOutputFile(compact, input);}if (status.ok()) {status = input->status();}delete input;input = NULL;// 统计本次compaction的读写量CompactionStats stats;stats.micros = env_->NowMicros() - start_micros - imm_micros;for (int which = 0; which < 2; which++) {for (int i = 0; i < compact->compaction->num_input_files(which); i++) {stats.bytes_read += compact->compaction->input(which, i)->file_size;}}for (size_t i = 0; i < compact->outputs.size(); i++) {stats.bytes_written += compact->outputs[i].file_size;}mutex_.Lock();stats_[compact->compaction->level() + 1].Add(stats);if (status.ok()) {status = InstallCompactionResults(compact);}if (!status.ok()) {RecordBackgroundError(status);}VersionSet::LevelSummaryStorage tmp;Log(options_.info_log,"compacted to: %s", versions_->LevelSummary(&tmp));return status;
}

manual_compaction

manul_compaction通过:

  // Compact the underlying storage for the key range [*begin,*end].// In particular, deleted and overwritten versions are discarded,// and the data is rearranged to reduce the cost of operations// needed to access the data.  This operation should typically only// be invoked by users who understand the underlying implementation.//// begin==nullptr is treated as a key before all keys in the database.// end==nullptr is treated as a key after all keys in the database.// Therefore the following call will compact the entire database://    db->CompactRange(nullptr, nullptr);virtual void CompactRange(const Slice* begin, const Slice* end) = 0;

接口来指定compaction的key范围,从这个范围中回去对应key的sstable,然后做compaction

相关文章:

leveldb源码解析六——compact

compact分为manual_compaction、minor_compaction、major_compaction&#xff0c;统一由MaybeScheduleCompaction触发&#xff1a; void DBImpl::MaybeScheduleCompaction() {mutex_.AssertHeld();if (background_compaction_scheduled_) {// Already scheduled} else if (shu…...

数据结构(二):单向链表、双向链表

数据结构&#xff08;二&#xff09;一、什么是链表1.数组的缺点2.链表的优点3.链表的缺点4.链表和数组的区别二、封装单向链表1. append方法&#xff1a;向尾部插入节点2. toString方法&#xff1a;链表元素转字符串3. insert方法&#xff1a;在任意位置插入数据4.get获取某个…...

COCO物体检测评测方法简介

本文从ap计算到map计算&#xff0c;最后到coco[0.5:0.95:0.05] map的计算&#xff0c;一步一步拆解物体检测指标map的计算方式。 一、ap计算方法 一个数据集有多个类别&#xff0c;对于该数据库有5个gt&#xff0c;算法检测出来10个bbox&#xff0c;对于人这个类别来说检测有…...

记一次上环境获取资源失败的案例

代码结构以及资源位置 测试代码 RestController RequestMapping("/json") public class JsonController {GetMapping("/user/1")public String queryUserInfo() throws Exception {// 如果使用全路径, 必须使用/开头String path JsonController.class.ge…...

实战超详细MySQL8离线安装

在RedHat中&#xff0c;RPM Bundle 方式安装MySQL8。建议一定要用 RPM Bndle 版本安装&#xff0c;包全。官网下载&#xff1a;https://dev.mysql.com/downloads/mysql/1.卸载mariadb&#xff0c;会与MySQL安装冲突。rpm -qa | grep mariadb 查看有无mariadb如果有&#xff0…...

依赖倒置原则|SOLID as a rock

文章目录 意图动机:违反依赖倒置原则解决方案:C++中依赖倒置原则的例子依赖倒置原则的优点1、可复用性2、可维护性在C++中用好DIP的标准总结本文是关于 SOLID as Rock 设计原则系列的五部分中的 最后一部分。 SOLID 设计原则侧重于开发 易于维护、可重用和可扩展的软件。 在…...

Webpack的知识要点

在前端开发中&#xff0c;一般情况下都使用 npm 和 webpack。   npm是一个非常流行的包管理工具&#xff0c;帮助开发者管理项目中使用的依赖库和工具。它可以方便地为项目安装第三方库&#xff0c;并在项目开发过程中进行版本控制。   webpack是一个模块打包工具&#xff…...

handler解析(2) -Handler源码解析

目录 基础了解&#xff1a; 相关概念解释 整体流程图&#xff1a; 源码解析 Looper 总结&#xff1a; sendMessage 总结&#xff1a; ThreadLocal 基础了解&#xff1a; Handler是一套 Android 消息传递机制,主要用于线程间通信。实际上handler其实就是主线程在起了一…...

【算法】kmp

KMP算法 名称由来 是由发明这个算法的三个科学家的名称首字母组成 作用 用于字符串的匹配问题 举例说明 字符串 aabaabaaf 模式串 aabaaf 传统匹配方法 第一步 aabaabaaf aabaaf 此时&#xff0c;b和f不一致&#xff0c;则把模式串从头和文本串的第二个字符开始比 第…...

git 常用命令之 git checkout

大家好&#xff0c;我是 17。 git checkout 是 git 中最重要最常用的命令之一&#xff0c;本文为大家详细解说一下。 恢复工作区 checkout 的用途之一是恢复工作区。 git checkout . checkout . 表示恢复工作区的所有更改,未跟踪的文件不会有变化。 恢复工作区的所有文件风…...

一些常见错误

500状态码: 代表服务器业务代码出错, 也就是执行controller里面的某个方法的过程中报错, 此时在IDEA的控制台中会显示具体的错误信息, 所以需要去看IDEA控制台的报错404状态码: 找不到资源找不到静态资源 检查请求地址是否拼写错误 检查静态资源的位置是否正确 如果以上都没有问…...

[单片机框架][调试功能] 回溯案发现场

程序莫名死机跑飞&#xff0c;不知道问题&#xff0c;那么下面教你回溯错误源 回溯案发现场一、修改HardFault_Handler1. xx.s 在启动文件&#xff0c;找到HardFault_Handler。并修改。2. 定义HardFault_Handler_C函数。&#xff08;主要是打印信息并存储Flash&#xff09;3. 根…...

MySQL主从同步-(二)搭建从机服务器

在docker中创建并启动MySQL从服务器&#xff1a;**端口3307docker run -d \-p 3307:3306 \-v /atguigu/mysql/slave1/conf:/etc/mysql/conf.d \-v /atguigu/mysql/slave1/data:/var/lib/mysql \-e MYSQL_ROOT_PASSWORD123456 \--name atguigu-mysql-slave1 \mysql:8.0.3创建MyS…...

Linux系列 备份与分享文档

作者简介&#xff1a;一名在校云计算网络运维学生、每天分享网络运维的学习经验、和学习笔记。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 目录 前言 一.备份与分享文档 1.使用压缩和解压缩工具 &#xff08;1&…...

SNI生效条件 - 补充nginx-host绕过实例复现中SNI绕过的先决条件

文章目录1.前置环境搭建2.测试SNI生效条件(时间)3. 证书对SNI的影响3.1 双方使用同一个证书&#xff1a;3.2 双方使用不同的证书与私钥4. 端口号区分测试4.1 端口号区分&#xff0c;证书区分&#xff1a;4.2 端口号区分,证书不区分&#xff1a;5.总结SNI运行机制6. SNI机制绕过…...

傻白探索Chiplet,Modular Routing Design for Chiplet-based Systems(十一)

阅读了Modular Routing Design for Chiplet-based Systems这篇论文&#xff0c;是关于多chiplet通信的&#xff0c;个人感觉核心贡献在于实现了 deadlock-freedom in multi-chiplet system&#xff0c;而不仅仅是考虑单个intra-chiplet的局部NoC可以通信&#xff0c;具体的一些…...

C语言静态库、动态库的封装和注意事项

1、动态库、静态库介绍 参考博客&#xff1a;《静态库和动态库介绍以及Makefile》&#xff1b; 2、代码目录结构和编译脚本 参考博客&#xff1a;《实际工作开发中C语言工程的目录结构分析》&#xff1b; 3、编写库的流程 (1)明确需求:需求是否合理、需求的使用场景、需求可能遇…...

MyBatis-Plus分页插件和MyBatisX插件

MyBatis-Plus分页插件和MyBatisX插件六、插件1、分页插件a>添加配置类b>测试八、代码生成器1、引入依赖2、快速生成十、MyBatisX插件1、新建spring boot工程a>引入依赖b>配置application.ymlc>连接MySQL数据库d>MybatisX逆向生成2、MyBatisX快速生成CRUD申明…...

年前无情被裁,面试大厂的这几个月…

2月份了&#xff0c;金三银四也即将来临&#xff0c;在这个招聘季&#xff0c;大厂也开始招人&#xff0c;但还是有很多人吐槽说投了很多简历&#xff0c;却迟迟没有回复… 另一面企业招人真的变得容易了吗&#xff1f;有企业HR吐槽&#xff0c;简历确实比以前多了好几倍&…...

基于Java的分片上传功能

起因&#xff1a;最近在工作中接到了一个大文件上传下载的需求&#xff0c;要求将文件上传到share盘中&#xff0c;下载的时候根据前端传的不同条件对单个或多个文件进行打包并设置目录下载。 一开始我想着就还是用老办法直接file.transferTo(newFile)就算是大文件&#xff0c…...

KDS安装步骤

KDS kinetis design studio 软件 第一步官网(https://www.nxp.com/ 注册账号下载set成功下载软件。 随着AI&#xff0c;大数据这些技术的快速发展&#xff0c;与此有关的知识也普及开来。如何在众多网站中寻找最有价值的信息&#xff0c;如何在最短的时间内获得最新的技…...

JavaSE-线程池(1)- 线程池概念

JavaSE-线程池&#xff08;1&#xff09;- 线程池概念 前提 使用多线程可以并发处理任务&#xff0c;提高程序执行效率。但同时创建和销毁线程会消耗操作系统资源&#xff0c;虽然java 使用线程的方式有多种&#xff0c;但是在实际使用过程中并不建议使用 new Thread 的方式手…...

开源代码的寿命为何只有1年?

说实话&#xff0c;如果古希腊的西西弗斯是一个在2016年编写开源代码的开发者&#xff0c;那他会有宾至如归的感觉。著名的西西弗斯处罚&#xff0c;是神话流传下来的&#xff0c;他被迫推一块巨大的石头上山&#xff0c;当登顶之后&#xff0c;只能眼睁睁看着它滚下去&#xf…...

完善登录功能--过滤器的使用

系列文章目录 Spring Boot读取配置文件内容的三种方式 Spring Boot自动配置–如何切换内置Web服务器 SpringBoot项目部署 上述为该系列部分文章&#xff0c;想了解更多可看我博客主页哦&#xff01; 文章目录系列文章目录前言一、创建自定义过滤器LoginCheckFilter二、在启动类…...

CSS基础:属性和关系选择器

字体属性 color 文本颜色 div{ color:red;} div{ color:#ff0000;} div{ color:rgb(255,0,0);} div{ color:rgba(255,0,0,.5);}font-size 文本大小 h1 {font-size:40px;} h2 {font-size:30px;} p {font-size:14px;}注意&#xff1a;chrome浏览器接受最小字体是12px font-we…...

设计模式:原型模式解决对象创建成本大问题

一、问题场景 现在有一只猫tom&#xff0c;姓名为: tom, 年龄为&#xff1a;1&#xff0c;颜色为&#xff1a;白色&#xff0c;请编写程序创建和tom猫属性完全相同的10只猫。 二、传统解决方案 public class Cat {private String name;private int age;private String color;…...

驱动开发(二)

一、驱动流程 驱动需要以下几个步骤才能完成对硬件的访问和操作&#xff1a; 模块加载函数 module_init注册主次设备号 <应用程序通过设备号找到设备>驱动设备文件 <应用程序访问驱动的方式> 1、手动创建 &#xff08;mknod&#xff09;2、程序自动创建file_oper…...

《狂飙》大结局,这22句经典台词值得细品

最近爆火的热播剧《狂飙》大家都看了吗&#xff1f; 剧情紧凑、演技炸裂、豆瓣评分9.0&#xff0c;可以说是开年评分最高的一部国产剧。 ​ 虽然大结局了。 里面有很多经典台词&#xff0c;值得每个人细细品味。 01 这世界不缺梦想 有本事你就去实现它 02 你这么善良 怎么跟坏…...

【计算机网络期末复习】第二章 物理层

✍个人博客&#xff1a;https://blog.csdn.net/Newin2020?spm1011.2415.3001.5343 &#x1f4e3;专栏定位&#xff1a;为想复习学校计算机网络课程的同学提供重点大纲&#xff0c;帮助大家渡过期末考~ &#x1f4da;专栏地址&#xff1a; ❤️如果有收获的话&#xff0c;欢迎点…...

多核异构核间通信-mailbox/RPMsg 介绍及实验

1. 多核异构核间通信 由于MP157是一款多核异构的芯片&#xff0c;其中既包含的高性能的A7核及实时性强的M4内核&#xff0c;那么这两种处理器在工作时&#xff0c;怎么互相协调配合呢&#xff1f; 这就涉及到了核间通信的概念了。 IPCC (inter-processor communication contr…...

wordpress支持视频播放器插件下载/威海seo

最近做了一个word文档导入的功能&#xff0c;但是因为项目紧急&#xff0c;所以做的很粗糙。好不容易周末了&#xff0c;就自己撸了一会代码&#xff0c;想把他做成一个通用的工具&#xff0c;以备以后用到时直接黏贴。概述POI 的起源POI是apache的一个开源项目&#xff0c;他的…...

wap网站建设案例/百度推广点击一次多少钱

本文介绍如何获得微信公众平台关注用户的基本信息&#xff0c;包括昵称、头像、性别、国家、省份、城市、语言。本文的方法将囊括订阅号和服务号以及自定义菜单各种场景&#xff0c;无论是否有高级接口权限&#xff0c;都有办法来获得用户基本信息&#xff0c;而无需模拟登录。…...

石家庄小学网站建设/常州百度推广公司

看完 w3cschool《css 教程》中的《css 图像透明/不透明》&#xff0c;你对 CSS 中的半透明颜色可能已经有了基础的了解&#xff0c;CSS 透明算得上是一种相当流行的技术&#xff0c;但在跨浏览器支持上&#xff0c;对于开发者来说&#xff0c;可以说是一件令人头疼的事情。目前…...

web手机版网站开发框架/视频营销的策略与方法

前段时间学习 4G LTE 方面的内容&#xff0c;自己整理了一些笔记&#xff0c;笔记为手写英文版&#xff0c;比较详细 笔记/课程包括一些 4G LTE 中的关键技术&#xff0c;不是很深入&#xff0c;比较容易理解&#xff0c;很适合新手入门。看完课程会对4G LTE有一个整体的认识&a…...

nodejs 做视频网站/数字营销课程

亲测可用&#xff0c;若有疑问请私信 在执行函数里加一些处理 &#xff0c;比如加一个计数器之类的&#xff0c;通过这个变量的变化了能了解它是否还在执行。比如&#xff1a; (function(ifun) {ifun.exeCount 0setInterval(function() {ifun()ifun.exeCount }, 1000…...

类似红盟的网站怎么做/今日油价最新

回调模式&#xff1a; 在计算机程序设计中&#xff0c;回调函数&#xff0c;或简称回调&#xff0c;是指通过函数参数传递到其它代码的&#xff0c;某一块可执行代码的引用。这一设计允许了底层代码调用在高层定义的子程序。 1.定义回调函数接口 2.编写调用逻辑 3.传递回调函数…...