Skip to content
This repository has been archived by the owner on Mar 26, 2023. It is now read-only.

Xuedixuedi/Elevator-dispatching-demo

Repository files navigation

进程管理之电梯调度系统

——操作系统课程项目1

姓名:

学号:

[TOC]

项目目的

  1. 学习调度算法
  2. 通过实现电梯调度,体会操作系统调度过程
  3. 学习特定环境下多线程编程方法

开发工具

  1. 开发环境:Chrome+VS Code
  2. 开发语言:HTML+CSS+JavaScript
  3. 第三方框架:Element UI、Vue.js、Webpack

项目需求

基本任务

某一栋楼20层,有5部互联的电梯。基于线程的思想,编写一个电梯调度程序。

五部电梯门口的按钮是互联结的,即当外部请求按钮按下去时,需要操作系统选择一部电梯来响应这个请求。

所有电梯初始状态都在第一层,无按钮被按下。每个电梯如果在它的上层或者下层没有相应请求情况下,则应该在原地保持不动。

解决方案

预览方式

  1. 项目在线预览地址(需联网):https://xuedixuedi.github.io/Elevator-dispatching-demo/dist/

  2. 或克隆本仓库/进入作业文件夹后,在终端输入以下命令预览:

# install dependencies
npm install

# serve with hot reload at localhost:8080
npm run dev

# build for production with minification
npm run build

# build for production and view the bundle analyzer report
npm run build --report

​ 打开浏览器,输入 http://localhost:8080/ 预览

组件构成

未命名文件 (1)的副本

组件 内容 负责逻辑
Home 盛放Inside和Outside的父组件 处理电梯外部调度逻辑
Inside 每部电梯的内部 处理电梯内调度逻辑
Outside 每层楼的外部按钮 处理每层楼按钮逻辑

电梯内部:

  • 楼层数字键

  • 开门/关门键

  • 报警键、紧急通话键

  • 当前楼层及运行方向显示

电梯外部:

  • 每层楼对应上下行键

系统设计

电梯调度系统

系统 功能
监控系统 每一个最小时间间隔汇报并修改一次电梯状态
交互系统 接受来自电梯内外用户按下按钮的指令
调度系统 调度系统负责实现电梯运行的主要逻辑

系统主要变量

变量 作用 值/类型
running 描述电梯在是否在运行中 bool
going_up 描述电梯当前状态是向上或向下(对应内部显示方向) bool
door 描述当前门是打开或关闭 bool
current_floor 描述电梯当前楼层位置 Number
outside_up[] 楼层数对应下标存放外部向上的呼梯信号 1或0
outside_down[] 楼层数对应下标存放外部向下的呼梯信号 1或0
inside[] 楼层数对应下标存放内部呼梯信号 1或0
call[] 存放内外所有呼梯信号 呼梯楼层数
button_click[] 楼层数对应下标存放内部按钮是否被按下,关联内部显示样式 1或0

主要函数

函数 功能
dial(floor) 将按下的按钮加入call[]队列并将队列从大到小排序,并更新当前电梯状态
checkStatus() 接受来自电梯内外用户按下按钮的指令,并及时更新running 和 going_up的值
run() 判断运行中的电梯当前楼层是否存在内外呼叫队列中,如果存在,改变电梯运行状态并更新当前状态
ding(floor) 暂停计时器,开门,关门,重新开启计时器
calculateDistance(press_floor) 计算当前外部按下的楼层应该加入哪一部电梯的等待队列

算法设计

flow

内调度

核心算法:LOOK算法

电梯调度的核心是 SCAN 算法,这是操作系统用于磁盘调度的算法之一。SCAN 算法的核心思想非常简单,就是在一条路上来来回回的走,一个方向上走到头之后换反方向再走到头。

LOOK算法是SCAN算法的一种改进。对LOOK算法而言,电梯同样在最底层和最顶层之间运行。但当LOOK算法发现电梯所移动的方向上不再有请求时立即改变运行方向,而扫描算法则需要移动到最底层或者最顶层时才改变运行方向。

要实现这个算法,主要需要两样东西:一个数组,以及一个“LOOK”的算法。

1. 等待队列

dial(floor) {
            console.log("现在按下第几层?", floor)
            this.call.push(floor)
            this.call.sort()
            console.log("现在的队列是:", this.call)
            if (!this.running) {
                this.checkStatus()
            }
            // }
        },

我用一个数组 call 来保存所有被按下的楼层,无论是在电梯外还是电梯内按的。 dial() 函数同样适用于电梯内外的所有楼层按钮,只要按下,都按照其中的逻辑添加到 queue 中。但一个数字最多在数组中出现三次:内部请求,外部向上请求,外部向下请求。

sort() 这一步很关键,它确保了前进方向上楼层都是以楼层数为序的,而不是以添加顺序为序(这是电梯调度算法的特点)。这里因为数据量不大,因此不深究排序算法的复杂度,直接使用Array类的原生方法。

