2024年2月

好久不见,我又回来了。给大家分享一个最近c#代码操作ftp服务器的代码示例


1  public abstract classFtpOperation2 {3         /// <summary>
4         ///FTP服务器地址5         /// </summary>
6         private stringftpServer;7 
8         /// <summary>
9         ///用户名10         /// </summary>
11         private stringuserName;12 
13         /// <summary>
14         ///密码15         /// </summary>
16         private stringpassWord;17 
18         /// <summary>
19         ///FTPHelper类的构造函数20         /// </summary>
21         /// <param name="ftpServer">FTP服务器地址</param>
22         /// <param name="userName">用户名</param>
23         /// <param name="passWord">密码</param>
24         public FtpOperation(string ftpServer, string userName, stringpassWord)25 {26             this.ftpServer =ftpServer;27             this.userName =userName;28             this.passWord =passWord;29 }30 
31         /// <summary>
32         ///执行FTP操作的方法33         /// </summary>
34         /// <param name="action">要执行的操作</param>
35         private voidExecuteFtpOperation(Action action)36 {37             try
38 {39 action.Invoke();40 }41             catch(WebException ex)42 {43                 if (ex.Status ==WebExceptionStatus.Timeout)44 {45                     Console.WriteLine("连接超时。");46 }47                 else
48 {49                     Console.WriteLine("发生错误 WebException: {0}", ex.Message);50 }51 }52             catch(Exception ex)53 {54                 Console.WriteLine("发生错误: {0}", ex.Message);55 }56 }57 }58 }

基础类的构造函数和属性


FtpOperation 中其他的方法

调用示例

            //FTP 服务器地址
            string ftpServer = "ftp://127.0.0.1:27/";//FTP 服务器用户名
            string userName = "Administrator";//FTP 服务器密码
            string password = "admin";

FtpTest ftp
= newFtpTest(ftpServer, userName, password);//ftp.QueryAll("/Template"); //查询 ftp.FtpDeleteFolders("");//删除所有
ftp.FtpUploadFolder(
"e:\\CoaTemplate", "");//将文件夹的内容上传到根目录
ftp.FtpUploadFolder(
@"D:\GitCode\Blog.Core", "/gitCode/Blog.Core");//将本地文件夹的内容上传到指定目录var data = ftp.RecursiveQueryAll("");//查询所有文件信息
ftp.FtpMoveFolder(
"/CoaTemplate", "/1/CoaTemplate");//文件夹移动
ftp.FtpDownloadFolder(
"/1", "d:\\1\\"); //将ftp服务器的指定文件夹下载到本地目录

贴了半天代码,都不太行,一会能展开,一会展不开,源码地址放下面了。

项目地址:https://github.com/yycb1994/FtpSiteManager

缘起一:


公司现有数据仓库,是通过kettle从mysql抽取到目标库,运行多年,主要有以下问题,

1,效率低:kettle抽取行数少

2,容错性差:一个表抽取出错就导致后续计算会出问题,

3,扩展性差: 对多库多表等支持不好

近300张表抽取,再加上计算,每天都算到7点,还有2个巨大的计算要等到10点左右才能算完。

上一任数仓开发者,使用datax来替换kettle,将数据通过datax抽取到新的greeplum中,效率很高,而且稳定,但每个表一个shell文件,300个表要300个shell文件,每次修改都要登陆到linux修改,太麻烦了。但迁移了部分表又没有全部迁移。

自从2022年11月接下这套数据仓库系统,就想怎么优化这数仓系统。

缘起二:


2023年10月开始,公司接了几个关于数据指标系统计算的大单。经济形势不好,经济上开始开源节流,我们这些维护人员转做项目,当项目确定后,和另一个开发人员一起做个数据仓库系统。这个数据仓库的ETL抽取层任务交给我,因为以前设计和实现过下面2个系统:

1、
SSIS数据同步系统

2,
用ELK分析每天4亿多条腾讯云MySQL审计日志(1)--解决过程

借鉴2个系统的思路,就想,是否可以通过程序调用datax,做个闭环的基于datax的系统,不用再写很多shell文件,将其元数据都配置化,提高开发效率。

方法:

市面上是有datax_web这个可视化配置平台,自己测试,和自己想要的不一样,思考了一下,要么用linux的shell或者python3来写这个datax的配置调度,将其需要的数据都配置化:

1,测试了linux的shell,在读取mysql表数据字段数据时,进行多字段数据读取,字段数据读出后,不能很好分割出,有空格的会作为一个新字段。
不可用

2,因datax也是python开发的,支持python2,想用python3程序开发,实现配置化,通过测试,发现是可以直接在python3程序调用datax,而且很方便。
可用

目标:   简化datax的开发,其配置等数据都数据库表配置化

经过1个多月的开发和测试,达到了上面的2个要求,同时可以记录执行过程等相关信息(如抽取时间,抽取速度,抽取行数等),执行错误记录相关错误信息,不用再登陆到linux去看日志文件信息。

这个小系统,被命名为pydatax抽取系统,和以前用kettle和单个文件datax相比,有很大优势:
简单,实用和高效

pydatax新系统带来巨大便利:

    1. 抽取表等相关信息数据全部可配置化
    2. 抽取出错信息直接表中查看
    3. 新加和修改直接修改表数据即可完成,极大提高效率
    4. 抽取历史数据和错误数据可单独执行
    5. 有抽取的历史记录日志等信息

说明

相信很多人对于java中父子继承关系中,子类实例化调用过程中,代码块的执行顺序都容易忘记或搞混,尤其是java初级笔试题或面试题最容易出这类题目,让人恨得牙痒痒!!!

本文就一次性将其连根铲除,看完你就不会有这个烦恼了,哈哈。

  • 先引用一下骨灰级大作《Java编程思想》的复用章节

Java 中万物皆对象,所以加载活动就容易得多。记住每个类的编译代码都存在于它自己独立的文件中。该文件只有在使用程序代码时才会被加载。一般可以说“类的代码在首次使用时加载“。这通常是指创建类的第一个对象,或者是访问了类的 static 属性或方法。构造器也是一个 static 方法尽管它的 static 关键字是隐式的。因此,准确地说,一个类当它任意一个 static 成员被访问时,就会被加载。

首次使用时就是 static 初始化发生时。所有的 static 对象和 static 代码块在加载时按照文本的顺序(在类中定义的顺序)依次初始化。static 变量只被初始化一次。

那么总的来说:
代码块分三种:static静态代码块,匿名代码块(没有名字且没有被static修饰的代码块叫做实例代码块,又称匿名代码块),普通代码块

代码块执行顺序:静态代码块 -> 匿名代码块 -> 构造函数 -> 普通代码块

继承中代码块执行顺序:父类静态块 -->子类静态块 -> 父类匿名代码块 -> 父类构造器 -> 子类匿名代码块 -> 子类构造器

举例

class Parent {
    {
        System.out.println("执行父类第1个匿名代码块");
    }

    static {
        System.out.println("父类静态代码块");
    }

    {
        System.out.println("执行父类第2个匿名代码块");
    }

