python数据结构之多线程实现银行叫号服务

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))
1.问题的定义即分析 银行叫号系统中大概能分成两个对象:一个为来的顾客,一个为银行本身 顾客(Customer)里面需要包含有数据: 【1】每个顾客都应该有一个标签以用来区分,因此需要一个ID(cID) 【2】问题中有求等待的时间,而有关等待时间的两个变量即为进入银行的时间以及接受服务的时间(enterTime和startTime) 【3】由于题目中说明顾客的服务时间不是固定的,因此每个顾客的服务时间又有区别,所以需要有一个变量来进行表示(serTime) 【4】顾客接受服务完后离开的时间(endTime) 综上:Customer中所需要的私有成员有(cID,enterTime,startTime,serTime,endTime) 而Customer的成员函数其实只是对以上数据进行输入输出,所以有成员函数有: 一系列的Set函数,一系列的Get函数,以及构造函数,复制函数,=的重载函数以及Reset函数 银行(bank)里面需要包含的数据: 银行中的人有两种状态,一是等待,二是接受服务,而这等待状态遵循先到先得的原则,因此,采用队列这种结构来表示这些状态比较合适,已经接受过服务的要反复输入输出,用容易遍历的数据类型vector 【1】处于等待状态的人(waiting) 【2】处于服务状态或已经接受过服务的人(serving) 由于经常要对waiting以及serving进行操作,所以不考虑将其作为私有成员(省去了Set以及Get函数) 考虑一下所含有的成员函数:Reset函数(保证银行是空的),Display函数(对已经接受过服务的以及正在服务的顾客进行展示),Assign()(断某个窗口是否是空的利用每次都改变的endTime - startTime == 随机生成的serTime判断 添加功能函数:查看最大等待时间以及其对应的人数的函数Maxwaiting 2.类与算法设计 类设计: class Customer { private: int enterTime; //进入银行的时间 int startTime; //开始服务的时间 int cID; //顾客的编 int endTime; //顾客停止服务的时间 int serTime; //顾客服务从开始到结束服务所需要的时间(随机的) public: Customer(); //默认构造函数 Customer(const Customer &new;_customer); //复制函数 Customer &operator;=(const Customer&old;_customer); //=重载函数 void Reset(); //重置,将所有数据归零 int GetServDurance(); //返回等待时间 int GetEndTime(); //返回离开银行的时间 int GetSerTime(); //返回接受服务的时间 int GetStartTime();//返回开始接受服务的时间 int GetID(); //返回ID void SetEnterTime(int new_enter); void SetStartTime(int new_start); void SetEndTime(int new_end); void SetID(int new_ID); void SetSerTime(int new_sertime); }; class Bank { public: vector<Customer> serving; //在服务窗口接受过服务的人 queue<Customer> waiting; //在银行内等待的人 bool Assign(Customer customer); //判断第i个窗口的顾客是否服务完成 void Display(); //将在等待的人或者是已经接受了服务的人展示到屏幕上 void MaxWaiting(); void Reset(); }; 算法设计 随机数的产生: 【1】由于需要对过程进行模拟,所以需要一整套的模拟系统,包括产生随机人数,随机服务时间等等 【2】事实上,银行服务窗口的更新是按照一定的时间(即每隔几秒刷新一次),同样的人也是类似,因此需要几个单位时间已确定进行这些随机事件发生的间隔 几个变量如下: const int UnitTime = 10;//单位时间为10 const int ServTime = 5;//服务时间为5-10 const int UnitTimePeo = 5;//单位时间进来的人数为5-10; const int StopEnterTime = TimeSlot - 2 * UnitTime;//为了保证所有顾客能完成服务,顾客进入的时间为0-80 const int PerTime = 5;//每隔5秒显示一次窗口信息 int TimeOfServing = 0;//随机生成的服务时间(5-10) int Number = 0; //随机生成的人数(5-10) 通过这些变量以及srand((unsigned)time(0)),rand()即可产生一系列的随机数以便进行模拟 过程的模拟 每当产生一系列随机的人,就将他们放进waiting的队列中去,接着遍历各个窗口,若窗口为空,则先来的人可以去那个窗口(即push到serving,pop掉waiting),若不为空,则改变结束时间,之后Present + 1 显示内容:剩余的时间,各窗口的情况,以及已经接受了服务的人的等待时间 注意:该程序为每五刷新一次,每十随机进入一些人
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值