BUAA_OO_2022_第二单元总结

面向对象第二单元总结

一、对三次作业的分析

第一次作业

作业简介

A-E层每层单部电梯,每部电梯可以在1-10层运行,实现多线程实时调度电梯系统

难点分析

  1. 多线程:输入线程输入请求和电梯线程处理请求是并行发生的,需要处理线程读写冲突问题;
  2. 电梯运行策略:需要设计调度算法快速处理请求

解决方案

1. 线程安全问题

​ 本次作业每一楼座仅存在一部电梯,也就意味着能完成人请求的电梯是固定的,因此我们可以在输入线程简单根据请求所在楼座将请求分类保存到不同的的容器中,再令电梯从对应的容器中读取请求即可。为了解决多线程读写冲突问题,仅需要将容器加锁。PersonRequest在我的程序中的流动方向如下图所示:

​ 主线程实现了开5个电梯线程,并充当输入线程的功能。本次作业的需求较为简单,调度器融合在了主线程内,将输入请求按照楼座分配到Request Transfer中。Request Transfer类似于生产者-消费者模型中的传送带,为输入线程实现了inputRequest,setEnd方法,为电梯线程实现了outputRequest方法,通过对上述三个方法加synchronized同步锁解决了多线程读写冲突问题。

​ 轮询是多线程程序编写中最难发现的错误,在我的第一次作业实现中,当Request Transfer中的请求数量为0时,电梯线程调用outputRequest方法时会调用wait函数,使线程进入等待状态,当输入线程调用inputRequest或setEnd时会调用notifyAll函数,唤醒等待中的电梯线程,这样能够避免轮询的发生。

​ 同时,指导书中明确指出官方提供的输出包是线程不安全的,由于可能存在多个线程同时调用输出包,我们还需要为官方输出包套一层同步锁。

2.电梯调度算法

​ 本单元作业的基本调度算法为ALS算法,概括性的来说是根据时间顺序选取主请求,以主请求的方向为电梯的运行方向,在电梯沿该方向运动的过程中,尽可能捎带能捎带的请求。另外一种被大家广为采用的调度算法是LOOK算法,在LOOK算法中,没有主请求的概念,电梯沿一个方向运行到该方向上不在存在请求且电梯中没有未完成的请求时即改变运行方向,在运行过程中,也是尽可能捎带能捎带的请求。

​ 由于前两次作业的性能评价标准是以电梯的运行时间为标准,ALS调度策略中的主请求略显多余,没有必要以主请求的方向作为电梯运行方向。相比之下,LOOK策略能接到某一方向上最远的请求,沿一个方向查找的更为彻底,所以LOOK策略的理论效果更好。我在本地比较的结果是LOOK策略要比ALS策略快接近20%。

总体架构

1. UML类图

2. UML协作图

第二次作业

作业简介

同时存在横向电梯和纵向电梯,横向电梯可以沿顺时针或逆时针在A-E座横向运行,纵向电梯可以在1-10层纵向运行,人的请求在同座异层或同层异座。

迭代开发

​ 相比于上次作业,需要进行更改之处如下:

  1. 增加了横向电梯;
  2. 本次作业每一层,每一座的电梯数量不定,这意味着人上的电梯不是确定的,需要考虑多线程的冲突问题;
  3. 需要额外增加一个调度器将人的请求分配到恰当的电梯来处理,这里还需要考虑调度器向电梯的分配策略。

解决方案

1.线程安全问题

​ 在第二次作业中,我仍旧采用第一次作业类似于生产者-消费者模式,通过锁住传送带"Request Handler"来解决线程安全问题。这里的锁依旧选取了synchronized锁。在课上老师曾讲过轻量级的读写锁,但是由于我的同步方法几乎都是读写兼具的,所以采用这种锁对性能提升意义不大。从本质上来讲,第二次作业和第一次作业涉及到的可能引发线程安全问题的点并没有变化,无非是消费者电梯增加了。同时在这次作业实现过程中我增加了调度器,它将输入线程的请求进行分类,分配到不同的传送带"Request Handler"上,再由电梯线程从加了同步锁的Request Handler中按照设定好的分配策略读取并完成请求。PersonRequest在我的程序中的流动方向如下图所示:

​ 可以看出第一二次作业人请求的流动过程是相似的,第二次作业仅是添加了Scheduler将人的请求分配到恰当的Request Handler;多个电梯线程从一个Request Handler中读取请求。