    public Parent() {
        System.out.println("父类构造函数");
    }

    public static void parentStaticMethod() {
        System.out.println("父类静态方法");
    }
}

class Child extends Parent {
    static {
        System.out.println("子类静态代码块");
    }

    {
        System.out.println("执行子类第1个匿名代码块");
    }

    public Child() {
        System.out.println("子类构造函数");
    }

    public static void childStaticMethod() {
        System.out.println("子类静态方法");
    }
}

public class Main {
    public static void main(String[] args) {
        Child child = new Child();
        child.childStaticMethod();
    }
}

输出:
父类静态代码块
子类静态代码块
执行父类第1个匿名代码块
执行父类第2个匿名代码块
父类构造函数
执行子类第1个匿名代码块
子类构造函数
子类静态方法

解释

在Java中,父子类继承时,静态成员(包括静态方法和静态变量)的加载顺序是由类加载器按照代码的顺序进行的。当一个类被加载时,它的静态成员也会被加载。加载顺序是从上到下,从父类到子类。

前言:

这是最好的时代,也是最坏的时代;是充满挑战的时代,也是充满机遇的时代。是科技飞速的时代,也是无限可能的时代。

近年来,人工智能(AI)技术的飞速发展已经席卷了全球,不断突破着技术边界,为各行各业带来前所未有的变革。作为引领未来的核心技术之一,AI正在深刻地改变着我们的生活方式和工作模式。前几天OpenAI发布了邻人叹止Sora大模型,专门用于生成视频。这一模型在人工智能领域引起了广泛的关注和讨论,因为它在视频生成技术上实现了重大的突破。实现了对现实世界的理解和模拟两层能力。这意味着Sora不仅能理解你的文字描述,还能将其转化为真实、生动的视频画面。这将对很多行业形成冲击,也在不断地推动着行业的进步。让我们一起走进这个充满创新和变革的时代,共同迎接挑战,寻求机遇。

我们还是闲话少叙,切入正题。

序:

档案,它们是历史的碎片,是知识的宝藏,也是我们回望过去的窗口。然而,管理这些浩如烟海的资料,却常常让人头疼不已。在信息技术飞速发展的今天,传统的档案管理方式正面临着前所未有的挑战与机遇。好在,科技的魔法棒为我们指明了方向——机器人和三维可视化技术的融合应用,档案管理正逐步迈向智能化、自动化的新时代,正携手为我们打开档案管理的新篇章。

“工欲善其事,必先利其器。”古人的智慧,深刻揭示了技术创新对工作效率提升的重要性。如今,机器人已经能够胜任许多繁琐、重复性的工作,而三维可视化技术则为我们提供了一个全新的视角,让我们能够更直观、更全面地了解档案室的每一个角落。通过构建档案室的三维模型,我们可以更直观地理解各个环节的空间布局和操作逻辑,为实际操作提供有力的参考。此外,三维可视化还能帮助我们模拟和预测不同场景下的运行效果,从而指导我们做出更合理的决策。

下面我们探讨并如何利用三维可视化技术,模拟和优化档案室中机器人取档、盘点、人工查档以及巡检等关键环节的运作流程。这些环节共同构成了档案室管理的完整流程,每个环节都有其独特的作用和价值,共同确保档案室的高效、准确和安全运行。

一、机器人取档

1.1、效果展示

  1. 机器人定位前往档案架

2、机器人取档案

3、机器人交付档案

1.2、解决方案

3D密集架库房机器人取档是一种利用机器人技术实现档案拿取功能的无人值守库房管理方式。这种库房管理方式采用了数字孪生技术、三维建模等技术,构建了技防与人防相结合、软硬件同步发展的档案实体安全管理体系。在3D密集架库房中,机器人通过智能系统控制,可以精确地定位到需要拿取的档案位置,并通过智能机械臂等设备将档案取出。同时,库房还配备了实时定位感应系统、导轨式跟踪摄像系统、智能温湿度控制系统等先进配套设备,确保档案的精密存储和展呈。通过机器人实现档案拿取功能,可以大大提高库房管理的效率和准确性,减少人为因素造成的误差和损失。同时,无人值守库房也可以有效减少人力成本和安全风险,提高库房的安全性和可靠性。

  • 构建档案室的三维模型,包括密集架、通道、机器人等。
  • 为机器人设计三维模型,并添加导航系统、机械臂和抓取工具。
  • 编写机器人的取档逻辑,包括路径规划、物体识别和抓取动作。
  • 在虚拟环境中模拟机器人的取档过程,通过可视化界面展示机器人的运动轨迹和取档结果。
  1. 定义:机器人取档是指利用机器人技术从档案室中自动取出特定档案的过程。
  2. 过程:机器人根据输入的档案编号或关键词,通过导航系统移动到相应的密集架前,使用机械臂或其他工具从指定位置取出档案。
  3. 优势:提高取档效率,减少人力成本,降低人为错误,适用于大规模和高密度的档案存储。

1.3、代码实现

由于代码篇幅过长,此处给出实现逻辑的伪代码结构

1、统一机器人动作方法

我们把机器人的每一步动作都当作一次执行动画,根据不同动作名称,来实现不懂动作

functiondoRouteRunStep(index, data) {
/*
  处理资源回收  
  doGC();
  */
var currentroute =data[index];if(currentroute) {var cameraPosition =currentroute.position;var cameraTarget =currentroute.target;var runTime =currentroute.timeLong;var additionsTimeLong = 0;if(currentroute.additions) {
additionsTimeLong
=currentroute.additionsTimeLong;
}
if (additionsTimeLong == 0) {if (runTime != 0) {
modelbusiness.changeCameraPositionObj
= msj3DObj.commonFunc.changeCameraPosition(cameraPosition, cameraTarget, runTime, function() {if (modelbusiness.runState == 0) {

}
});
}
if(currentroute.additions) {
additionsTimeLong
=runTime;
$.each(currentroute.additions,
function(_index, _obj) {if(_obj) {if (_obj.changeParam == "function") {
modelbusiness[_obj.
function](_obj.addtion_name, _obj.paramvalue)
}
else{var cobj =msj3DObj.commonFunc.findObject(_obj.addtion_name);if (_obj.changeParam == "position"){if (_obj.addtion_name == "people" || _obj.addtion_name == "jiqiren") {
cobj.lookAt(
newTHREE.Vector3(
_obj[_obj.changeParam].x,
cobj.position.y,
_obj[_obj.changeParam].z));
}
if (_obj.addtion_name == "people") {
cobj.mixer.clipAction(cobj.oldGLTFObj.animations[
1]).play()
cobj.mixer.clipAction(cobj.oldGLTFObj.animations[
0]).stop();
console.log(
"run");
}
}
var _tween = new TWEEN.Tween(cobj[_obj.changeParam]).to(_obj[_obj.changeParam], additionsTimeLong).onUpdate(function() {if (modelbusiness.runState == 0) {
_tween.stop();
}
}).onComplete(
function() {if (_obj.addtion_name == "people" && _obj.changeParam == "position") {
cobj.mixer.clipAction(cobj.oldGLTFObj.animations[
1]).stop();
cobj.mixer.clipAction(cobj.oldGLTFObj.animations[
0]).play();
console.log(
"stop");
}
}).start();
modelbusiness.currentTween
=_tween;
}
}
});

}
modelbusiness.currentSetTimeOut
= setTimeout(function() {if (data[index + 1]) {
modelbusiness.doRouteRunStep(index
+ 1, data);
}
}, runTime
+100)
}
}else{if (data[index + 1]) {
modelbusiness.doRouteRunStep(index
+ 1, data);
}
}
}