同时,需要维护inside[]outside_up[]outside_down[]三个数组,用来判断队列中的数字是来自哪里的请求。

2. LOOK算法

checkStatus() {
            //判断呼叫队列是否还有东西,修改运行状态
            this.running = this.call.length > 0 ? true : false
            //是否在底层
            if (this.current_floor == this.min_floor) {
                this.going_up = true
                //是否在顶层
            } else if (this.current_floor == this.max_floor) {
                this.going_up = false
            } else {
                //在中间层的状态判断
                this.going_up &&
                (!this.running ||
                    this.current_floor <= this.getMaxInQueue(this.call))
                    ? (this.going_up = true)
                    : (this.going_up = false)
                !this.going_up &&
                (!this.running ||
                    this.current_floor >= this.getMinInQueue(this.call))
                    ? (this.going_up = false)
                    : (this.going_up = true)
                console.log(this.getMaxInQueue(this.call), this.going_up)
            }
        },

checkStatus() 用于更新一些状态变量的取值,用于决定电梯是否开始运行,以及什么时候该回头了。 running 用于记录当前电梯的运行状态,初始状态为 false,因为假定它停在一楼。只要有任何一层楼被按下,即数组 queue 非空,电梯就处于运行状态。 going—_up 用于记录电梯是上行还是下行,上行为 true,下行为 false,初始状态为 true,因为假定它停在一楼,向上的可能性最大。在顶层和底层时固定回头,对于中间的楼层,就看当前前进方向上时候存在于请求队列的楼层,如果有,就继续走,否则回头。对于停止状态下的电梯,暂时不管它的状态。

3. 主函数

设定了一个定时器,每秒钟运行一次主函数run(),执行电梯的移动和判断当前是否需要停靠等动作。

对于运行中的电梯, run() 函数首先检查当前楼层时候被按下,即 current_floor 是否存在于数组 call 中,存在则调用 ding(floor) 函数,暂停计时器,熄灭该楼层按钮的灯光,将该楼层从数组中移除,开门,关门,重启计时器。否则根据行进方向向上或向下移动一层,并更新楼层计数器。注意:由于 JavaScript 本身并不支持类似 sleep(milis) 这种用于暂停的函数,而回调机制又都是异步的,因此只能通过嵌套的 setTimeout() 来实现延时触发的效果,好在这里只有两个动作需要回调,否则代码的嵌套深度会达到惊人的程度。

运行函数:

run() {
            var need_stop = false
            if (this.running) {
                if (this.call.indexOf(this.current_floor) > -1) {
                    //到达内部呼叫楼层
                    if (this.inside[this.current_floor] == 1) {
                        this.removeFromQueue(this.call, this.current_floor)
                        this.inside[this.current_floor] = 0
                        this.button_click[this.current_floor] = 0
                        need_stop = true
                    }
                    if (this.going_up) {
                        if (this.outside_up[this.current_floor] == 1) {
                            console.log("到达外面呼叫:", this.current_floor)
                            this.removeFromQueue(this.call, this.current_floor)
                            this.outside_up[this.current_floor] = 0
                            this.updateOutsideUp(this.current_floor)
                            need_stop = true
                        }
                        if (
                            this.outside_down[this.current_floor] == 1 &&
                            this.current_floor == this.getMaxInQueue(this.call)
                        ) {
                            this.removeFromQueue(this.call, this.current_floor)
                            this.outside_down[this.current_floor] = 0
                            this.updateOutsideDown(this.current_floor)
                            need_stop = true
                        }
                    } else {
                        if (this.outside_down[this.current_floor] == 1) {
                            this.removeFromQueue(this.call, this.current_floor)
                            this.outside_down[this.current_floor] = 0
                            this.updateOutsideDown(this.current_floor)
                            need_stop = true
                        }
                        if (
                            this.outside_up[this.current_floor] == 1 &&
                            this.current_floor == this.getMinInQueue(this.call)
                        ) {
                            this.removeFromQueue(this.call, this.current_floor)
                            this.outside_up[this.current_floor] = 0
                            this.updateOutsideUp(this.current_floor)
                            need_stop = true
                        }
                    }
                    if (need_stop) {
                        console.log("stop:", this.current_floor)
                        this.ding(this.current_floor)
                    } else {
                        this.going_up ? this.moveUp() : this.moveDown()
                    }
                } else {
                    this.going_up ? this.moveUp() : this.moveDown()
                    this.updateFloorInfo()
                }
                this.checkStatus()
            }
        },

停靠函数:

//暂停计时器,熄灭该楼层的灯光
        ding(floor) {
            let _this = this
            let that = this
            //需要电梯停下,就把timer清空
            if (this.timer) {
                clearInterval(this.timer)
            }
            this.openDoor()
            //不会重复执行的延时函数
            setTimeout(function() {
                _this.closeDoor()
                setTimeout(function() {
                    that.timer = setInterval(that.run, 1000)
                }, 3000)
            }, 4000)
        },
