4. 模拟银行服务完成程序代码。
目前,在以银行营业大厅为代表的窗口行业中大量使用排队(叫号)系统,该系统完全模拟了人群排队全过程,通过取票进队、排队等待、叫号服务等功能,代替了人们站队的辛苦。
排队叫号软件的具体操作流程为:
- 顾客取服务序号
当顾客抵达服务大厅时,前往放置在入口处旁的取号机,并按一下其上的相应服务按钮,取号机会自动打印出一张服务单。单上显示服务号及该服务号前面正在等待服务的人数。
- 服务员工呼叫顾客
服务员工只需按一下其柜台上呼叫器的相应按钮,则顾客的服务号就会按顺序的显示在显示屏上,并发出“叮咚”和相关语音信息,提示顾客前往该窗口办事。当一位顾客办事完毕后,柜台服务员工只需按呼叫器相应键,即可自动呼叫下一位顾客。
编写程序模拟上面的工作过程,主要要求如下:
- 程序运行后,当看到“请点击触摸屏获取号码:”的提示时,只要按回车键,即可显示“您的号码是:XXX,您前面有YYY位”的提示,其中XXX是所获得的服务号码,YYY是在XXX之前来到的正在等待服务的人数。
- 用多线程技术模拟服务窗口(可模拟多个),具有服务员呼叫顾客的行为,假设每个顾客服务的时间是10000ms,时间到后,显示“请XXX号到ZZZ号窗口!”的提示。其中ZZZ是即将为客户服务的窗口号。
思路:
-
1.要编写一个队列的class类,编写判断队列是否为空,入队,出队,队列的长度,前面还有多少人排队等函数
-
2.要编写一个模拟多个柜台窗口叫号的class类,用多线程编写一个叫号函数(理论上是一个死循环,隔多少秒就开始叫号)
- 绑定的参数主要包括多线程调用,等待的队伍及初始化,线程锁
- 调用的函数主要是叫号函数:用while(true)死循环,延时+如果队伍不是空的:就上锁,调用出队实例,解锁
- 多线程
- _thread和threading,_thread是低级模块,threading是高级模块,对_thread进行了封装。绝大多数情况下,我们只需要使用threading这个高级模块。启动一个线程就是把一个函数传入并创建Thread实例,然后调用start()开始执行。
- 由于任何进程默认就会启动一个线程,我们把该线程称为主线程,主线程又可以启动新的线程,Python的threading模块有个current_thread()函数,它永远返回当前线程的实例。主线程实例的名字叫MainThread,子线程的名字在创建时指定,我们用LoopThread命名子线程。名字仅仅在打印时用来显示,完全没有其他意义,如果不起名字Python就自动给线程命名为Thread-1,Thread-2……
- 线程锁
- 多线程和多进程最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,而多线程中,所有变量都由所有线程共享,所以,任何一个变量都可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱了。 具体参考:https://www.liaoxuefeng.com/wiki/1016959663602400/1017629247922688 的lock部分。主要原因是操作系统在执行多线程的时候是交替进行的,所以修改共享变量的时候容易出错。
- 如果我们要确保共享变量balance计算正确,就要给 修改共享变量的函数change_it() 上一把锁,当某个线程开始执行change_it()时,我们说,该线程因为获得了锁,因此其他线程不能同时执行change_it(),只能等待,直到锁被释放后,获得该锁以后才能改。即获得锁的那个线程才能调用change函数,相当于锁匙与锁匹配一样,change函数是锁,锁是锁钥 由于锁只有一个,无论多少线程,同一时刻最多只有一个线程持有该锁,所以,不会造成修改的冲突。创建一个锁就是通过threading.Lock()来实现:1.先获得锁。2.调用修改程序 3.释放锁
- 谈谈个人理解,线程相当于机器低级语言中的“中断服务例程”,类似单片机,;多线程处理的话就要用多个锁进行并发执行。
-
3.编写银行取号系统的class类,要调用队列的实例,并设置取号的初值(0)和最大值(可设可不设),并编写取号函数.
# 多线程调用实例
import time, threading
# 新线程执行的代码:
"""循坏函数"""
def loop():
#current_thread()函数,它永远返回当前线程的实例
#current_thread.name()函数,它永远返回当前线程的实例的名字
print('thread %s is running...' % threading.current_thread().name)
n = 0
while n < 5:
n = n + 1
print('thread %s >>> %s' % (threading.current_thread().name, n))
time.sleep(1)
print('thread %s ended.' % threading.current_thread().name)
#主程序
print('thread %s is running...' % threading.current_thread().name)
t = threading.Thread(target=loop, name='LoopThread') #调用多线程高级模块并把loop函数传入作为线程实例Thread
t.start() #启动主线程
t.join() #是让t线程半路杀入,直到t线程执行结束才给其他线程机会
print('thread %s ended.' % threading.current_thread().name)
thread MainThread is running...
thread LoopThread is running...
thread LoopThread >>> 1
thread LoopThread >>> 2
thread LoopThread >>> 3
thread LoopThread >>> 4
thread LoopThread >>> 5
thread LoopThread ended.
thread MainThread ended.
# 线程锁实例
balance = 0
lock = threading.Lock()
def run_thread(n):
for i in range(100000):
# 先要获取锁:
lock.acquire()
try:
# 放心地改吧:
change_it(n)
finally:
# 改完了一定要释放锁:
lock.release()
当多个线程同时执行lock.acquire()时,只有一个线程能成功地获取锁,然后继续执行代码,其他线程就继续等待直到获得锁为止。
获得锁的线程用完后一定要释放锁,否则那些苦苦等待锁的线程将永远等待下去,成为死线程。所以我们用try…finally来确保锁一定会被释放。
锁的好处就是确保了某段关键代码只能由一个线程从头到尾完整地执行,坏处当然也很多,首先是阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了。其次,由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁,导致多个线程全部挂起,既不能执行,也无法结束,只能靠操作系统强制终止。
银行叫号服务系统
#导入多线程库
import time,threading
#定义队列
class Queue:
#绑定参数
def __init__(self):
self.item=[]
#判断队列是否非空
def isEmpty(self):
return self.item==[]
#入队
def enqueue(self,item):
self.item.append(item) #可能需要该insert
#出队
def dequeue(self):
if self.item!=[]:
return self.item.pop()
else:
return False
#队列大小
def size(self):
if self.item!=[]:
return len(self.item)
else:
print('队列为空')
#前面还有多少人排队//或者说你前面那个是多少号
def paidui_number(self):
if self.item!=[]:
return self.item[len(self.item)-1]
else:
return False
#多线程实现柜台叫号
class Counter(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.waitQueue = Queue() ## 初始化等待的队伍
self.lock = threading.Lock()
def callIng(self):
while True:
### 柜台一直叫号,要一直循环
time.sleep(5)
if not self.waitQueue.isEmpty():
self.lock.acquire()
print("请客户{},到{}窗口办理业务".format(self.waitQueue.paidui_number(), threading.current_thread().name))
try:
self.waitQueue.dequeue()
finally:
self.lock.release()
#取号系统
class quhao_system():
#绑定参数
def __init__(self):
self.serviceQueue=Queue()#实例化队列函数
self.nowNum=0 #还没有人取号的时候初值为0
def quhao(self):
self.nowNum +=1
return self.nowNum
#主程序
if __name__ =="__main__":
#初值设置
res=quhao_system() #实例化取号函数
counter_window=3 #设置柜台数
serviceWindow=[None]*counter_window #初始化设置正在服务的窗口
threadlist=[None]*counter_window #初始化设置多线程表gong
for i in range(counter_window):
serviceWindow[i]=Counter()#服务窗口实例化为柜台叫号函数
serviceWindow[i].waitQueue=res.serviceQueue #将柜台的等待队列实例化为取号队列方法
threadlist[i] = threading.Thread(name=(i + 1), target=serviceWindow[i].callIng, args=())
#实例化多线程列表中的第i个线程,其中线程命名为'i+1',线程执行的函数为第i个服务窗口的叫号函数,不用对共享变量进行修改。
threadlist[i].start() #启动第i个线程
threadlist[i].join()
while(True):
input('请点击触摸屏取号:')#input()函数:将()里的内容打印并获取输入信息。
callNumber=res.quhao()#实例化取号函数
if res.serviceQueue !=None:
print('当前你的号码为'+str(callNumber)+",你前面还有"+str(res.serviceQueue.size())+"个人") #paidui_number可能要改为size
res.serviceQueue.enqueue(res.nowNum)
else:
print('你的号码是{},下一个就到您'.format(callNumber))