2、统一调用,配置动画步骤

functionjiqirenDD 
(area, nub, Face, pandianRow, pandianCol) { $("#pandianDataDetail").html("");var cabforename = "cabinet2_";var positionZ = [ -350];if (area == 1) { cabforename= "cabinet_"; positionZ= [ 360]; }var rundata =[];for (var i = nub; i <= nub; i++) {var cabobj = msj3DObj.commonFunc.findObject(cabforename +i);var step1 ={"timeLong": (i == nub ? nub * 700 : 1200), "position": (i == nub ?{"x": cabobj.position.x + 180, "y": 8, "z": 23.203} : {"x": cabobj.position.x + 15, "y": 212, "z": (area == 1 ? -193 : 197) }), "target": (i == nub ? { "x": cabobj.position.x + 15, "y": 10, z: 0.876428765845767 } : { "x": cabobj.position.x + 15, "y": 89.3259331925796, z: 0.876428765845767 }), "additionsTimeLong": 0,"additions": [ {"changeParam": "position", "position": { x: cabobj.position.x+ 15, z:23.203, },"addtion_name": "jiqiren"} ] };var step2 ={"timeLong": 300, "position": { "x": cabobj.position.x + 15, "y": 212, "z": (area == 1 ? -193 : 197) }, "target": { "x": cabobj.position.x + 15, "y": 89.3259331925796, z: 0.876428765845767} };//转动面向架子 var step3 ={"timeLong": 300, "position": { "x": cabobj.position.x + 15, "y": 212, "z": (area == 1 ? -193 : 197) }, "target": { "x": cabobj.position.x + 15, "y": 89.3259331925796, z: 0.876428765845767 }, "additionsTimeLong": 100,"additions": [ {"changeParam": "position", "position": { x: cabobj.position.x+ 15, z:23.203 + (area == 1 ? 1 : -1), },"addtion_name": "jiqiren"} ] };//开架 var step3_1 ={"timeLong": 0, "position": { "x": cabobj.position.x + 15, "y": 212, "z": (area == 1 ? -193 : 197) }, "target": { "x": cabobj.position.x + 15, "y": 89.3259331925796, z: 0.876428765845767 }, "additionsTimeLong": 0, "additionsTimeLong": 1000,"additions": [ {"changeParam": "function", "function": "openCabnitByName", "paramvalue": "打开架子", "addtion_name": cabforename +i } ] }; rundata=rundata.concat([step1, step2, step3, step3_1]);for (var pj = 0; pj < positionZ.length; pj++) {//进入 位置一 var step4 ={"timeLong": 0, "position": { "x": cabobj.position.x + 15, "y": 212, "z": (area == 1 ? -193 : 197) }, "target": { "x": cabobj.position.x + 15, "y": 89.3259331925796, z: 0.876428765845767 }, "additionsTimeLong": 0, "additionsTimeLong": 3000,"additions": [ {"changeParam": "position", "position": { x: cabobj.position.x+ 15, z: positionZ[pj], },"addtion_name": "jiqiren"} ] };//转动面向架子 var step5 ={"timeLong": 0, "position": { "x": cabobj.position.x + 15, "y": 212, "z": (area == 1 ? -193 : 197) }, "target": { "x": cabobj.position.x + 15, "y": 89.3259331925796, z: 0.876428765845767 }, "additionsTimeLong": 0, "additionsTimeLong": 100,"additions": [ {"changeParam": "position", "position": { x: cabobj.position.x+ (area == 1 ? 16 : 14), z: positionZ[pj], },"addtion_name": "jiqiren"} ] };//停留 var step6 ={"timeLong": 0, "position": { "x": cabobj.position.x + 15, "y": 212, "z": (area == 1 ? -193 : 197) }, "target": { "x": cabobj.position.x + 15, "y": 89.3259331925796, z: 0.876428765845767 }, "additionsTimeLong": 0, "additionsTimeLong": 3000,"additions": [ {"changeParam": "function", "function": "jiqirenQuShu", "paramvalue": "取档案", "addtion_name": "jiqiren"} ] }; rundata=rundata.concat([step4, step5, step6]); }//转动角度 var step7 ={"timeLong": 300, "position": { "x": cabobj.position.x + 15, "y": 212, "z": (area == 1 ? -193 : 197) }, "target": { "x": cabobj.position.x + 15, "y": 89.3259331925796, z: 0.876428765845767 }, "additionsTimeLong": 0, "additionsTimeLong": 0};//回退 var step8 ={"timeLong": 0, "position": { "x": cabobj.position.x + 15, "y": 212, "z": (area == 1 ? -193 : 197) }, "target": { "x": cabobj.position.x + 15, "y": 89.3259331925796, z: 0.876428765845767 }, "additionsTimeLong": 5000,"additions": [ {"changeParam": "position", "position": { x: cabobj.position.x+ 15, z:23.203, },"addtion_name": "jiqiren"} ] }; rundata=rundata.concat([step7, step8]); }this.pandianDD = this.pandianDD.concat(rundata);//回去档案给人 this.pandianDD.push({"timeLong": 8000, "position": { x: 1795.3290848770343, y: 216.90756778187463, z: 148.3450456666665639 }, "target": { x: 1912.5600353289337, y: -11.238253288388648, z: 166.20100949665147 }, "additionsTimeLong": 0,"additions": [ {"changeParam": "position", "position": { "x": 1900, "y": -93, "z": 150 }, "addtion_name": "jiqiren"} ] });this.pandianDD.push({"timeLong": 0, "position": { x: 1795.3290848770343, y: 216.90756778187463, z: 148.3450456666665639 }, "target": { x: 1912.5600353289337, y: -11.238253288388648, z: 166.20100949665147 }, "additionsTimeLong": 4000,"additions": [ {"changeParam": "function", "function": "jiqirenGeiShu", "paramvalue": "给档案", "addtion_name": "jiqiren"} ] });this.pandianDD.push({"timeLong": 0, "position": { "x": 1496.961677226794, "y": 6.048526864006573, "z": -396.50227110708374 }, "target": { "x": 1482.9322600214946, "y": -4.512061245433281, "z": -165.11374369473938 }, "additionsTimeLong": 1000,"additions": [ {"changeParam": "function", "function": "stopDD", "paramvalue": "停止调档", "addtion_name": ""} ] });this.doRouteRun(0, this.pandianDD); }

二、机器人盘点

2.1、效果展示

机器人盘点扫描

2.2、解决方案

密集架库房机器人盘点解决方案是一种利用机器人技术实现对密集架库房中档案进行高效、准确盘点的方案。该方案结合了导航定位、图像识别、数据分析等多种技术,旨在解决传统盘点方式中存在的效率低下、误差率高、安全风险大等问题。

3D密集架库房机器人盘点是指利用机器人技术自动对密集架库房中的档案进行清点和核查的过程。这种技术结合了3D建模、导航定位、图像识别等多种先进技术,能够实现对库房内档案的高效、准确盘点。

在3D密集架库房中,机器人首先通过3D建模技术获取库房的三维布局和档案存放信息。然后,机器人利用导航定位系统自主导航到各个密集架前,并通过图像识别技术对档案进行识别和清点。

在盘点过程中,机器人会逐个扫描密集架上的档案,并与库房管理系统中的档案信息进行比对。如果发现差异或错误,机器人会立即进行记录和报告,以便工作人员及时处理。

通过3D密集架库房机器人盘点,可以大大提高盘点的效率和准确性,减少人为因素导致的误差和遗漏。同时,机器人盘点还可以实现无人值守,减少人力成本和安全风险,提高库房的管理水平和安全性。

具体来说,密集架库房机器人盘点解决方案包括以下几个部分:

  1. 机器人及导航定位系统:选用具有自主导航功能的机器人,通过激光导航、视觉导航等技术实现精确定位和自主移动。机器人能够自主遍历库房中的各个密集架区域,进行档案的盘点工作。
  2. 图像识别系统:机器人配备高清摄像头和图像识别算法,能够对密集架上的档案进行精确识别。通过图像识别技术,机器人可以读取档案的标签、条形码等信息,并与库房管理系统中的数据进行比对。
  3. 数据分析系统:机器人盘点完成后,将收集到的数据上传至数据分析系统。系统对盘点数据进行处理和分析,生成盘点报告和异常情况预警,帮助管理人员快速了解库房的存储状态和潜在问题。
  4. 安全防护系统:为确保盘点过程的安全,解决方案中还包括安全防护系统。该系统能够监测库房内的温度、湿度等环境参数,确保档案的安全存储。同时,机器人还配备了碰撞检测、紧急停车等功能,以避免在盘点过程中发生意外。
  • 在档案室的三维模型中,为每个密集架和档案分配唯一的标识符。
  • 使用图像识别算法(如深度学习模型)训练机器人识别档案上的标签或条形码。
  • 编写盘点逻辑,让机器人在虚拟环境中遍历各个密集架,并使用机械臂或扫描设备读取档案信息。
  • 将读取的数据与库存数据进行比对,生成盘点报告,并通过可视化界面展示盘点结果。
  1. 定义:机器人盘点是指利用机器人技术对档案室中的档案进行自动清点和核查的过程。
  2. 过程:机器人按照预设的路线遍历档案室,利用图像识别、RFID等技术识别档案,并与库存数据进行比对,生成盘点报告。
  3. 优势:提高盘点的准确性和效率,减少人工盘点的时间和误差,实时更新库存数据,优化库存管理。

2.3、代码实现

 functionjiqirenShaomiao  (objname) {if (modelbusiness.runState == 1) {//$("#PanDianData").show();
        modelbusiness.sendMessage(null,"盘点扫描");var jiqiren =msj3DObj.commonFunc.findObject(objname);
jiqiren.children[
1].visible = true;new TWEEN.Tween(jiqiren.children[1].rotation).to({ x: 0.8 }, 750).onUpdate(function() {
modelbusiness.addPanDianData();
}).onComplete(
function() {if (modelbusiness.runState == 1) {new TWEEN.Tween(jiqiren.children[1].rotation).to({ x: 2.4 }, 1500).onUpdate(function() {
modelbusiness.addPanDianData();

}).onComplete(
function() {if (modelbusiness.runState == 1) {new TWEEN.Tween(jiqiren.children[1].rotation).to({ x: Math.PI / 2 }, 750).onUpdate(function() {
modelbusiness.addPanDianData();

}).onComplete(
function() {
jiqiren.children[
1].visible = false;
}).start();
}
}).start();
}
}).start();
}
}

三、人工查档

3.1、效果展示

3.2、解决方案

在3D档案室中,为了提高查档取档的效率和准确性,通常会采用一些先进的技术手段,如智能货架、RFID标签、自动化搬运设备等。这些技术可以帮助快速定位档案位置、减少人工查找时间,并提高档案管理的整体效率。

此外,为了确保档案的安全性和完整性,3D档案室还会采取一系列安全措施,如监控摄像头、门禁系统、防火防盗设施等。这些措施可以有效防止档案被盗、丢失或损坏等情况的发生。

概述:创建一个三维可视化的档案室环境,模拟用户或管理员在档案室中手动查找和检索档案的过程。

实现:

  1. 使用3D建模软件构建档案室的三维模型,并提供交互式界面供用户浏览。
  2. 在模型中设置检索工具或目录系统,允许用户输入查询条件(如关键词、档案编号等)。
  3. 根据查询条件,高亮显示符合条件的档案位置,并提供导航指引帮助用户快速找到档案。
  4. 允许用户在虚拟环境中手动浏览档案架、抽屉等,查看档案的详细信息。

定义:人工查档是指档案管理员或用户通过手动方式在档案室中查找和检索档案的过程。

过程:用户或管理员在档案室的目录系统或检索工具中输入查询条件,然后手动浏览档案架、抽屉或密集架,找到符合条件的档案。

适用场景:适用于档案量不大、对查档速度要求不特别高或需要人工判断的情况。

在3D档案室中,人工查档取档的过程通常涉及以下步骤:

  1. 交互界面查询:用户首先通过2D面板(例如触摸屏或电脑界面)进行档案查询。可以输入关键词、档案编号或其他相关信息来检索所需的档案。
  2. 系统定位:档案管理系统根据用户输入的查询条件,快速定位到相应的档案位置。这通常涉及到对档案室中各个密集架、档案盒和具体档案的精确管理。
  3. 人工取档:一旦系统定位到目标档案的位置,用户(通常是档案管理员或工作人员)会前往相应的密集架或档案盒处,手动取出所需的档案。在这个过程中,可能需要使用梯子或其他工具来访问高处的档案。
  4. 档案核验:取出档案后,工作人员会进行核验,确保取出的档案与用户需求一致。
  5. 档案利用:核验无误后,档案会被提供给用户进行查阅或使用。在使用完毕后,档案需要按照规定进行归档和整理。

3.3、代码实现

 var cabforename = "cabinet2_";var positionZ = [-350];if (area == 1) {
cabforename
= "cabinet_";
positionZ
= [320];
}
var rundata =[];
{
var cabobj = msj3DObj.commonFunc.findObject(cabforename +nub);var step1 ={"timeLong": nub * 500 , "position": { "x": cabobj.position.x + 180, "y": 8, "z": 23.203 }, "target": { "x": cabobj.position.x + 15, "y": -18.18775405235692, "z": 23.203 }, "additionsTimeLong": 0,"additions": [
{
"changeParam": "position", "position": {
x: cabobj.position.x
+ 15,
z:
23.203,
},
"addtion_name": "people"}
]
};
//{ x: -1289.8040127242514, y: 216.26956010470187, z: -193.36005383255667 } //msj3DObj.controls.target //si { x: -1287.253404838314, y: 89.3259331925796, z: 0.876428765845767 } var step2 ={"timeLong": 300, "position": { "x": cabobj.position.x + 15, "y": 212, "z": (area == 1 ? -193 : 197) }, "target": { "x": cabobj.position.x + 15, "y": 89.3259331925796, z: 0.876428765845767 }, "additionsTimeLong": 0,"additions": [
{
"changeParam": "function", "function": "sendMessage", "paramvalue": "面向档案架", "addtion_name": "people"}
]
};
//转动面向架子 var step3 ={"timeLong": 300, "position": { "x": cabobj.position.x + 15, "y": 212, "z": (area == 1 ? -193 : 197) }, "target": { "x": cabobj.position.x + 15, "y": 89.3259331925796, z: 0.876428765845767 }, "additionsTimeLong": 100,"additions": [
{
"changeParam": "position", "position": {
x: cabobj.position.x
+ 15,
z:
23.203 + (area == 1 ? 1 : -1),
},
"addtion_name": "people"}
]
};
//开架 var step3_1 ={"timeLong": 0, "position": { "x": cabobj.position.x + 15, "y": 212, "z": (area == 1 ? -193 : 197) }, "target": { "x": cabobj.position.x + 15, "y": 89.3259331925796, z: 0.876428765845767 }, "additionsTimeLong": 0, "additionsTimeLong": 1000,"additions": [
{
"changeParam": "function", "function": "openCabnitByName", "paramvalue": "打开架子", "addtion_name": cabforename +nub },
{
"changeParam": "function", "function": "sendMessage", "paramvalue": "打开架子", "addtion_name": "people"}
]
};
rundata
=rundata.concat([step1, step2, step3, step3_1]);for (var pj = 0; pj < positionZ.length; pj++) {//进入 位置一 var step4 ={"timeLong": 0, "position": { "x": cabobj.position.x + 15, "y": 212, "z": (area == 1 ? -193 : 197) }, "target": { "x": cabobj.position.x + 15, "y": 89.3259331925796, z: 0.876428765845767 }, "additionsTimeLong": 0, "additionsTimeLong": 3000,"additions": [
{
"changeParam": "position", "position": {
x: cabobj.position.x
+ 15,
z: positionZ[pj],
},
"addtion_name": "people"}
]
};
//转动面向架子 var step5 ={"timeLong": 0, "position": { "x": cabobj.position.x + 15, "y": 212, "z": (area == 1 ? -193 : 197) }, "target": { "x": cabobj.position.x + 15, "y": 89.3259331925796, z: 0.876428765845767 }, "additionsTimeLong": 0, "additionsTimeLong": 100,"additions": [
{
"changeParam": "position", "position": {
x: cabobj.position.x
+ (area == 1 ? 16 : 14),
z: positionZ[pj],
},
"addtion_name": "people"}
]
};
//停留 var step6 ={"timeLong": 0, "position": { "x": cabobj.position.x + 15, "y": 212, "z": (area == 1 ? -193 : 197) }, "target": { "x": cabobj.position.x + 15, "y": 89.3259331925796, z: 0.876428765845767 }, "additionsTimeLong": 0, "additionsTimeLong": 17000,"additions": [
{
"changeParam": "function", "function": "peopleGetBook", "paramvalue": "取书_" + area + "_" + nub, "addtion_name": "people"},
{
"changeParam": "function", "function": "sendMessage", "paramvalue": "取档案", "addtion_name": "people"}
]

};

