301
社区成员
发帖
与我相关
我的任务
分享
本次作业中我采用了Input->Manager->Elevator三级分派策略。Input线程负责接收请求,并将其放到waitQueue队列中,等待Manager线程将其分配到具体的Elevator线程。
这里的重点是RequestTable这个类。由于Input和Manager都要对其访问,是共享对象,且必须异步访问(否则可能出现某个请求被误删/分配两次的情况),所以必须是线程安全的。所以该类的所有方法我都设计成了同步方法。并且在set类的方法——即可以改变队列状态的方法中,都加上了notifyAll以唤醒其他线程。
本次的作业指定了每个乘客要被分配到的电梯,所以调度器的功能只是按照输入进行分配,真正需要设计的是电梯线程的运行策略。
我的Elevator类中的run方法很简洁:
@Override
public void run() {
while (true) {
Advice advice = getAdvice();
if (advice == Advice.OVER) {
return;
} else if (advice == Advice.MOVE) {
move();
} else if (advice == Advice.REVERSE) {
direction = -direction;
} else if (advice == Advice.WAIT) {
processingQueue.waitQueue();
} else if (advice == Advice.OPEN) {
openAndClose();
}
}
}
其中Adive是一个枚举类——表示了电梯运行的所有可能情况。getAdvice方法用来检索当前电梯的运行状态与请求队列里乘客的需求,返回当前的电梯运行策略:
public Advice getAdvice() {
if (checkEnter() || checkExit()) {
// 如果有人要上电梯或下电梯
return Advice.OPEN;
}
if (curNum != 0) {
// 如果电梯里有人
return Advice.MOVE;
}
if (requestTable.isEmpty()) {
// 当前请求对列中没人
if (requestTable.isEnd()) {
// 输入结束,所有请求处理完毕
return Advice.OVER;
}
// 输入尚未结束,电梯原地等待
return Advice.WAIT;
} else {
// 当前请求队列中有人
if (arePassengersAhead()) {
// 当前方向上有人在等
return Advice.MOVE;
} else {
// 否则,翻转方向
return Advice.REVERSE;
}
}
}
这个是往届学长们大多采用的look算法,采用这种getAdvice分层设计使得代码层次清晰,便于维护——在第7次作业中我的设计里只需要稍微修改一个checkEnter函数就可以。
基于我的设计里分层的考量,每个线程的结束条件都很清晰:

本次的架构沿用了上次的三级结构,具体实现上主要有以下两点新增:
本次没有指派电梯了,所以要由我们在调度器里决定分配给哪个电梯。我的策略就是纯随机,也没有去卷影子电梯,但最后结果上来看随机算法的性能相当地好,可谓是零投入,高回报啊。
在我设计的三层结构里,都要做适当的调整:
由于第一次作业中分层的设计,以及在电梯内部的自我分级设计,使得本次迭代任务比较轻松——只要把每一级自身的任务做相应的修改即可。
本次的架构与前面的作业中无异,类图也完全相同,主要就是内部实现的一些细节:
我将双轿厢电梯看成完全独立的两个电梯,当接受到这种重置请求时,我直接将原来的电梯线程结束,并new出来两个新电梯。
这里面就涉及几点要修改的地方:
本次就是在原来的架构上修改了上面这几点,总共修改不到50行,我认为这是架构层次清晰的功劳。
简单来说,我的设计将电梯简化到了极致,防止电梯相撞的方法是让其中一个电梯永远到不了换乘层,同时控制6部电梯不会出现永远到不了某一层的情况出现。

在这三次作业中,主要有如下几个bug:
本单元debug最大的障碍在于线程运行的异步性,导致无法准确获知各线程的运行状况。
最难的部分在于出现忙等CTLE时,由于我无法准确知道哪个线程忙等了,所以一开始调试时只能靠肉眼去看——但这样显然效率极低。
后来在助教的启发下,在每个线程循环的开始处打印一些信息,如果某时刻输出了一堆信息,就说明该线程在不断轮询。如此一来,调试就变得非常轻松了。
在三次作业里,我都采用了同步块和同步方法进行同步处理。
其中RequestTable类是一个同步类,其所有方法都是同步的,其中会修改自身属性的方法中还加上了notifyAll以唤醒相应的进程。
代码中其他要对等待队列进行操作的地方也应对其进行上锁——主要是在电梯线程中开门关门操作里,采用同步块设计。
在我的双轿厢电梯里,由于我要控制不能让所有电梯都重置成相同的状态,所以设计了一个静态的锁对象,每次进行双轿厢重置时,进入该锁对象的同步块进行修改,判断是将上面的电梯少重置一层还是下电梯少重置一层。
我的设计比较简单,没有过多的同步设计,只在必要的地方进行了加锁,所以出现bug的几率小了很多,完成作业时每太受到这部分内容的困扰。





