进程与线程(二)——线程
一 线程的两种调用方式
threading 模块建立在thread 模块之上。thread模块以低级、原始的方式来处理和控制线程,而threading 模块通过对thread进行二次封装,提供了更方便的api来处理线程。
直接调用:
1 import threading 2 import time 3 4 def sayhi(num): #定义每个线程要运行的函数 5 6 print("running on number:%s" %num) 7 8 time.sleep(3) 9 10 if __name__ == '__main__': 11 12 t1 = threading.Thread(target=sayhi,args=(1,)) #生成一个线程实例 13 t2 = threading.Thread(target=sayhi,args=(2,)) #生成另一个线程实例 14 15 t1.start() #启动线程 16 t2.start() #启动另一个线程 17 18 print(t1.getName()) #获取线程名 19 print(t2.getName())
继承式调用:
1 import threading 2 import time 3 4 5 class MyThread(threading.Thread): 6 def __init__(self,num): 7 threading.Thread.__init__(self) 8 self.num = num 9 10 def run(self):#定义每个线程要运行的函数 11 12 print("running on number:%s" %self.num) 13 14 time.sleep(3) 15 16 if __name__ == '__main__': 17 18 t1 = MyThread(1) 19 t2 = MyThread(2) 20 t1.start() 21 t2.start() 22 23 print("ending......")
二 threading.thread的实例方法
join&Daemon方法:
1 import threading 2 from time import ctime,sleep 3 4 def ListenMusic(name): 5 6 print ("Begin listening to %s. %s" %(name,ctime())) 7 sleep(3) 8 print("end listening %s"%ctime()) 9 10 def RecordBlog(title): 11 12 print ("Begin recording the %s! %s" %(title,ctime())) 13 sleep(5) 14 print('end recording %s'%ctime()) 15 16 17 threads = [] 18 19 20 t1 = threading.Thread(target=ListenMusic,args=('水手',)) 21 t2 = threading.Thread(target=RecordBlog,args=('python线程',)) 22 23 threads.append(t1) 24 threads.append(t2) 25 26 if __name__ == '__main__': 27 28 for t in threads: 29 #t.setDaemon(True) #注意:一定在start之前设置 30 t.start() 31 # t.join() 32 # t1.join() 33 t1.setDaemon(True) 34 35 #t2.join()########考虑这三种join位置下的结果? 36 print ("all over %s" %ctime())
join():在子线程完成运行之前,这个子线程的父线程将一直被阻塞。
setDaemon(True):
将线程声明为守护线程,必须在start() 方法调用之前设置, 如果不设置为守护线程程序会被无限挂起。这个方法基本和join是相反的。
当我们 在程序运行中,执行一个主线程,如果主线程又创建一个子线程,主线程和子线程 就分兵两路,分别运行,那么当主线程完成
想退出时,会检验子线程是否完成。如 果子线程未完成,则主线程会等待子线程完成后再退出。但是有时候我们需要的是 只要主线程
完成了,不管子线程是否完成,都要和主线程一起退出,这时就可以 用setDaemon方法啦
其它方法:
# run(): 线程被cpu调度后自动执行线程对象的run方法 # start():启动线程活动。 # isAlive(): 返回线程是否活动的。 # getName(): 返回线程名。 # setName(): 设置线程名。 threading模块提供的一些方法: # threading.currentThread(): 返回当前的线程变量。 # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。 # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
三 同步锁(Lock)(同步锁和数据库中的事物有点像,将一组操作进行捆绑操作)
1 import time 2 import threading 3 4 def addNum(): 5 global num #在每个线程中都获取这个全局变量 6 #num-=1 7 8 temp=num 9 #print('--get num:',num ) 10 time.sleep(0.1) 11 num =temp-1 #对此公共变量进行-1操作 12 13 num = 100 #设定一个共享变量 14 thread_list = [] 15 for i in range(100): 16 t = threading.Thread(target=addNum) 17 t.start() 18 thread_list.append(t) 19 20 for t in thread_list: #等待所有线程执行完毕 21 t.join() 22 23 print('final num:', num )
观察:time.sleep(0.1) /0.001/0.0000001 结果分别是多少?
多个线程都在同时操作同一个共享资源,所以造成了资源破坏,怎么办呢?(join会造成串行,失去所线程的意义)
我们可以通过同步锁来解决这种问题
1 R=threading.Lock() 2 3 #### 4 def sub(): 5 global num 6 R.acquire() 7 temp=num-1 8 time.sleep(0.1) 9 num=temp 10 R.release()
四 线程死锁和递归锁
在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁,因为系统判断这部分资源都正在使用,所有这两个线程在无外力作用下将一直等待下去。下面是一个死锁的例子:
1 import threading,time 2 3 class myThread(threading.Thread): 4 def doA(self): 5 lockA.acquire() 6 print(self.name,"gotlockA",time.ctime()) 7 time.sleep(3) 8 lockB.acquire() 9 print(self.name,"gotlockB",time.ctime()) 10 lockB.release() 11 lockA.release() 12 13 def doB(self): 14 lockB.acquire() 15 print(self.name,"gotlockB",time.ctime()) 16 time.sleep(2) 17 lockA.acquire() 18 print(self.name,"gotlockA",time.ctime()) 19 lockA.release() 20 lockB.release() 21 22 def run(self): 23 self.doA() 24 self.doB() 25 if __name__=="__main__": 26 27 lockA=threading.Lock() 28 lockB=threading.Lock() 29 threads=[] 30 for i in range(5): 31 threads.append(myThread()) 32 for t in threads: 33 t.start() 34 for t in threads: 35 t.join()#等待线程结束,后面再讲。
解决办法:使用递归锁,将
lockA=threading.Lock() lockB=threading.Lock()<br>#--------------<br>lock=threading.RLock()
为了支持在同一线程中多次请求同一资源,python提供了“可重入锁”:threading.RLock。RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次acquire。直到一个线程所有的acquire都被release,其他的线程才能获得资源。
应用:
import time import threading class Account: def __init__(self, _id, balance): self.id = _id self.balance = balance self.lock = threading.RLock() def withdraw(self, amount): with self.lock: self.balance -= amount def deposit(self, amount): with self.lock: self.balance += amount def drawcash(self, amount):#lock.acquire中嵌套lock.acquire的场景 with self.lock: interest=0.05 count=amount+amount*interest self.withdraw(count) def transfer(_from, to, amount): #锁不可以加在这里 因为其他的其它线程执行的其它方法在不加锁的情况下数据同样是不安全的 _from.withdraw(amount) to.deposit(amount) alex = Account('alex',1000) yuan = Account('yuan',1000) t1=threading.Thread(target = transfer, args = (alex,yuan, 100)) t1.start() t2=threading.Thread(target = transfer, args = (yuan,alex, 200)) t2.start() t1.join() t2.join() print('>>>',alex.balance) print('>>>',yuan.balance)
同步条件(Event)
An event is a simple synchronization object;the event represents an internal flag,
and threads can wait for the flag to be set, or set or clear the flag themselves.
event = threading.Event()
# a client thread can wait for the flag to be set
event.wait()
# a server thread can set or reset it
event.set()
event.clear()
If the flag is set, the wait method doesn’t do anything.
If the flag is cleared, wait will block until it becomes set again.
Any number of threads may wait for the same event.
1 import threading,time 2 class Boss(threading.Thread): 3 def run(self): 4 print("BOSS:今晚大家都要加班到22:00。") 5 print(event.isSet()) 6 event.set() 7 time.sleep(5) 8 print("BOSS:<22:00>可以下班了。") 9 print(event.isSet()) 10 event.set() 11 class Worker(threading.Thread): 12 def run(self): 13 event.wait() 14 print("Worker:哎……命苦啊!") 15 time.sleep(1) 16 event.clear() 17 event.wait() 18 print("Worker:OhYeah!") 19 if __name__=="__main__": 20 event=threading.Event() 21 threads=[] 22 for i in range(5): 23 threads.append(Worker()) 24 threads.append(Boss()) 25 for t in threads: 26 t.start() 27 for t in threads: 28 t.join()
信号量(Semaphore)
信号量用来控制线程并发数的,BoundedSemaphore或Semaphore管理一个内置的计数 器,每当调用acquire()时-1,调用release()时+1。
计数器不能小于0,当计数器为 0时,acquire()将阻塞线程至同步锁定状态,直到其他线程调用release()。(类似于停车位的概念)
BoundedSemaphore与Semaphore的唯一区别在于前者将在调用release()时检查计数 器的值是否超过了计数器的初始值,如果超过了将抛出一个异常。
1 import threading,time 2 class myThread(threading.Thread): 3 def run(self): 4 if semaphore.acquire(): 5 print(self.name) 6 time.sleep(5) 7 semaphore.release() 8 if __name__=="__main__": 9 semaphore=threading.Semaphore(5) 10 thrs=[] 11 for i in range(100): 12 thrs.append(myThread()) 13 for t in thrs: 14 t.start()
多线程利器---队列(queue)
列表是不安全的数据结构
import threading,time li=[1,2,3,4,5] def pri(): while li: a=li[-1] print(a) time.sleep(1) try: li.remove(a) except Exception as e: print('----',a,e) t1=threading.Thread(target=pri,args=()) t1.start() t2=threading.Thread(target=pri,args=()) t2.start()
思考:如何通过对列来完成上述功能?
queue is especially useful in threaded programming when information must be exchanged safely between multiple threads.
queue列队类的方法
1 创建一个“队列”对象 2 import Queue 3 q = Queue.Queue(maxsize = 10) 4 Queue.Queue类即是一个队列的同步实现。队列长度可为无限或者有限。可通过Queue的构造函数的可选参数maxsize来设定队列长度。如果maxsize小于1就表示队列长度无限。 5 6 将一个值放入队列中 7 q.put(10) 8 调用队列对象的put()方法在队尾插入一个项目。put()有两个参数,第一个item为必需的,为插入项目的值;第二个block为可选参数,默认为 9 1。如果队列当前为空且block为1,put()方法就使调用线程暂停,直到空出一个数据单元。如果block为0,put方法将引发Full异常。 10 11 将一个值从队列中取出 12 q.get() 13 调用队列对象的get()方法从队头删除并返回一个项目。可选参数为block,默认为True。如果队列为空且block为True, 14 get()就使调用线程暂停,直至有项目可用。如果队列为空且block为False,队列将引发Empty异常。 15 16 Python Queue模块有三种队列及构造函数: 17 1、Python Queue模块的FIFO队列先进先出。 class queue.Queue(maxsize) 18 2、LIFO类似于堆,即先进后出。 class queue.LifoQueue(maxsize) 19 3、还有一种是优先级队列级别越低越先出来。 class queue.PriorityQueue(maxsize) 20 21 此包中的常用方法(q = Queue.Queue()): 22 q.qsize() 返回队列的大小 23 q.empty() 如果队列为空,返回True,反之False 24 q.full() 如果队列满了,返回True,反之False 25 q.full 与 maxsize 大小对应 26 q.get([block[, timeout]]) 获取队列,timeout等待时间 27 q.get_nowait() 相当q.get(False) 28 非阻塞 q.put(item) 写入队列,timeout等待时间 29 q.put_nowait(item) 相当q.put(item, False) 30 q.task_done() 在完成一项工作之后,q.task_done() 函数向任务已经完成的队列发送一个信号 31 q.join() 实际上意味着等到队列为空,再执行别的操作
1 import queue 2 3 #先进后出 4 5 q=queue.LifoQueue() 6 7 q.put(34) 8 q.put(56) 9 q.put(12) 10 11 #优先级 12 # q=queue.PriorityQueue() 13 # q.put([5,100]) 14 # q.put([7,200]) 15 # q.put([3,"hello"]) 16 # q.put([4,{"name":"alex"}]) 17 18 while 1: 19 20 data=q.get() 21 print(data)
生产者消费者模型:
为什么要使用生产者和消费者模式
在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。
什么是生产者消费者模式
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
这就像,在餐厅,厨师做好菜,不需要直接和客户交流,而是交给前台,而客户去饭菜也不需要不找厨师,直接去前台领取即可,这也是一个结耦的过程。
1 import time,random 2 import queue,threading 3 4 q = queue.Queue() 5 6 def Producer(name): 7 count = 0 8 while count <10: 9 print("making........") 10 time.sleep(random.randrange(3)) 11 q.put(count) 12 print('Producer %s has produced %s baozi..' %(name, count)) 13 count +=1 14 #q.task_done() 15 #q.join() 16 print("ok......") 17 def Consumer(name): 18 count = 0 19 while count <10: 20 time.sleep(random.randrange(4)) 21 if not q.empty(): 22 data = q.get() 23 #q.task_done() 24 #q.join() 25 print(data) 26 print('\033[32;1mConsumer %s has eat %s baozi...\033[0m' %(name, data)) 27 else: 28 print("-----no baozi anymore----") 29 count +=1 30 31 p1 = threading.Thread(target=Producer, args=('A',)) 32 c1 = threading.Thread(target=Consumer, args=('B',)) 33 # c2 = threading.Thread(target=Consumer, args=('C',)) 34 # c3 = threading.Thread(target=Consumer, args=('D',)) 35 p1.start() 36 c1.start() 37 # c2.start() 38 # c3.start()