rundata
=rundata.concat([step4, step5, step6]);
}
//转动角度 var step7 ={"timeLong": 300, "position": { "x": cabobj.position.x + 15, "y": 212, "z": (area == 1 ? -193 : 197) }, "target": { "x": cabobj.position.x + 15, "y": -18.18775405235692, "z": 23.203 }, "additionsTimeLong": 0, "additionsTimeLong": 0};//回退 var step8 ={"timeLong": 0, "position": { "x": cabobj.position.x + 15, "y": 212, "z": (area == 1 ? -193 : 197) }, "target": { "x": cabobj.position.x + 15, "y": -18.18775405235692, "z": 23.203 }, "additionsTimeLong": 5000,"additions": [
{
"changeParam": "position", "position": {
x: cabobj.position.x
+ 15,
z:
23.203,
},
"addtion_name": "people"}
]
};
rundata
=rundata.concat([step7, step8]);
}
this.peopleSearchData = this.peopleSearchData.concat(rundata);this.peopleSearchData.push({"timeLong": 0, "position": { "x": 1496.961677226794, "y": 6.048526864006573, "z": -396.50227110708374 }, "target": { "x": 1482.9322600214946, "y": -4.512061245433281, "z": -165.11374369473938 }, "additionsTimeLong": 1000,"additions": [
{
"changeParam": "function", "function": "stopChaDang", "paramvalue": "停止查档", "addtion_name": ""}
]
});
this.doRouteRun(0, this.peopleSearchData);