this.timer = setInterval(this.run, 1000)

外调度

外调度主要考虑外部1-20楼按下的电梯请求,需要5部电梯中的哪一部来解决,本系统采用的方法是:在每一次外部按钮被点击时,得到五部电梯的运行方向与当前楼层,以及等待队列中有哪些楼层,通过计算每一部电梯到达当前楼层的时间,综合中间需要停顿的时间,得到到达发送请求楼层时间最短的电梯,并将该请求加入电梯的请求队列。一旦一部电梯接受了这个请求,其他电梯将不再考虑这个请求。

计算某层按下向上的按钮时应该加入哪部电梯的代码:

//计算应该放入哪个电梯
        calculateUpDistance(press_floor) {
            var maxDistance = 2 * this.floor_count //可能出现的最小距离
            var distance = 0
            var elevatorToPush = 1
            for (var i = 0; i < this.ele_count; i++) {
                if (!this.running[i]) {
                    distance = Math.abs(press_floor - this.current_floor[i])
                } else {
                    var minInQueue = this.getMinInQueue(this.call[i]) //找出呼叫队列中最小的
                    var maxInQueue = this.getMaxInQueue(this.call[i]) //找出呼叫队列中最大的
                    if (this.current_floor[i] <= press_floor) {
                        if (this.going_up[i]) {
                            console.log("电梯向上,按键比当前高")
                            distance =
                                press_floor -
                                this.current_floor[i] +
                                5 *
                                    this.betweenCount(
                                        press_floor,
                                        this.current_floor[i],
                                        i
                                    )
                        } else {
                            console.log("电梯向下,按键比当前高")
                            distance =
                                this.current_floor[i] -
                                minInQueue +
                                press_floor -
                                minInQueue +
                                5 *
                                    this.betweenCount(
                                        minInQueue,
                                        press_floor,
                                        i
                                    )
                        }
                    } else {
                        if (this.going_up[i]) {
                            console.log("电梯向上,按键比当前低")
                            distance =
                                maxInQueue -
                                this.current_floor[i] +
                                maxInQueue -
                                minInQueue +
                                Math.abs(minInQueue - press_floor) +
                                5 *
                                    this.betweenCount(
                                        maxDistance,
                                        maxInQueue,
                                        i
                                    )
                            console.log(
                                "distance:",
                                distance,
                                "max:",
                                maxDistance
                            )
                        } else {
                            console.log("电梯向下,按键比当前低")
                            distance =
                                this.current_floor[i] -
                                minInQueue +
                                Math.abs(minInQueue - press_floor) +
                                5 *
                                    this.betweenCount(
                                        this.current_floor[i],
                                        minInQueue,
                                        i
                                    )
                        }
                    }
                    if (distance < maxDistance) {
                        maxDistance = distance
                        elevatorToPush = i + 1
                    }
                }
            }
            console.log(
                press_floor + "上楼将要放入第" + elevatorToPush + "电梯"
            )
            this.up = true
            return elevatorToPush //得到按下按键的楼层即将放入哪个电梯
        

用户界面

PC端

  1. 初始界面

    xuedixuedi.github.io_Elevator-dispatching-demo_dist_

  2. 运行界面截图

    localhost_8080_

手机端

  1. 紧急通话&报警

    gifhome_320x693_7s

  2. 电梯运行

gifhome_320x693_31s

项目总结

反思

  1. 由于对js语言不甚熟悉,在setTimeout()函数中传入函数时,函数会指向window对象,而不是当前组件对象。

    为了解决这个问题,在代码中加入let this = that 重命名this即可解决,或采用ES6中的箭头函数即可。

优点

  1. 采用了响应式布局,保证了在多种分辨率和大小的浏览器,包括PC端和手机端上运行均能保持较为美观的用户界面。
  2. 布局到了GitHub Pages,使该项目的体验只需要具备一款主流浏览器即可,不需要额外环境负担。
  3. 采用高度组件化的开发,代码低耦合高内聚,在需要修改电梯数量和总楼层数量时,仅需要对代码进行简单修改即可实现。

不足

  1. 纯粹采用前端开发,没有在本次项目中采用与后端通信的方式,而是将所有逻辑写在前端中。由于需要在多组件中传值,没有对传值做足够的优化,导致了部分代码的冗余。
  2. 在外部调度中,即按下的按钮需要分配给哪部电梯的算法中,使用的算法是对到达时间粗略的估计,没有考虑当电梯需要在某层楼长期开门停靠时,当前电梯等待队列可能出现的饥饿情况。后查找资料发现,目前电梯的控制技术已经进入了电梯群控的时代。陆续发展出了一批新方法,包括:基于专家系统的电梯群控方法、基于模糊逻辑的电梯群控方法、基于遗产算法的电梯群控方法、基于胜景网络的电梯群控方法和基于模糊神经网络的电梯群控方法。这些能更好的解决出现的问题

About

Elevator scheduling model implemented using JavaScript

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published