​ 在Request Handler类中,为输入线程实现了inputRequest、setEnd方法,为电梯线程实现了arrangeRequests、isEnd方法,并为上述四种方法添加了synchronized同步锁,保证Request Handler对象仅能够被一个线程得到。同时当请求数量为0时,电梯线程调用isEnd方法时会调用wait函数,等待输入线程调用inputRequest或setEnd方法中的notifyAll方法的唤醒,这也就避免了轮询问题。

​ 总的来说,尽管第二单元的主题是多线程,前两次作业通过简单的生产者-消费者模式,用同步锁锁住传送带中的输入输出方法,在消费者从传送带中得不到请求时调用wait,生产者向传送带中放入请求时调用notifyAll将消费者唤醒就可以解决涉及到的多线程读写冲突和轮询问题,所以我的大部分精力投入到了设计电梯调度算法中。(当然这种安逸只维持到了第二次作业,第三次作业需要对线程安全问题和轮询问题进行深入思考,这一点会在下文中做详细分析)

2.电梯调度问题

​ 电梯调度的迷惑之处在于对于不同的请求序列和请求投入时间,不同电梯调度算法的表现是不同的。所以性能成绩是取决于强测输入的指令序列的,是带有随机性和不确定性的,我们需要寻找的是尽可能适应大部分情境的电梯调度策略。

​ 借助第一次作业的经验,我的电梯基本调度算法是LOOK算法。在第二次作业中,由于存在多个电梯竞争一个人请求的情况,我分别实现了自己设计的一个分配策略和自由竞争策略。

​ 我设计的分配策略是将人的请求分为以下四类:

  1. 起点在电梯运行方向上且运行方向和电梯运动方向相同
  2. 起点在电梯运行方向上但运行方向和电梯运动方向相反
  3. 起点在电梯下次运行方向上且运行方向和电梯下次运动方向相同
  4. 起点在电梯下次运行方向上但运行方向和电梯下次运动方向相反

调度器优先将1类请求分配给电梯,当没有1类请求时,为电梯分配2类请求。当1、2类请求都不存在时,电梯将不获得请求(在完成已有请求后)改变方向。改变方向后,3类请求变成了1类请求,4类请求变成了2类请求,仍然按照上述规则将这两种请求依次分配给电梯。这种调度方式的好处在于不存在两个电梯同时前去接一个请求。处理如下请求时6号电梯和1号电梯分别去接1、2号请求。

[0.0]ADD-building-6-A
[1.0]1-FROM-A-5-TO-A-3
[2.1]2-FROM-A-2-TO-A-9

自由竞争策略的想法是在电梯到达每层时,彼此竞争地获取等待队列中在当前位置处的请求,这种调度策略产生的效果是随机的。就像是只有用魔法才能打败魔法,用随机的调度策略去处理随机的请求效率异常的好。当然,这种调度方式是不符合实际要求的,在处理上述请求时,A座的1号和6号电梯会同时前去5楼接人,但是仅有一部电梯能够接到人,这种调度在是不会应用到实际场景中的。

总体架构

1. UML类图

2. UML协作图

第三次作业

作业简介

电梯种类为纵向电梯和能够到达特定楼座的横向电梯,可以定制电梯容量和运行速度,人的请求可以是不同楼座且不同楼层

迭代开发

  1. 横向电梯不一定能到达所有楼层;
  2. 人的请求可能需要多部电梯满足;
  3. 由以上两点引发的线程安全和轮询问题

解决方案

​ 本次作业相对于第二次作业仅需要在处理人请求时遍历已有电梯,将人的请求分成至多三段即可,为此,我额外建立了一个MyPersonRequest类,这个类能得到请求输入时当前拥有的电梯情况,根据当前电梯情况按照一定的策略将人的请求分成一段两段或三段,此后,人便按照这个分段进行运动。Person Request在我的程序中的流动方向如下图所示:

​ 大致架构和第二次作业类似:输入线程将所有请求输入到分配器,分配器按照人请求在当前一段是在同一楼层还是同一楼座,分配到恰当的Request Handler,该楼层或者楼座的电梯线程从Request Handler中拿到请求进行处理。电梯完成人的该段请求后,检查人是否已经到达终点。若没有到达终点,则获取人的下一段运行方向,重新将人的请求输入到分配器进行分配。