四、设备巡检

4.1、效果展示

4.2、解决方案

3D模拟档案室设备巡检是一种利用3D建模和仿真技术来模拟和评估档案室设备巡检过程的方法。这种技术可以帮助档案管理员或设备维护人员在不进入实际档案室的情况下,对档案室设备进行全面、系统的检查和评估。以下是3D模拟档案室设备巡检的一般步骤:

  1. 建立3D档案室模型:首先,需要利用3D建模软件建立档案室的详细模型。模型应该包括档案室内的所有设备、设施以及空间布局。

  2. 设备数据集成:将档案室设备的实际运行数据集成到3D模型中。这些数据可以包括设备的运行状态、温度、噪音等参数,以便在模拟过程中进行实时监测和分析。

  3. 制定巡检计划:在3D模型中制定设备巡检计划,明确巡检的路线、时间以及需要检查的设备。

  4. 模拟巡检过程:通过仿真软件,模拟巡检人员按照巡检计划在档案室内进行设备巡检的过程。在模拟过程中,可以实时监测设备的运行状态和参数,并发现潜在的问题或隐患。

  5. 问题诊断与分析:根据模拟巡检的结果,对发现的问题或隐患进行诊断和分析。可以利用仿真软件提供的数据分析工具,对设备的运行状态、性能等指标进行深入挖掘,为后续的维护和管理提供决策支持。

  6. 结果展示与报告:将模拟巡检的结果以直观的方式进行展示,如生成巡检报告、数据分析图表等。这些结果可以作为改进档案室设备管理和维护的依据,帮助提高设备巡检的效率和准确性。

通过3D模拟档案室设备巡检,可以在不进入实际档案室的情况下,全面、系统地检查和评估档案室设备的运行状态和性能。这种技术可以帮助档案管理员或设备维护人员及时发现潜在问题或隐患,提高设备巡检的效率和准确性,为档案室的安全和稳定运行提供有力保障。