可见,复杂度较高的类/方法主要集中在Manager和Input类里,因为我觉得这两个类的逻辑非常简单直接,所以就没有进行过度的封装。而在业务逻辑的核心Elevator类中,其代码规模是最大的,但是其复杂度反而降到很低。
第五次作业中,我的checker的思路是先检查电梯运行行为和合法性,再把输出逆向得到一个输入序列去检验其形式的合法性——这样能非常清晰地表现出具体哪里出了bug,但是第二次作业开始加入了重置,这一评测思路就行不通了,于是就转变成了将输入输出综合看待,以输出去模拟电梯的行为,最后和输入去比对。
核心代码如下:
bool Checker::check()
{
for (Log* log : *(parser->getLogStream())) {
//std::cout << log->toString() << std::endl;
Elevator* elevator = elevators[log->getElevatorId()];
switch (log->getType()) {
case Type::ARRIVE: {
Arrive* a = dynamic_cast<Arrive*>(log);
int floor = a->getCurFloor();
// 跳跃的楼层不合法
if (abs(floor - elevator->getCurFloor()) != 1) {
writeError(a->toString() + " You jump from " + std::to_string(elevator->getCurFloor()) + " to " + std::to_string(floor));
return false;
}
// 将电梯及其内的乘客楼层更新
elevator->setFloor(floor);
// 判断当前是否在重置
if (elevator->getAccept().first == true) {
int s = elevator->getStep();
if (s == 2) {
writeError(a->toString() + " You moved more than 2 times after accepting reset");
return false;
}
elevator->setStep(s + 1);
}
break;
}
case Type::OPEN: {
Open* o = dynamic_cast<Open*>(log);
// 判断两次开门不合法
if (elevator->getIsOpen()) {
writeError(o->toString() + " You opened doors for twice");
return false;
}
elevator->setIsOpen(true);
break;
}
case Type::CLOSE: {
Close* c = dynamic_cast<Close*>(log);
// 判断重复关门
if (!elevator->getIsOpen()) {
writeError(c->toString() + " You closed the door when iti's already closed");
return false;
}
elevator->setIsOpen(false);
break;
}
case Type::IN: {
In* i = dynamic_cast<In*>(log);
int floor = i->getCurFloor();
int passengerId = i->getPassengerId();
// 检查上车楼层的正确性
if (floor != elevator->getCurFloor()) {
writeError(i->toString() + " The passenger got in the elevator on " + std::to_string(elevator->getCurFloor()) + "-th floor when he's actually on" + std::to_string(floor));
return false;
}
// 检查是否有这个人
// 检查是否被分配过
if (persons[passengerId]->getTargetElevatorId() == 0) {
writeError(i->toString() + " The passenger hasn't been received");
return false;
}
// 检查是否分配正确
if (persons[passengerId]->getTargetElevatorId() != i->getElevatorId()) {
writeError(i->toString() + " The passenger gets on the wrong elevator");
return false;
}
// 检查是否已经上电梯
if (persons[passengerId]->getIsIn()) {
writeError(i->toString() + " The passenger has been in the elevator");
return false;
}
// 检查是否超载
if (elevator->getPassengersNum() == elevator->getCapacity()) {
writeError(i->toString() + " Overload!");
return false;
}
persons[passengerId]->setIsIn(true);
elevator->addPassenger(persons[passengerId]);
break;
}
case Type::OUT: {
Out* o = dynamic_cast<Out*>(log);
int floor = o->getCurFloor();
int passengerId = o->getPassengerId();
// 检查当前是否在电梯里
if (!elevator->hasPassenger(passengerId)) {
writeError(o->toString() + " The passenger isn't in the elevator");
return false;
}
// 检查下电梯的楼层是否正确
if (persons[passengerId]->getCurFloor() != floor) {
writeError(o->toString() + " The passenger gets off on " + std::to_string(floor) + "-th floor when he's actually on the " + std::to_string(persons[passengerId]->getCurFloor()) + "-th");
return false;
}
elevator->removePassenger(passengerId);
persons[passengerId]->setIsIn(false);
persons[passengerId]->setTargetElevatorId(0);
break;
}
case Type::RECEIVE: {
Receive* r = dynamic_cast<Receive*>(log);
int passengerId = r->getPassengerId();
Person* person = persons[passengerId];
// 检查是否已经被receive了
if (person->getTargetElevatorId() != 0) {
writeError(r->toString() + " The passenger has been received");
return false;
}
person->setReceived(r->getElevatorId());
break;
}
case Type::RESET_ACCEPT: {
Reset* r = dynamic_cast<Reset*>(log);
// 检查是否已经正在重置
if (elevator->getAccept().first == true) {
writeError(r->toString() + " The reset request has been accepted");
return false;
}
elevator->setAccept(true, r->getTime());
elevator->setReset(r);
break;
}
case Type::RESET_BEGIN: {
Reset* r = dynamic_cast<Reset*>(log);
// 检查是否Accept
if (elevator->getAccept().first == false) {
writeError(r->toString() + " The reset request begins before accepting");
return false;
}
// 检查是否已经begin了
if (elevator->getIsBegin()) {
writeError(r->toString() + " The reset request has begun");
return false;
}
// 检查begin的时候是否还有乘客
if (!elevator->getPassengers().empty()) {
writeError(r->toString() + " When the elevator reset started, there were still passengers inside");
return false;
}
elevator->setStep(0);
elevator->setIsBegin(true);
for (const auto& pair : persons) {
Person* p = pair.second;
if (p->getTargetElevatorId() == r->getElevatorId()) {
p->setTargetElevatorId(0);
}
}
break;
}
case Type::RESET_END: {
Reset* r = dynamic_cast<Reset*>(log);
double time = r->getTime();
// 检查是否begin
if (!elevator->getIsBegin()) {
writeError(r->toString() + " The reset request is end before beginning");
return false;
}
// 检查距离accept的时间戳是否小于5s
if (abs(time - elevator->getAccept().second) >= 5) {
writeError(r->toString() + " The gap between accept and begin is more than 5s");
return false;
}
elevator->setCapacity(elevator->getReset()->getCapacity());
elevator->setAccept(false, 0);
elevator->setIsBegin(false);
break;
}
}
}
bool Checker::finalCheck()
{
for (const auto& pair : persons) {
Person* person = pair.second;
if (person->getIsIn()) {
writeError("Person with the id of " + std::to_string(person->getId()) + " didn't get out of the elevator in the end");
return false;
}
if (!person->vertifyFloorDestination()) {
writeError("Person with the id of " + std::to_string(person->getId()) + "didn't reach the destinantion");
return false;
}
}
for (const auto& pair : elevators) {
// 检查是否关门
if (pair.second->getIsOpen()) {
writeError("The elevator with the id of " + std::to_string(pair.first) + " didn't close in the end");
return false;
}
// 检查是否没有乘客
if (!pair.second->getPassengers().empty()) {
for (const auto& p : pair.second->getPassengers()) {
writeError("The elevator with the id of " + std::to_string(pair.first) + " conatins the passeger with the id of " + std::to_string(p.first) + " in the end");
return false;
}
}
// 坚持是否有电梯还在reset
if (pair.second->getIsBegin()) {
writeError("The elevator with the id of " + std::to_string(pair.first) + " is still resetting in the end");
return false;
}
}
return true;
}
同时,由于多线程程序自动生成评测的数据不一定能覆盖到极端情况,所以设置了不同层次的接口,以满足多样化的评测需求:

大名鼎鼎的电梯月终于结束了,亲身体验下来只能感叹一句电梯果然名不虚传,这一个月的体验感属实拉满了。虽然过程异常艰难,但是完成这三次迭代作业后还是有不少收获的: