电梯使用起来非常的方便,只要按个按钮就可以去到想去的楼层。那么电梯是如何工作的,它内部的算法是如何起作用的呢?我们又如何编写电梯的程序呢?在写代码之前,我们先要知道电梯的工作原理。
如上图所示,电梯的工作是基于多线程的。它由消息线程,状态机线程,以及开关门子线程共同协调完成动作。消息线程产生消息队列(MsgQueue),普通消息(非开门关门消息)按产生的时间顺序依次进入消息队列,状态机线程按顺序不断的读取消息队列里的消息,每读取一条消息后,执行消息改变自身状态,然后读取下一条消息。而如果消息线程中产生了开、关门的消息(ExitFlag),那么此时先判断可行性(比如电梯在运行过程中无法开关门),若可行,则立即中断其它动作,执行此消息。
首先我们给电梯定义几个状态,然后定义各种消息,定义楼层的高度,最后写出状态改变的具体逻辑。如图,我们给消息和状态都编码。
TOP = 6
BOTTOM = 1
STATE = {0:"门停开着",1:"门停关着",2:"电梯上升",3:"电梯下降"}
DIR = {0:"向下", 1:"向上"}
# 消息编码:0 00关门 ,01 开门, 02 有人进
# 1 11去1L ,12去2L,13去三楼,14去4楼,15去五楼,16去六楼
# 2 21一楼有人按上,22二楼有人按上,23三楼有人按上,24四楼有人按上,25五楼有人按上
# 3 32二楼有人按下,33三楼有人按下,34四楼有人按下,35五楼有人按下,36六楼有人按下
状态机就是状态转移图。举个最简单的例子。人有三个状态健康,感冒,康复中。触发的条件有淋雨(t1),吃药(t2),打针(t3),休息(t4)。所以状态机就是健康-(t4)->健康;健康-(t1)->感冒;感冒-(t3)->健康;感冒-(t2)->康复中;康复中-(t4)->健康,等等。就是这样状态在不同的条件下跳转到自己或不同状态的图。现在,我们来看看电梯的状态机:
真正生活中的电梯,它的消息来自于使用者按按钮,然后通过传感器设备采集信号传给中心处理系统,我们这里直接忽略硬件部分,用一些随机数字产生消息代码。首先定义一个消息的class,它包括消息类型type和消息值value。
class Msg:
def __init__(self,type,value):
self.type = type
self.value = value
exitFlag = []
MsgQueue = []
def Msgfunction():
global MsgQueue,exitFlag
for i in range(4):
type = random.randint(0, 3)
value = 0
if type == 0:
value = random.randint(0, 2)
if lock.acquire():
exitFlag.append(1)
lock.release()
if type == 2:
value = random.randint(BOTTOM,TOP-1)
if type == 3:
value = random.randint(BOTTOM+1,TOP)
if type == 1:
value = random.randint(BOTTOM, TOP)
TIME = random.randint(1, 8)
m = Msg(type, value)
if lock.acquire():
MsgQueue.append(m)
print("产生消息编码:"+ str([m.type,m.value]))
lock.release()
time.sleep(TIME)
注意到该函数最下面有一个锁死语句,即消息队列在增加消息时,锁死了消息队列,不让其它线程访问。这里主要是因为,状态机线程在完成一个消息动作后,会将已完成的消息弹出消息队列,而这个过程与上面添加消息是互斥的,因为他们都需要访问资源MsgQueue。
相比上面的两个线程,这个线程相对难一点。主要是考虑到实时性。首先,要考虑两个问题。什么时候可以开关门,不能开关门要怎样。电梯只有处在状态“停门关着”才能开门,而只有处在状态 “门停开着”才能关门。如果此时电梯处于其它状态,则直接忽视掉开关门命令即可。但是又有问题了,通常我们会碰到这样的问题,比如门正在关闭中,电梯里面有人按了开门按钮,或者外面有人进来,那么此时电梯的状态要为“门停关着”,也就是说在执行动作关门前,提前将状态转换过去。
此外,开关门线程只在调用开关门函数时起作用,电梯下一次状态的转换必须等待开关门动作完成,而开关门线程运行时,还可以重启开关门线程,譬如“有人进”。
def closeThread():
global exitFlag
counter = 3
print("正在关门...")
while counter:
if exitFlag != [1]:
print("关门终止")
break
time.sleep(1)
counter -=1
if counter == 0:
print("已关门")
def closedoor():
t = threading.Thread(target=closeThread)
t.start()
t.join()
def openThread():
global exitFlag
counter = 3
print("正在开门...")
while counter:
if exitFlag != [1]:
print("开门终止")
break
time.sleep(1)
counter -=1
if counter == 0:
print("已开门")
def opendoor():
t = threading.Thread(target=openThread)
t.start()
t.join()
主线程就简单了,只需要负责启动状态机线程和消息队列线程。运行主线程,电梯就开始运作啦。
if __name__ == "__main__":
thread1 = threading.Thread(target=Msgfunction)
thread2 = threading.Thread(target=statemachine)
thread1.start()
thread2.start()
好了,源码放在后面了。
import time
import threading
import random
TOP = 6
BOTTOM = 1
STATE = {0:"门停开着",1:"门停关着",2:"电梯上升",3:"电梯下降"}
DIR = {0:"向下", 1:"向上"}
# 消息编码:0 00关门 ,01 开门, 02 有人进
# 1 11去1L ,12去2L,13去三楼,14去4楼,15去五楼,16去六楼
# 2 21一楼有人按上,22二楼有人按上,23三楼有人按上,24四楼有人按上,25五楼有人按上
# 3 32二楼有人按下,33三楼有人按下,34四楼有人按下,35五楼有人按下,36六楼有人按下
lock = threading.Lock()
class Msg:
def __init__(self,type,value):
self.type = type
self.value = value
exitFlag = []
MsgQueue = []
def Msgfunction():
global MsgQueue,exitFlag
for i in range(4):
type = random.randint(0, 3)
value = 0
if type == 0:
value = random.randint(0, 2)
if lock.acquire():
exitFlag.append(1)
lock.release()
if type == 2:
value = random.randint(BOTTOM,TOP-1)
if type == 3:
value = random.randint(BOTTOM+1,TOP)
if type == 1:
value = random.randint(BOTTOM, TOP)
TIME = random.randint(1, 8)
m = Msg(type, value)
if lock.acquire():
MsgQueue.append(m)
print("产生消息编码:"+ str([m.type,m.value]))
lock.release()
time.sleep(TIME)
def closed(state, cur, d):
if d == 1:
if startup(cur,d):
state = 2
else:
d = 0
if startup(cur,d):
state = 3
else:
return state,cur,d
else:
if startup(cur,d):
state = 3
else:
d = 1
if startup(cur,d):
state = 2
else:
return state,cur,d
return state,cur,d
def up(state,cur,d):
while True:
state = state
if stop(cur,d):
state = 1
print("当前状态:%s,当前楼层:%d,运行方向:%s" % (STATE[state], cur, DIR[d]))
break
cur +=1
print("正在前往第%d层..." % cur)
time.sleep(2)
return state,cur,d
def down(state,cur,d):
while True:
state = state
if stop(cur,d):
state = 1
print("当前状态:%s,当前楼层:%d,运行方向:%s" % (STATE[state], cur, DIR[d]))
break
cur -=1
print("正在前往第%d层..." % cur)
time.sleep(2)
#print("当前状态:%s,当前楼层:%d,运行方向:%d" % (STATE[state], cur, d))
#time.sleep(10)
return state,cur,d
def startup(cur,d):
global MsgQueue
tmp = False
if d == 1:
for m in MsgQueue:
if m.type == 1 and m.value > cur:
tmp = True
if m.type == 2 and m.value > cur:
tmp = True
if m.type == 3 and m.value > cur:
tmp = True
if d == 0:
for m in MsgQueue:
if m.type == 1 and m.value < cur:
tmp = True
if m.type == 2 and m.value < cur:
tmp = True
if m.type == 3 and m.value < cur:
tmp = True
return tmp
def stop(cur,d):
global MsgQueue
tmp = False
if d == 1:
if cur == TOP:
tmp = True
tmplist = MsgQueue[:]
for m in MsgQueue:
if m.type == 1 and m.value == cur:
tmp = True
tmplist.remove(m)
if m.type == 2 and m.value == cur:
tmp = True
tmplist.remove(m)
MsgQueue = tmplist[:]
if d == 0:
if cur == BOTTOM:
tmp = True
tmplist = MsgQueue[:]
for m in MsgQueue:
if m.type == 1 and m.value == cur:
tmp = True
tmplist.remove(m)
if m.type == 3 and m.value == cur:
tmp = True
tmplist.remove(m)
MsgQueue = tmplist[:]
return tmp
def closeThread():
global exitFlag
counter = 3
print("正在关门...")
while counter:
if exitFlag != [1]:
print("关门终止")
break
time.sleep(1)
counter -=1
if counter == 0:
print("已关门")
def closedoor():
t = threading.Thread(target=closeThread)
t.start()
t.join()
def openThread():
global exitFlag
counter = 3
print("正在开门...")
while counter:
if exitFlag != [1]:
print("开门终止")
break
time.sleep(1)
counter -=1
if counter == 0:
print("已开门")
def opendoor():
t = threading.Thread(target=openThread)
t.start()
t.join()
def statemachine():
global MsgQueue,exitFlag
state = 0
cur = 1
d = 0
while True:
time.sleep(0.3)
print("当前状态:%s,当前楼层:%d,运行方向:%s" % (STATE[state], cur, DIR[d]))
if MsgQueue == [] and state == 1:
continue
if exitFlag != []:
tmplist = MsgQueue[:]
for m in tmplist:
if m.type == 0 and m.value == 0:
if state == 0:
state = 1
closedoor()
exitFlag.pop(0)
tmplist.remove(m)
if m.type == 0 and m.value == 1:
if state == 1 or state == 0:
state = 0
opendoor()
exitFlag.pop(0)
tmplist.remove(m)
if m.type == 0 and m.value == 2:
if state == 1:
state = 0
opendoor()
exitFlag.pop(0)
tmplist.remove(m)
MsgQueue = tmplist[:]
continue
if state == 0:
counter = 4
while counter:
if exitFlag != []:
print("超时终止")
break
time.sleep(1)
counter -= 1
if counter == 0:
print("超时")
exitFlag.append(1)
closedoor()
exitFlag.pop(0)
state = 1
continue
if state == 1:
if MsgQueue == []:
continue
state, cur, d = closed(state, cur, d)
continue
if state == 2:
if MsgQueue == []:
continue
state,cur,d = up(state, cur, d)
if state == 1:
exitFlag.append(1)
opendoor()
exitFlag.pop(0)
state = 0
continue
if state == 3:
if MsgQueue == []:
continue
state,cur,d = down(state, cur, d)
if state == 1:
exitFlag.append(1)
opendoor()
exitFlag.pop(0)
state = 0
continue
if __name__ == "__main__":
thread1 = threading.Thread(target=Msgfunction)
thread2 = threading.Thread(target=statemachine)
thread1.start()
thread2.start()
import time
import threading
import random
TOP = 6
BOTTOM = 1
STATE = {0: "门停开着", 1: "门停关着", 2: "电梯上升", 3: "电梯下降"}
DIR = {0: "向下", 1: "向上"}
# 消息编码:0 00关门 ,01 开门, 02 有人进
# 1 11去1L ,12去2L,13去三楼,14去4楼,15去五楼,16去六楼
# 2 21一楼有人按上,22二楼有人按上,23三楼有人按上,24四楼有人按上,25五楼有人按上
# 3 32二楼有人按下,33三楼有人按下,34四楼有人按下,35五楼有人按下,36六楼有人按下
msgLock = threading.Lock() # 消息锁
exitLock = threading.Lock() # 开关门锁
responseInterval = 0.3 # 响应间隔0.3秒
exitTime = 1.5 # 开关门时间
class Msg:
msgDecode = [["关门", "开门", "有人进"], "去{}楼", "{}楼有人按上", "{}楼有人按下"] # 消息解码表
def __init__(self, type_, value):
self.type = type_
self.value = value
if type_ == 0:
self.info = self.msgDecode[0][value]
else:
self.info = self.msgDecode[type_].format(value)
class Exit:
exitDecode = {0: "关门中", 1: "开门中", 2: "开门中", 3: "无状态"}
def __init__(self, value):
self.value = value
self.info = self.exitDecode[value]
exitFlag = Exit(3)
msgQueue = []
def printMsg(name, queue):
"""
打印消息队列
"""
if type(queue) is list:
info = [m.info for m in queue]
else:
info = queue.info
print(" "*50 + name+": "+str(info))
def update_msgQueue(action, target):
"""
更新消息队列
:param action: 动作包括"append"和"="
:param target: 更新值
:return:
"""
global msgQueue, msgLock
if msgLock.acquire():
if action == "=":
if msgQueue[:] == target[:]: # 如果没有改变则跳过
msgLock.release()
return
msgQueue = target
else:
eval("msgQueue."+action)(target)
printMsg("msgQueue",msgQueue)
msgLock.release()
def update_exitFlag(target):
"""
更新开关门状态
:param target: 更新值
:return:
"""
global exitFlag, exitLock
if exitLock.acquire():
exitFlag = Exit(target)
printMsg("exitFlag", exitFlag)
exitLock.release()
def msgFunction():
"""
随机产生消息
:return:
"""
global msgQueue
for i in range(4):
type = random.randint(0, 3)
value = 0
if type == 0:
value = random.randint(0, 2)
if type == 2:
value = random.randint(BOTTOM, TOP-1)
if type == 3:
value = random.randint(BOTTOM+1, TOP)
if type == 1:
value = random.randint(BOTTOM, TOP)
TIME = random.randint(1, 8)
m = Msg(type, value)
print("产生消息:", m.info)
exist = False
for each in msgQueue:
if m.type == each.type and m.value == each.value:
exist = True
break
if not exist:
update_msgQueue("append",m)
time.sleep(TIME)
class StateThreed(threading.Thread):
def run(self):
global msgQueue, exitFlag
# 初始化电梯状态、楼层和方向
state, new_state = 0, 0
cur, new_cur = 1, 1
d, new_d = 0, 0
timeout = 3 # 超时3秒
print("当前状态:%s,当前楼层:%d,运行方向:%s" % (STATE[state], cur, DIR[d]))
while True:
time.sleep(responseInterval) # 动作响应间隔为responseInterval秒
if (state, cur, d) != (new_state, new_cur, new_d): # 如果发生改变,则复制并打印状态
state, cur, d = new_state, new_cur, new_d
print("当前状态:%s,当前楼层:%d,运行方向:%s" % (STATE[state], cur, DIR[d]))
else:
print("............")
if state == 0: # 门停开着,包括正在开门时
if exitFlag.value == 3: # 开关门动作中不计算超时
timeout -= responseInterval # 超时减去responseInterval秒
tmp_list = msgQueue[:]
for m in msgQueue:
if m.type == 0: # 有0类消息
tmp_list.remove(m) # 不管三七二十一先移除
if m.value == 0: # 关门消息
new_state = 1 # 先设置状态为门停关着,后关门
self.closeDoor()
timeout = 3
break
else: # 开门状态下 有人按开门或有人进
timeout = 3
if m.value == cur: # 开门状态下有人在外面按与电梯运行同方向的按钮
if (d == 1 and m.type == 2) or (d == 2 and m.type == 3):
tmp_list.remove(m)
timeout = 3
update_msgQueue("=", tmp_list)
if timeout <= 0:
print("超时")
new_state = 1
self.closeDoor()
elif state == 1: # 门停关着, 包括正在关门时
tmp_list = msgQueue[:]
for m in msgQueue:
if m.type == 0:
tmp_list.remove(m)
if m.value: # 不是关门消息
new_state = 0
self.openDoor()
timeout = 3
break
if m.value == cur: # 关门状态下有人在外面按与电梯运行同方向的按钮
if (d == 1 and m.type == 2) or (d == 2 and m.type == 3):
tmp_list.remove(m)
new_state = 0
self.openDoor()
timeout = 3
break
if m.type == 1: # 到当前层直接移除
tmp_list.remove(m)
update_msgQueue("=", tmp_list)
if new_state == 1 and exitFlag.value == 3: # 门停关着无状态才能启动
timeout = 3 # 重置超时时间
new_state, new_cur, new_d = self.closed(state, cur, d)
else: # 电梯上升或下降
if state == 2:
new_cur, new_d = self.up(cur, d)
else:
new_cur, new_d = self.down(cur, d)
tmp_list = msgQueue[:] # 清除掉电梯上升或下降期间的0类指令
for m in msgQueue:
if m.type == 0:
tmp_list.remove(m)
update_msgQueue("=", tmp_list)
new_state = 1 # 到达楼层后自动开门,加入一个开门指令
m = Msg(0, 1)
update_msgQueue("append", m)
def closed(self, state, cur, d):
"""
门关着的时候,响应
:param state: 电梯状态
:param cur: 当前楼层
:param d: 运行方向
:return: 更新后的三个输入值
"""
if d == 1: # 向上
if self.startup(cur, d): # 如果启动,
state = 2 # 电梯状态为上升
else: # 不能启动
d = 0 # 尝试改变电梯方向
if self.startup(cur, d): # 如果启动
state = 3 # 电梯下降
else:
d = 1 # 恢复d
else: # 向下
if self.startup(cur, d): # 如果启动
state = 3 # 电梯状态上升
else:
d = 1
if self.startup(cur, d):
state = 2
else:
d = 0
return state, cur, d
def up(self, cur, d):
while not self.stop(cur, d):
cur += 1
print("正在前往第%d层..." % cur)
time.sleep(2)
print("到达第{}层".format(cur))
return cur, d
def down(self, cur, d):
while not self.stop(cur, d):
cur -= 1
print("正在前往第%d层..." % cur)
time.sleep(2)
print("到达第{}层".format(cur))
return cur, d
def startup(self, cur, d):
"""
是否启动
:param cur:当前楼层
:param d: 运行方向
:return: bool值是否启动
"""
global msgQueue
is_start = False
has_same_d = False
tmp_list = msgQueue[:]
for m in msgQueue:
if m.type != 0:
if d == 1 and m.value > cur: # 向上,非0类型消息,楼层大于当前楼层
has_same_d = True
is_start = True
if d == 0 and m.value < cur: # 向下,非0类型消息,楼层小于当前楼层
has_same_d = True
is_start = True
if not has_same_d: # 如果没有同方向消息,则只要有当前层的消息,即使与电梯方向相反都扔掉
for m in msgQueue:
if m.type and m.value == cur:
tmp_list.remove(m)
update_msgQueue("=", tmp_list)
return is_start
def stop(self, cur, d):
"""
是否停止
:param cur:当前楼层
:param d: 运行方向
:return: bool值是否停止
"""
global msgQueue
is_stop = False # 返回值是否停下
has_same_d = False # 是否存在与电梯运行方向相同方向消息
tmp_list = msgQueue[:]
if d == 1: # 上升
if cur == TOP:
is_stop = True
for m in msgQueue: # 移除掉消息队列里当前层的有关消息
if m.type == 1 or m.type == 2: # 电梯内的到达当前层移除, 电梯外的当前层向上移除
has_same_d = True # 存在同方向消息
if m.value == cur:
is_stop = True
tmp_list.remove(m)
else: # 下降
if cur == BOTTOM:
is_stop = True
for m in msgQueue:
if m.type == 1 or m.type == 3: # 电梯内的到达当前层移除,电梯外的当前层向下移除
has_same_d = True
if m.value == cur:
is_stop = True
tmp_list.remove(m)
if not has_same_d: # 如果没有同方向消息,则只要有当前层的消息,即使与电梯方向相反也停下来, 防止电梯跑过头
for m in msgQueue:
if m.type and m.value == cur:
is_stop = True
tmp_list.remove(m)
update_msgQueue("=", tmp_list)
return is_stop
def closeDoor(self):
global exitFlag
if exitFlag.value == 0: # 如果已经在关门状态则不启动关门子线程
return
t = threading.Thread(target=closeThread) # 启动关门子线程
t.start()
def openDoor(self):
global exitFlag
if exitFlag.value == 1 or exitFlag.value == 2: # 如果已经在开门状态则不启动开门子线程
return
t = threading.Thread(target=openThread) # 启动开门子线程
t.start()
def openThread():
"""
开门子线程
"""
global exitFlag, exitTime
print("正在开门...")
update_exitFlag(1) # 更新为开门中
counter = exitTime
while counter > 0:
if exitFlag.value != 1 and exitFlag.value != 2: # 被另一个开门线程打断
print("开门终止")
exitTime = counter #中涂打断,开关门时间变成已花费时间
return
if exitLock.acquire():
time.sleep(responseInterval)
exitLock.release()
counter -= responseInterval
# 成功开门
print("已开门...")
update_exitFlag(3)
exitTime = 1.5
def closeThread():
"""
关门子线程
"""
global exitFlag, exitTime
print("正在关门...")
update_exitFlag(0) # 更新为关门中
counter = exitTime
while counter > 0:
if exitFlag.value != 0: # 被另一个开门线程改变
print("关门终止")
exitTime = counter
return
if exitLock.acquire():
time.sleep(responseInterval)
exitLock.release()
counter -= responseInterval
# 成功关门
print("已关门")
update_exitFlag(3)
exitTime = 1.5
if __name__ == "__main__":
thread1 = threading.Thread(target=msgFunction)
thread2 = StateThreed()
thread1.start()
thread2.start()
1.打印结果更直观
2.exitFlag有一个列表改为了一个值
3.修复了一些小bug
4.写了注释。。。。