实现:

  • 使用3D建模软件构建档案室的三维模型,并集成各种设备和传感器的模型。
  • 编写巡检逻辑,模拟巡检人员按照预定的巡检计划和路线在虚拟环境中进行巡检。
  • 使用传感器模拟技术,模拟巡检人员使用各种设备(如温度计、湿度计、摄像头等)对档案室环境进行监测和记录。
  • 在巡检过程中,如果发现异常情况或问题,可以通过可视化界面进行标注和记录,以便后续处理和改进。

定义:巡检是指定期对档案室的设施、设备和环境进行检查和评估的过程,以确保档案室的安全和正常运行。

过程:巡检人员按照预定的巡检计划和路线,对档案室的空调、消防系统、安防设备、照明、温度湿度等环境参数进行检查,记录异常情况,并及时处理。

目的:预防潜在的安全隐患,及时发现和解决问题,确保档案的安全保存和档案的连续性。

由于篇幅原因,我们本节课先到这里,后面我们更新如何创建一个可编辑工具完成配置

技术交流 1203193731@qq.com

如果你有什么要交流的心得 可邮件我

其它相关文章:

如何使用webgl(three.js)实现煤矿隧道、井下人员定位、掘进面、纵采面可视化解决方案——第十九课(一)

如何使用webgl(three.js)实现3D消防、3D建筑消防大楼、消防数字孪生、消防可视化解决方案——第十八课(一)

webgl(three.js)3D光伏,3D太阳能能源,3D智慧光伏、光伏发电、清洁能源三维可视化解决方案——第十六课

如何用webgl(three.js)搭建一个3D库房,3D仓库3D码头,3D集装箱,车辆定位,叉车定位可视化孪生系统——第十五课

webgl(three.js)实现室内三维定位,3D定位,3D楼宇bim、实时定位三维可视化解决方案——第十四课(定位升级版)

使用three.js(webgl)搭建智慧楼宇、设备检测、数字孪生——第十三课

如何用three.js(webgl)搭建3D粮仓、3D仓库、3D物联网设备监控-第十二课

如何用webgl(three.js)搭建处理3D隧道、3D桥梁、3D物联网设备、3D高速公路、三维隧道桥梁设备监控-第十一课

如何用three.js实现数字孪生、3D工厂、3D工业园区、智慧制造、智慧工业、智慧工厂-第十课

使用webgl(three.js)创建3D机房,3D机房微模块详细介绍(升级版二)

如何用webgl(three.js)搭建一个3D库房-第一课

如何用webgl(three.js)搭建一个3D库房,3D密集架,3D档案室,-第二课

使用webgl(three.js)搭建一个3D建筑,3D消防模拟——第三课

使用webgl(three.js)搭建一个3D智慧园区、3D建筑,3D消防模拟,web版3D,bim管理系统——第四课

如何用webgl(three.js)搭建不规则建筑模型,客流量热力图模拟

使用webgl(three.js)搭建一个3D智慧园区、3D建筑,3D消防模拟,web版3D,bim管理系统——第四课(炫酷版一)

使用webgl(three.js)搭建3D智慧园区、3D大屏,3D楼宇,智慧灯杆三维展示,3D灯杆,web版3D,bim管理系统——第六课

如何用webgl(three.js)搭建处理3D园区、3D楼层、3D机房管线问题(机房升级版)-第九课(一)

本文分享自华为云社区《
Go并发范式 流水线和优雅退出 Pipeline 与 Cancellation
》,作者:张俭。

介绍

Go 的并发原语可以轻松构建流数据管道,从而高效利用 I/O 和多个 CPU。 本文展示了此类pipelines的示例,强调了操作失败时出现的细微之处,并介绍了干净地处理失败的技术。

什么是pipeline?

pipeline在Go中并没有书面的定义,只是众多并发程序中的一种。非正式地,pipeline由一系列stage组成。每个stage是运行着同一个function的协程组。在每个stage,协程们

  • 通过inbound channel从上游获取数据
  • 在data上进行运算,通常会产生新的值
  • 通过outbound channel向下游发送数据

每个Stage都有数个inbound channel和outbound channel,除了第一个和最后一个Stage,分别只有outbound和inbound channel。第一个Stage通常叫做
Source

Producer
。最后一个Stage通常叫做
Sink

Consumer

我们将从一个简单的示例pipeline开始来解释这些想法和技术。 稍后,我们将提供一个更实际的例子。

Squaring numbers 平方数

考虑一个有着三个阶段的流水线。

第一阶段,
gen
,是个将整数列表转换为一个发射列表中整数的channel的函数。
gen
函数启动一个go routine,用来发送channel中的整数,然后当所有的整数都被发出后,将channel关闭:

func gen(nums ...int) <-chan int{out := make(chan int)
go func() {
for _, n :=range nums {out <-n
}
close(
out)
}()
return out}

第二阶段,
sq
从上面的channel中接收数据,返回一个发射对应整数平方数的channel。当inbound channel关闭后,并且这一阶段将所有的value发送到下游后,再将这个outbound channel关闭

func sq(in <-chan int) <-chan int{out := make(chan int)
go func() {
for n := range in{out <- n *n
}
close(
out)
}()
return out}

main函数组织整个pipeline,并且运行最终的stage:从第二个stage中接收数据然后逐个打印,直到channel被关闭

func main() {//Set up the pipeline
    c := gen(2, 3)out :=sq(c)//Consume the output//4
    fmt.Println(<-out)//9
    fmt.Println(<-out)
}

既然sq的inbound channel和outbound channel类型相同,我们可以将其进行任意数量的组合。我们还可以将main函数重写为循环,就像在其他Stage中做的那样一样。

func main() {//Set up the pipeline and consume the output.
    for n := range sq(sq(gen(2, 3))) {
fmt.Println(n)
//16 then 81 }
}

扇入和扇出

许多函数可以从一个channel中获取数据直到channel被关闭,这被叫做扇出。这提供了一种在worker之间分配工作以并行化 CPU 使用和 I/O 的方法。

一个函数可以通过将多个input channel多路复用到同一个channel,当所有的channel关闭时,该多路复用channel才关闭。从而达到从多个input获取数据并处理,直到所有input channel都关闭才停止的效果。这叫做扇入。

我们可以将我们的流水线改为运行两个
sq
,每个都从相同的channel读取数据。我们引入一个新的函数
merge
,来做扇入的工作

func main() {in := gen(2, 3)//Distribute the sq work across two goroutines that both read from in.
    c1 := sq(in)
c2 :
= sq(in)//Consume the merged output from c1 and c2. for n :=range merge(c1, c2) {
fmt.Println(n)
//4 then 9, or 9 then 4 }
}

merge
函数通过对每个channel开启一个协程,把数据拷贝到另一个out channel中,实现将channel列表转换为一个channel的效果。当所有send操作完成后,再将out channel关闭。

向一个已经关闭上的channel发送数据会导致panic,所以保证发送完所有再关闭channel至关重要。sync.WaitGroup提供了一个简单地方式来编排这个同步

func merge(cs ...<-chan int) <-chan int{varwg sync.WaitGroupout := make(chan int)//Start an output goroutine for each input channel in cs. output//copies values from c to out until c is closed, then calls wg.Done
    output := func(c <-chan int) {for n :=range c {out <-n
}
wg.Done()
}
wg.Add(len(cs))
for _, c :=range cs {
go output(c)
}
//Start a goroutine to close out once all the output goroutines are//done. This must start after the wg.Add call. go func() {
wg.Wait()
close(
out)
}()
return out}