​ 本次作业需要特别关注的地方有以下两点:

  1. 分配器会受到电梯线程和输入线程多个线程的同时操作,故在分配器处需要加同步锁。
  2. 横线电梯到不了所有楼座,所以在wait时要判断它能接到的请求是不是空,而非总共的请求是不是空。由于轮询错误的难以察觉性,这也是互测hack同学的好思路(但是我房间的同学似乎都解决了这个问题

电梯架构和运行调度策略沿用了第二次作业,强测结束后我才发现第三次作业的性能评价标准变了,还要加上人总共的等待的时间。而我为了追求了冰冷的运行时间,没有优先处理最先到达的请求,设计的电梯缺少了人文关怀,导致没有取得最理想的强测分数

总体架构

1. UML类图

2. UML协作图

二、bug分析和hack策略

自己程序的bug分析

​ 三次作业中测强测都顺利通过(感谢第一次作业强测没有重测),第一次作业的互测被hack了两次,原因是对多线程的随机性理解不到位。当队列中不存在请求时,电梯线程调用wait方法进入等待状态。此时主线程有可以通过setEnd或inputRequest唤醒电梯线程。这里我误认为主线程调用setEnd或inputRequest中的notifyAll后必然由电梯线程拿到锁,因而在电梯线程被唤醒后仅仅判断了end而没判断请求队列是否为空。其实notifyAll只是唤醒了电梯线程,电梯线程还是要和主线程竞争锁的归属,所以可能会出现主线程inputRequest->主线程setEnd->电梯线程isEnd的情景,在电梯线程被唤醒后还需要对请求队列是否为空加以判断。其余两次互测均为发现bug。

​ 在课下,我编写了自己的评测机,实现了自动生成数据,使用正则表达式解析输入和输出,并对每个电梯进行运行逻辑检查,第三次作业的评测机代码如下:

Show Code
# python 3.8
import os
import random
import re
from fractions import Fraction

buildings = ['A', 'B', 'C', 'D', 'E']


class Person:
    def __init__(self, arrive_time, id, start_floor, end_floor, start_building, end_building):
        self.arrive_time = Fraction(arrive_time)
        self.id = id
        self.start_floor = int(start_floor)
        self.end_floor = int(end_floor)
        self.start_building = ord(start_building) - ord('A')
        self.end_building = ord(end_building) - ord('A')
        self.current_floor = int(start_floor)
        self.current_building = ord(start_building) - ord('A')
        self.current_time = Fraction(arrive_time)

    def check_end(self):
        return self.current_building == self.end_building and self.current_floor == self.end_floor


class Elevator:
    def __init__(self, id, capacity, speed, stop_place, type, current_floor, current_building, time):
        self.id = id
        self.capacity = Fraction(capacity)
        self.wait_time = Fraction(speed)
        self.stop_place = []
        self.ops = []
        self.type = type
        self.current_floor = int(current_floor)
        self.current_building = ord(current_building) - ord('A')
        self.time = Fraction(time)
        self.is_open = False
        self.person_on_elevator = dict()
        index = 0
        stop_place = int(stop_place)
        while stop_place != 0:
            if stop_place & 1 == 1:
                self.stop_place.append(index)
            index += 1
            stop_place = stop_place >> 1

    def add_op(self, op):
        self.ops.append(op)

    def get_ops(self):
        return self.ops

    def check_arrive(self, arrive_time, arrive_floor, arrive_building):
        arrive_time = Fraction(arrive_time)
        arrive_floor = int(arrive_floor)
        arrive_building = ord(arrive_building) - ord('A')
        if not arrive_time - self.time >= self.wait_time:
            raise Exception(
                "电梯 elevator: {} 运行时间错误: arrive_time = {}, former_time = {}".format(self.id, arrive_time, self.time))
        if self.type == "building":
            if not (self.current_building == arrive_building and abs(self.current_floor - arrive_floor) == 1):
                raise Exception(
                    "电梯运行楼层或楼座错误 elevator: {}: current_floor = {}, former_floor = {}, current_building = {},"
                    " former_building = {}".format(self.id, arrive_floor, self.current_floor, arrive_building,
                                                   self.current_building))
        else:
            if not (self.current_floor == arrive_floor and (
                    abs(self.current_building - arrive_building) == 1 or self.current_building * 10 + arrive_building in
                    [4, 40])):
                raise Exception(
                    "电梯运行楼层或楼座错误 elevator: {}: current_floor = {}, former_floor = {}, current_building = {},"
                    " former_building = {}".format(self.id, arrive_floor, self.current_floor, arrive_building,
                                                   self.current_building))
        self.time = arrive_time
        self.current_building = arrive_building
        self.current_floor = arrive_floor

    def check_open(self, open_time, open_floor, open_building):
        open_time = Fraction(open_time)
        open_floor = int(open_floor)
        open_building = ord(open_building) - ord('A')
        if open_time < self.time:
            raise Exception(
                "电梯开门时间错误 elevator: {}: arrive_time = {}, open_time = {}".format(self.id, self.time, open_time))
        if self.current_floor != open_floor or self.current_building != open_building:
            raise Exception("楼座或楼层错误 elevator: {}: time = {}".format(self.id, open_time))
        if self.is_open:
            raise Exception("重复开门 elevator: {}: time = {}".format(self.id, open_time))
        self.time = open_time
        self.is_open = True

    def check_close(self, close_time, close_floor, close_building):
        close_time = Fraction(close_time)
        close_floor = int(close_floor)
        close_building = ord(close_building) - ord('A')
        if close_time < self.time:
            raise Exception(
                "电梯关门时间错误 elevator: {}: arrive_time = {}, close_time = {}".format(self.id, self.time, close_time))
        if self.current_floor != close_floor or self.current_building != close_building:
            raise Exception("楼座或楼层错误 elevator: {}: time = {}".format(self.id, close_time))
        if not self.is_open:
            raise Exception("重复关门 elevator: {}: time = {}".format(self.id, close_time))
        self.time = close_time
        self.is_open = False

    def check_in(self, in_time, in_floor, in_building, in_person: Person):
        in_time = Fraction(in_time)
        in_floor = int(in_floor)
        in_building = ord(in_building) - ord('A')
        if not self.is_open:
            raise Exception("电梯门关,人不能进入电梯 elevator: {}: person {}".format(self.id, in_person.id))
        if in_building != self.current_building or in_floor != self.current_floor:
            raise Exception("楼座或楼层错误 elevator: {}".format(self.id))
        if in_floor != in_person.current_floor or in_building != in_person.current_building:
            raise Exception("人不在该层or座,不能进入电梯 elevator: {} person: {}: person_floor = {}, elevator_floor = {},"
                            "person_building = {}, elevator_building = {}"
                            .format(self.id, in_person.id, in_person.current_floor, self.current_floor,
                                    in_person.current_building, self.current_building))
        if in_time < in_person.arrive_time:
            raise Exception("人进入电梯时间错误 elevator: {}: arrive_time = {}, person_arrive_time = {}"
                            .format(self.id, in_time, in_person.arrive_time))
        self.person_on_elevator[in_person.id] = in_person
        if len(self.person_on_elevator) > self.capacity:
            raise Exception("超载 elevator: {}: after person {} get in".format(self.id, in_person.id))
        in_person.arrive_time = in_time
        self.time = in_time

    def check_out(self, out_time, out_floor, out_building, out_person_id: str):
        out_time = Fraction(out_time)
        out_floor = int(out_floor)
        out_building = ord(out_building) - ord('A')
        if not self.is_open:
            raise Exception("电梯门关,人不能走出电梯 elevator: {}: person {}".format(self.id, out_person_id))
        if out_building != self.current_building or out_floor != self.current_floor:
            raise Exception("楼座或楼层错误 elevator: {}".format(self.id))
        if out_person_id not in self.person_on_elevator.keys():
            raise Exception("人不在电梯上 elevator: {}: person_id = {}".format(self.id, out_person_id))
        out_person = self.person_on_elevator[out_person_id]
        self.person_on_elevator.pop(out_person_id)
        if out_time < out_person.arrive_time:
            raise Exception("人走出电梯时间错误 elevator: {}: arrive_time = {}, person_arrive_time = {}"
                            .format(self.id, out_time, out_person.arrive_time))
        out_person.current_floor = self.current_floor
        out_person.current_building = self.current_building
        out_person.current_time = out_time
        self.time = out_time
        if out_person.check_end():
            return None
        else:
            return out_person

    def check_finished(self):
        if len(self.person_on_elevator) != 0:
            for person_id in self.person_on_elevator.keys():
                print(person_id)
            raise Exception("人在电梯中未出来 elevator: {}".format(self.id))
        if self.is_open:
            raise Exception("电梯未关门 elevator: {}".format(self.id))


def create(num):
    personIdPool = set()
    elevatorIdPool = set()
    for i in range(5):
        elevatorIdPool.add(i)
    elevators = [1] * 5 + [0] * 10
    current_time = 1.0
    file = open("stdin.txt", "w")

    for i in range(num):
        current_time = current_time + random.randint(0, 10) / 100
        building = random.choice(buildings)
        coin = random.randint(1, 10)
        if coin == 1:
            if sum(elevators) >= 20:
                continue
            flag = False
            for elevator in elevators:
                if elevator != 3:
                    flag = True
                    break
            if not flag:
                continue
            v1 = [4, 6, 8]
            v2 = [0.2, 0.4, 0.6]
            capacity = random.choice(v1)
            speed = random.choice(v2)
            reach_place = random.randint(3, 31)
            while reach_place in [4, 8, 16]:
                reach_place = random.randint(3, 31)
            elevatorId = random.randint(1, 1000000)
            while elevatorId in elevatorIdPool:
                elevatorId = random.randint(1, 1000000)
            elevatorIdPool.add(elevatorId)
            elevatorType = random.randint(0, 14)
            while elevators[elevatorType] == 3:
                elevatorType = random.randint(0, 14)
            elevators[elevatorType] += 1
            if elevatorType < 5:
                request = "[{:.1f}]ADD-building-{}-{}-{}-{}\n".format(current_time, elevatorId, building, capacity, speed)
            else:
                request = "[{:.1f}]ADD-floor-{}-{}-{}-{}-{}\n".format(current_time, elevatorId, elevatorType - 4, capacity,
                                                                  speed, reach_place)
        else:
            personId = random.randint(1, 1000000)
            while personId in personIdPool:
                personId = random.randint(1, 1000000)
            personIdPool.add(personId)
            start_floor = random.randint(1, 10)
            end_floor = random.randint(1, 10)
            start_building = random.choice(buildings)
            end_building = random.choice(buildings)
            while start_floor == end_floor and start_building == end_building:
                end_floor = random.randint(1, 10)
                end_building = random.choice(buildings)
            request = "[{:.1f}]{}-FROM-{}-{}-TO-{}-{}\n".format(current_time, personId, start_building, start_floor,
                                                            end_building, end_floor)
        file.write(request)
    file.close()


def get_person_elevator(filename):
    file = open(filename, "r")
    lineStdin = file.read().split("\n")[:-1]
    file.close()
    person_dict = dict()
    elevator_dict = dict()

    for i in range(1, 6):
        elevator_dict[str(i)] = Elevator(str(i), 8, 0.6, 0, "building", 1, chr(ord('A') + i - 1), 0)
    elevator_dict['6'] = Elevator('6', 8, 0.6, 31, "floor", 1, 'A', 0)

    for line in lineStdin:
        person_pattern = "\[(?P<time>.*)](?P<id>\\d+)-FROM-(?P<start_building>[A-E]+)-(?P<start_floor>(10|[1-9]))-TO-(?P<end_building>[A-E]+)-(?P<end_floor>(10|[1-9]))"
        building_elevator_pattern = "\[(?P<time>.*)]ADD-building-(?P<elevator_id>\\d+)-(?P<building>[A-E]+)-(?P<capacity>\\d+)-(?P<speed>.*)"
        floor_elevator_pattern = "\[(?P<time>.*)]ADD-floor-(?P<elevator_id>\\d+)-(?P<floor>(10|[1-9]))-(?P<capacity>\\d+)-(?P<speed>.*)-(?P<stop_place>\\d+)"
        pattern = re.compile(person_pattern)
        match = re.match(pattern, line)
        if match:
            person = Person(match.group("time"), match.group("id"), match.group("start_floor"),
                            match.group("end_floor"),
                            match.group("start_building"), match.group("end_building"))
            person_dict[person.id] = person

        pattern = re.compile(building_elevator_pattern)
        match = re.match(pattern, line)
        if match:
            building_elevator = Elevator(match.group("elevator_id"), match.group("capacity"),
                                         match.group("speed"), 0, "building", 1, match.group("building"),
                                         match.group("time"))
            elevator_dict[building_elevator.id] = building_elevator

        pattern = re.compile(floor_elevator_pattern)
        match = re.match(pattern, line)
        if match:
            floor_elevator = Elevator(match.group("elevator_id"), match.group("capacity"), match.group("speed"),
                                      match.group("stop_place"), "floor", match.group("floor"), "A",
                                      match.group("time"))
            elevator_dict[floor_elevator.id] = floor_elevator

    return person_dict, elevator_dict


def check(filename):
    person_dict, elevator_dict = get_person_elevator("stdin.txt")

    file = open(filename, "r")
    lines = file.read().split('\n')[:-1]
    file.close()

    former_time = Fraction(lines[0][1:lines[0].index(']')])
    for line in lines:
        current_time = Fraction(line[1:line.index(']')])
        if not current_time >= former_time:
            raise Exception("时间戳非递增,at time {}".format(current_time))
        former_time = current_time
    print("时间戳检查完成")
    time = float(lines[-1][lines[-1].index("[") + 1:lines[-1].index("]")])

    for line in lines:
        arrive_pattern = "\[(?P<time>.*)]ARRIVE-(?P<building>[A-E]+)-(?P<floor>(10|[1-9]))-(?P<elevator_id>\\d+)"
        open_pattern = "\[(?P<time>.*)]OPEN-(?P<building>[A-E]+)-(?P<floor>(10|[1-9]))-(?P<elevator_id>\\d+)"
        close_pattern = "\[(?P<time>.*)]CLOSE-(?P<building>[A-E]+)-(?P<floor>(10|[1-9]))-(?P<elevator_id>\\d+)"
        in_pattern = "\[(?P<time>.*)]IN-(?P<person_id>\\d+)-(?P<building>[A-E]+)-(?P<floor>(10|[1-9]))-(?P<elevator_id>\\d+)"
        out_pattern = "\[(?P<time>.*)]OUT-(?P<person_id>\\d+)-(?P<building>[A-E]+)-(?P<floor>(10|[1-9]))-(?P<elevator_id>\\d+)"

        pattern = re.compile(arrive_pattern)
        match = re.match(pattern, line)
        if match:
            if match.group("elevator_id") not in elevator_dict.keys():
                raise Exception("电梯(ID: {})不存在".format(match.group("elevator_id")))
            elevator = elevator_dict[match.group("elevator_id")]
            elevator.check_arrive(match.group("time"), match.group("floor"), match.group("building"))

        pattern = re.compile(open_pattern)
        match = re.match(pattern, line)
        if match:
            if match.group("elevator_id") not in elevator_dict.keys():
                raise Exception("电梯(ID: {})不存在".format(match.group("elevator_id")))
            elevator = elevator_dict[match.group("elevator_id")]
            elevator.check_open(match.group("time"), match.group("floor"), match.group("building"))

        pattern = re.compile(close_pattern)
        match = re.match(pattern, line)
        if match:
            if match.group("elevator_id") not in elevator_dict.keys():
                raise Exception("电梯(ID: {})不存在".format(match.group("elevator_id")))
            elevator = elevator_dict[match.group("elevator_id")]
            elevator.check_close(match.group("time"), match.group("floor"), match.group("building"))

        pattern = re.compile(in_pattern)
        match = re.match(pattern, line)
        if match:
            if match.group("elevator_id") not in elevator_dict.keys():
                raise Exception("电梯(ID: {})不存在".format(match.group("elevator_id")))
            elevator = elevator_dict[match.group("elevator_id")]
            in_person_id = match.group("person_id")
            if in_person_id not in person_dict.keys():
                raise Exception("person ID({}) not Found".format(in_person_id))
            in_person = person_dict.pop(in_person_id)
            elevator.check_in(match.group("time"), match.group("floor"), match.group("building"), in_person)

        pattern = re.compile(out_pattern)
        match = re.match(pattern, line)
        if match:
            if match.group("elevator_id") not in elevator_dict.keys():
                raise Exception("电梯(ID: {})不存在".format(match.group("elevator_id")))
            elevator = elevator_dict[match.group("elevator_id")]
            out_person = elevator.check_out(match.group("time"), match.group("floor"), match.group("building"),
                                            match.group("person_id"))
            if out_person is not None:
                person_dict[match.group("person_id")] = out_person
    for elevator in elevator_dict.values():
        elevator.check_finished()
    if len(person_dict) != 0:
        for person_id in person_dict.keys():
            print(person_id)
        raise Exception("请求未完成")
    return time


if __name__ == "__main__":
    for i in range(1000):
        print("checking test point No.{}".format(i + 1))
        # refresh = input("refresh(y or n): ")
        refresh = "y"
        if refresh == 'y':
            # num = int(input("指令条数:"))
            num = 500
            create(num)
            print("test point create succeed!")
        else:
            print("using testpoint before.")

        os.system("datainput_student_win64.exe | java -jar Archer.jar > Archer.txt")
        print("Archer get!")
        os.system("datainput_student_win64.exe | java -jar Assassin.jar > Assassin.txt")
        print("Assassin get!")
        os.system("datainput_student_win64.exe | java -jar Berserker.jar > Berserker.txt")
        print("Berserker get!")
        os.system("datainput_student_win64.exe | java -jar Caster.jar > Caster.txt")
        print("Caster get!")
        os.system("datainput_student_win64.exe | java -jar Lancer.jar > Lancer.txt")
        print("Lancer get!")
        os.system("datainput_student_win64.exe | java -jar Rider.jar > Rider.txt")
        print("Rider get!")
        os.system("datainput_student_win64.exe | java -jar Saber.jar > Saber.txt")
        print("Saber get!")

        print("logical check .......")
        check("Archer.txt")
        check("Assassin.txt")
        check("Berserker.txt")
        check("Caster.txt")
        check("Lancer.txt")
        check("Rider.txt")
        check("Saber.txt")
        print("logical check succeed")

        print()

​ 在编写代码的过程中,轮询是一个比较隐匿的bug,可以通过评测机中的CPU运行时间判断或者用Java Profile对各个线程消耗CPU资源情况进行分析。我的一个解决思路是在可能产生轮询的地方添加输出语句进行判断(多线程程序调试打印信息确实要比单步单线程调试方便得多),如果程序在短时间内输出大量结果,则很有可能这里出现了轮询。

互测hack策略

​ 本单元作业是实时动态输入多线程电梯系统,和第一单元不同之处就在于实时和多线程。在互测阶段,我主要是用写的自动化程序进行测试。对一些我在编写代码时分析出的容易出现的bug也进行了针对性的测试,这些bug有以下几点:

  1. 第一次作业未对官方输出包加锁
  2. 三次作业中均可能出现的不退出程序问题,比如第三次作业一个同学在最后一条指令是添加电梯指令时就不会退出程序
  3. 轮询问题:主要是针对第三次作业横向电梯存在无法到达的楼座这一问题。有的同学的设计可能仅判断了请求队列是否为空而忽略了判断电梯是否能到达该座,导致电梯一直不wait产生轮询。

三、心得体会

​ 第二单元是多线程专题,由于之前从未接触过多线程,在本单元中我的收获是巨大的。从单元刚开始时不知道哪里加锁、哪里要wait、哪里要notifyAll,到单元结束时自己独立进行思考和分析,仅在有必要的关键方法加锁,让同步对象尽可能少的影响多线程程序的运行,我对多线程编码理解也是越来越透彻。但是仔细想想,我练习的多线程设计模式和锁的方法还比较少,三次作业仅是应用了生产者消费者模式和单例模式,没有动手时间课上讲的其它模式,自己还是有些固步自封了。

​ 电梯调度策略也是非常有趣的,常见的LOOK算法、ALS算法、分配请求、随机竞争我都尝试实现了一下。这几种调度算法的效率对比让我深深的体会到了大道至简的道理,LOOK策略和随机竞争策略都属于既原理简单好实现又效率高的策略。

​ 在这个单元中还有一件比较有成就感的事是自己编写了一个自动化小测试程序,在课下debug和互测时发挥了很大的作用,同时也给我省下很多时间,互测时运行起来程序就去做其它事情了。相比于第一单元,第二单元的多线程实时交互电梯系统更加有趣,也让我更愿意将时间投入在上面探索更好的设计模式和调度策略,让自己的电梯运行的更快更稳。本单元是一个非常有意思非常吸引我的一个单元。

posted @ 2022-04-28 09:15  吃月亮的人  阅读(38)  评论(2编辑  收藏  举报