短暂的停顿

我们的pipeline函数有这样的模式:

  • 当发送任务结束后,关闭发送output channel
  • 直到input channel关闭前,一直从input channel中接收消息

这个模式下,每个阶段都可以用协程+for循环的模式来书写,保证每个数据发送到下游后再关闭所有协程。

但是在实际的pipeline中,阶段并不总是接收所有来自inbound channel的数据。通常,如果inbound的值出现了错误,pipeline会提前退出。 在任何一种情况下,接收者都不必等待剩余值到达,并且我们希望fast fail(较早阶段的Stage尽早停止后期Stage不需要的值)。

在我们的示例pipeline中,如果一个Stage未能消费所有inbound值,则尝试计算后并发送这些值的 goroutine 将无限期阻塞:

    //Consume the first value from the output.
    out :=merge(c1, c2)
fmt.Println(
<-out) //4 or 9 return //Since we didn't receive the second value from out,//one of the output goroutines is hung attempting to send it. }

这就导致了资源泄漏:协程消耗内存、运行资源,并且在协程栈内的golang堆引用导致垃圾无法回收。协程只能自己退出,不能由垃圾回收机制回收。

即使下游的Stage无法接收所有inbound value,我们也需要把上游的协程退出。如果把上游的协程改为有buffer的,可以解决上面的问题。如果Buffer中还有空间,则发送操作可以立刻完成

c := make(chan int, 2) //buffer size 2
c <- 1  //succeeds immediately
c <- 2  //succeeds immediately
c <- 3  //blocks until another goroutine does <-c and receives 1

当要发送的数目可以在channel创建时知道时,buffer可以简化代码。举个例子,让我们来使用buffer channel,不开辟新的协程来重写
gen
方法:

func gen(nums ...int) <-chan int{out := make(chan int, len(nums))for _, n :=range nums {out <-n
}
close(
out)return out}

在我们的pipeline中,我们就需要在
merge
方法中使用的
channel
添加buffer:

func merge(cs ...<-chan int) <-chan int{varwg sync.WaitGroupout := make(chan int, 1) //enough space for the unread inputs//... 其余的没有变更 ...

尽管上面这个方案修复了阻塞的问题,但它是很差的方案。这里有一个对1的硬编码,这太脆弱了?你真的能预料到有多少个值不能被正常发送吗?一旦两个值不能正常发送,你的协程又阻塞了。

作为替代,我们需要给下游阶段提供一个机制,知会下游阶段,发送者已经停止发送了。

Explicity cancellation 显示取消


main
函数决定不从out处接收所有数据,而是退出时,它必须知会上游阶段的协程放弃接下来的发送。它通过向一个名叫
done
的channel发送数据来完成这个动作。因为发送方有两个,所以 向
done
发送两次数据。

func main() {in := gen(2, 3)//Distribute the sq work across two goroutines that both read from in.
    c1 := sq(in)
c2 :
= sq(in)//Consume the first value from output. done := make(chan struct{}, 2)out :=merge(done, c1, c2)
fmt.Println(
<-out) //4 or 9//Tell the remaining senders we're leaving. done <- struct{}{}
done
<- struct{}{}
}

发送到out channel的发送者把原来的逻辑替换成一个select操作,select或者发送一个数据,抑或从
done
处接收到数据。因为
done
中数据值的类型根本不重要,主要是接收到值这个事件本身很重要,所以
done
channel的类型时
struct {}

output
循环继续在
inbound
channel上执行,所以上游的阶段并没有被阻塞。(我们稍后会讨论如何让循环迅速返回。)

func merge(done <-chan struct{}, cs ...<-chan int) <-chan int{varwg sync.WaitGroupout := make(chan int)//Start an output goroutine for each input channel in cs.  output//copies values from c to out until c is closed or it receives a value//from done, then output calls wg.Done.
    output := func(c <-chan int) {for n :=range c {select{case out <-n:case <-done:
}
}
wg.Done()
}
//... the rest is unchanged ...

这个方法有一个问题:每一个下游接收者都需要知道可能阻塞的上游发送者总数。维护它们的数目,是一个琐碎又容易出错的事情。

我们需要一个机制来让不可知的、无界的发送协程来停止发送到下游的值。在Go,我们可以通过关闭channel来完成这件事,因为在已经关闭的channel上执行receive操作,会立刻返回该元素的零值。

这说明
main
函数可以简单地通过关闭
done
channel来让所有的发送者不阻塞。关闭操作是一个高效的广播。我们把pipeline中的每个函数都接受
done
作为参数,并把
done
在defer语句中关闭, 这样,如果在
main
函数中返回,都会通知pipeline中的阶段退出。

func main() {//Set up a done channel that's shared by the whole pipeline,//and close that channel when this pipeline exits, as a signal//for all the goroutines we started to exit.
    done := make(chan struct{})
defer close(done)
in := gen(done, 2, 3)//Distribute the sq work across two goroutines that both read from in. c1 := sq(done, in)
c2 :
= sq(done, in)//Consume the first value from output. out :=merge(done, c1, c2)
fmt.Println(
<-out) //4 or 9//done will be closed by the deferred call. }

现在当
done
channel关闭后,接收到close信息的阶段,都可以直接退出了。
merge
函数中的
outout
协程可以不从
inbound
channel中取数据直接退出,因为它知道,上游的发送sq,接收到close信息,也会直接退出。
output
通过defer语句来保证
wg.Done()
一定被调用。(译者注:来关闭out channel)

func merge(done <-chan struct{}, cs ...<-chan int) <-chan int{varwg sync.WaitGroupout := make(chan int)//Start an output goroutine for each input channel in cs.  output//copies values from c to out until c or done is closed, then calls//wg.Done.
    output := func(c <-chan int) {
defer wg.Done()
for n :=range c {select{case out <-n:case <-done:return}
}
}
//... the rest is unchanged ...

相似的,当接收到close信号时,
sq
函数也可以立刻返回。
sq
通过
defer
语句来保证
out
channel一定被关闭。

这是给构建pipeline的一些指导:

  • 当所有的发送操作完成后,关闭outbound channel
  • 如果发送发不阻塞,或是channel没有关闭,接收者会一直从channel中接收数据

Pipeline通过如下两个方式来解除发送者的阻塞

  • 确保channel的buffer足够大
  • 显示知会发送者,接收者已经放弃接收

Digesting a tree 对树进行摘要

让我们来考虑一个更实际的pipeline

MD5 是一种消息摘要算法,可用作文件校验和。 命令行实用程序 md5sum 打印文件列表的摘要值。

% md5sum *.go
d47c2bbc28298ca9befdfbc5d3aa4e65 bounded.go
ee869afd31f83cbb2d10ee81b2b831dc parallel.go
b88175e65fdcbc01ac08aaf1fd9b5e96 serial.go

我们的示例程序类似于 md5sum,但将单个目录作为参数并打印该目录下每个常规文件的摘要值,按路径名排序。

%go run serial.go .
d47c2bbc28298ca9befdfbc5d3aa4e65 bounded.go
ee869afd31f83cbb2d10ee81b2b831dc parallel.go
b88175e65fdcbc01ac08aaf1fd9b5e96 serial.go

我们的主函数调
MD5All
这个辅助函数,返回路径名和摘要值的map,
main
函数再将它们排序打印

func main() {//Calculate the MD5 sum of all files under the specified directory,//then print the results sorted by path name.
    m, err := MD5All(os.Args[1])if err !=nil {
fmt.Println(err)
return}var paths []string for path :=range m {
paths
=append(paths, path)
}
sort.Strings(paths)
for _, path :=range paths {
fmt.Printf(
"%x %s\n", m[path], path)
}
}

MD5All
函数是我们讨论的重点。在如下串行化的实现中,没有使用并发技术,只是简单对文件进行了遍历

//MD5All reads all the files in the file tree rooted at root and returns a map//from file path to the MD5 sum of the file's contents.  If the directory walk//fails or any read operation fails, MD5All returns an error.
func MD5All(root string) (map[string][md5.Size]byte, error) {
m :
= make(map[string][md5.Size]byte)
err :
= filepath.Walk(root, func(path string, info os.FileInfo, err error) error {if err !=nil {returnerr
}
if !info.Mode().IsRegular() {returnnil
}
data, err :
=ioutil.ReadFile(path)if err !=nil {returnerr
}
m[path]
=md5.Sum(data)returnnil
})
if err !=nil {returnnil, err
}
returnm, nil
}

并行计算摘要

在并行的解法中,我们将
MD5All
分割为两个阶段的pipeline。第一个阶段,
sumFiles
,遍历文件树,针对每个文件,在新的协程中计算摘要,然后把结果发送到channel中,这是result的类型

type result struct{
path
stringsum [md5.Size]byteerr error
}

sumFiles
返回两个channel:一个是result channel,另一个是
filepath.Walk
中产生的错误。
walk
函数针对每个文件启动一个新的协程来处理,然后检查
done
channel。如果
done
已经被关闭,
walk
函数会立刻停止:

func sumFiles(done <-chan struct{}, root string) (<-chan result, <-chan error) {//For each regular file, start a goroutine that sums the file and//sends the result on c.//Send the result of the walk on errc.
    c :=make(chan result)
errc :
= make(chan error, 1)
go func() {
varwg sync.WaitGroup//If any error occurred, walk method will return err := filepath.Walk(root, func(path string, info fs.FileInfo, err error) error {if err !=nil {returnerr
}
if !info.Mode().IsRegular() {returnnil
}
wg.Add(
1)
go func() {
data, err :
=ioutil.ReadFile(path)select{case c <-result{
path: path,
sum: md5.Sum(data),
err: err,
}:
case <-done:
}
wg.Done()
}()
//Abort the walk if done is closed. select{case <-done:return errors.New("walk canceled")default:returnnil
}
})
//Walk has returned, so all calls to wg.Add are done.//Start a goroutine to close c once all the sends are done.//No select needed here, since errc is buffered. errc <-err
}()
returnc, errc
}

MD5All

c
中接收到摘要数据。当发生错误时,
MD5All
会迅速返回,通过
defer
语句来关闭
done
channel

func MD5All(root string) (map[string][md5.Size]byte, error) {//MD5All closes the done channel when it returns; it may do so before//receiving all the values from c and errc.
    done := make(chan struct{})
defer close(done)

c, errc :
=sumFiles(done, root)

m :
= make(map[string][md5.Size]byte)for r :=range c {if r.err !=nil {returnnil, r.err
}
m[r.path]
=r.sum
}
if err := <-errc; err !=nil {returnnil, err
}
returnm, nil
}

有界的并行

parallel.go 中的 MD5All 实现为每个文件启动一个新的 goroutine。 在包含许多大文件的目录中,这可能会分配比机器上可用的内存更多的内存。

我们可以通过限制并行读取的文件数量来限制这些分配。 在新的解决方式中,我们通过创建固定数量的 goroutine 来读取文件来做到这一点。 我们的pipeline现在分为三个阶段:遍历树、读取并计算文件摘要以及收集摘要。

第一阶段 walkFiles 发射出文件树中常规文件的路径:

func walkFiles(done <-chan struct{}, root string) (<-chan string, <-chan error) {
paths :
= make(chan string)
errc :
= make(chan error, 1)
go func() {
//Close the paths channel after Walk returns. defer close(paths)//No select needed for this send, since errc is buffered. errc <- filepath.Walk(root, func(path string, info os.FileInfo, err error) error {if err !=nil {returnerr
}
if !info.Mode().IsRegular() {returnnil
}
select{case paths <-path:case <-done:return errors.New("walk canceled")
}
returnnil
})
}()
returnpaths, errc
}

第二阶段启动固定数量的协程来计算文件摘要,然后发送到c channel中

func digester(done <-chan struct{}, paths <-chan string, c chan<-result) {for path :=range paths {
data, err :
=ioutil.ReadFile(path)select{case c <-result{path, md5.Sum(data), err}:case <-done:return}
}
}

和之前的示例不同,因为多个协程都在共享channel上发送数据,
digester
函数并没有关闭output channel。作为替代,当所有的digesters跑完之后,
MD5All
会关闭channel

    //Start a fixed number of goroutines to read and digest files.
    c :=make(chan result)varwg sync.WaitGroupconst numDigesters = 20wg.Add(numDigesters)for i := 0; i < numDigesters; i++{
go func() {
digester(done, paths, c)
wg.Done()
}()
}
go func() {
wg.Wait()
close(c)
}()

这里也可以针对每个digester开启独立的channel,不过到时候就要对channel进行扇入处理。

最终阶段从
c
中取得所有结果,并且检查errc中的错误。此检查不能更早发生,因为在此之前,walkFiles 可能会阻塞:

(译者注:要保证检查errc的错误,发生在filePath.Walk启动后,
done
不会再次发送了,协程就不会退出)

   m := make(map[string][md5.Size]byte)for r :=range c {if r.err !=nil {returnnil, r.err
}
m[r.path]
=r.sum
}
//Check whether the Walk failed. if err := <-errc; err !=nil {returnnil, err
}
returnm, nil
}

总结

本文介绍了在 Go 中构建流数据pipeline的技术。 处理此类pipeline中的故障很棘手,因为pipeline中的每个阶段可能会阻止尝试向下游发送值,并且下游阶段可能不再关心传入的数据。 我们展示了关闭通道如何向管道启动的所有 goroutine 广播“done”信号,并定义了正确构建管道的指南。

点击关注,第一时间了解华为云新鲜技术~