Python攻克之路-信号量
1.信号量(锁)
信号量用来控制线程并发数的,BoundedSemaphore或Semaphore管理一个内置的计数器,每当调用acquire()时-1,调用release()时+1.
计数器不能小于0,当计数器为0时,acquire()将阻塞线程至同步锁定状态,直到其他线程调用release().
BoundedSemaphore和Semaphore的唯一区别在于前者将在调用release()时检查计数器的值是否超过了计数器的初始值,如果超过了将抛出一个异常
作用:数据库连接限制
问题:是否一起进入,一起出?
分析:并不是,内部的计数器可以控制,如果有一个线程完成任务,释放了锁,其他线程就可以进入
[root@node2 semaphore]# cat semaphore.py #!/usr/local/python3/bin/python3 import threading,time class MyThread(threading.Thread): def run(self): if semaphore.acquire(): print(self.name) time.sleep(3) semaphore.release() if __name__=="__main__": semaphore=threading.BoundedSemaphore(5) #创建信号量,5是控制线程数 thrs=[] for i in range(100): thrs.append(MyThread()) #同时启动100个线程执行run方法 for t in thrs: t.start() [root@node2 semaphore]# python3 semaphore.py Thread-1 #每5个出现一次 ... Thread-100 将for i in range(23) [root@node2 semaphore]# python3 semaphore.py Thread-1 .... Thread-20 Thread-21 Thread-22 Thread-23 最后只出3个
2.条件变量同步(锁)
描述:线程之间是可以通信的,某个线程走到一定时候,等待另一个线程的通知后,才继续向下走有一类线程需要满足条件之后才能够继续执行,Python提供了threading.Condition对象条件变量线程的支持,它除了能提供RLock()或Lock()的方法外,还供了wait()、notify()、notifyAll()方法.lock_con=threading.Condition([Lock/Rlock]): 锁是可选选项,不传人锁,对象自动创建一个RLock().
- wait(): 条件不满足时调用,线程会释放锁并进入等待阻塞
- notify(): 条件创造后调用,通知等待池激活一个线程
- notifyAll(): 条件创造后调用,通知等待池激活所有线程
场景:生产者是做包子的人,消费者是吃包子的人,需要生产者与消费者一起行动,得有包子,吃的人才可以吃
分析:生产者和消费者开始行动时,可能两个都有机会取得锁,如果消费者先得到锁,它会先wait,释放锁,让生产者先开始,生产者完成后通知消费者,消费者再次申请锁再开始吃包子,
[root@node2 threading]# cat condition.py #!/usr/local/python3/bin/python3 import threading,time from random import randint class Producer(threading.Thread): def run(self): global L while True: val=randint(0,100) #随机生成数,相当于创建一个包子 print('Producer',self.name,":Append"+str(val),L) if lock_con.acquire(): #请求一把锁 L.append(val) #向L中放包子 lock_con.notify() #通知消费者可以开始吃包子,相当于满足上一行代码的条件,可以激活消费者的锁 lock_con.release() time.sleep(3) class Consumer(threading.Thread): def run(self): global L #类似于承包子的东西 while True: lock_con.acquire() #有可能consumer先获取锁,但是消费者只是吃包子,只能等待包子先做出来 if len(L)==0: lock_con.wait() #wait先释放锁,然后一直阻塞,直到有人通知才向下走,但是程序不会从阻塞这里开始走,从lock_con.acquire()开始 print('consumer',self.name,":Delete"+str(L[0]),L) del L[0] lock_con.release() time.sleep(0.25) if __name__=="__main__": L=[] #空列表 lock_con=threading.Condition() threads=[] for i in range(5): #启动5个线程来执行生产者的方法,相当于有5个人做包子 threads.append(Producer()) threads.append(Consumer()) #消费者对象 for t in threads: #开启了6个线程对象 t.start() for t in threads: t.join() [root@node2 threading]# python3 condition.py Producer Thread-1 :Append7 [] Producer Thread-2 :Append85 [7] Producer Thread-3 :Append33 [7, 85] Producer Thread-4 :Append1 [7, 85, 33] Producer Thread-5 :Append2 [7, 85, 33, 1] consumer Thread-6 :Delete7 [7, 85, 33, 1, 2] consumer Thread-6 :Delete85 [85, 33, 1, 2] consumer Thread-6 :Delete33 [33, 1, 2] consumer Thread-6 :Delete1 [1, 2] consumer Thread-6 :Delete2 [2]
3.同步条件event
描述:类似于条件,但是它不需要使用锁,内部有一个标志位,如果调用event.wait(),如果判断为False,就会把线程阻塞住,True就继续向下走,通过event.set函数来修改标志位为True,event.clear()修改标志位为False,使用isSet()返回是True还是False
条件同步和条件变量同步差不多,只是少了锁功能,因为条件同步设计于不访问共享资源的条件环境。event=threading.Event():条件环境对象,初始值为False:
- event.isSet(): 返回event的状态值
- event.wait(): 如果event.isSet()==False将阻塞线程
- event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态,等待操作系统调度
- event.clear(): 恢复event的状态值为False
场景:老板说晚上加班,工作说累,但是工人要在老板后面反应
[root@node2 threading]# cat ot.py #!/usr/local/python3/bin/python3 import threading,time class Boss(threading.Thread): def run(self): print("Boss: we gonna ot tonight util 22:00.") #打印加班 event.isSet() or event.set() #event.isSet返回状态值时还是Fasle,event.set()设置为True,这时worker也会相应执行 time.sleep(5) #此时标志位为True print("Boss: <22:00>knock off.") event.isSet() or event.set() class Worker(threading.Thread): def run(self): event.wait() #默认是false,如果worker先得到资源,要wait,因为这是老板要说的话,老板说了后,才说累 print("Worker: tired!") time.sleep(0.25) event.clear() #相当于工作一段时间后,再把标志位设置为false,当boss去取时为False event.wait() print("Worker: oh...") if __name__=="__main__": event=threading.Event() #event取得事件 threads=[] for i in range(5): #启动5个worker,一个boss threads.append(Worker()) threads.append(Boss()) for t in threads: t.start() for t in threads: t.join() [root@node2 threading]# python3 ot.py Boss: we gonna ot tonight util 22:00. Worker: tired! Worker: tired! Worker: tired! Worker: tired! Worker: tired! Boss: <22:00>knock off. Worker: oh... Worker: oh... Worker: oh... Worker: oh... Worker: oh...
4.队列queue***(数据结构)
quue is especially userful in threaded programming when information must be exchaged safely between multiple threads
(1)、queue定义了3种信息列队模式类
Queus([maxsize]): FIFO列队模式(first in first out),[maxsize]定义列队容量,缺省为0,即无穷大
LifoQueue([maxsize]): LIFO列队模式(last in first out),类似于放糖到瓶子,先放入的只能后出,因为最后放的比较接近瓶盖
PriorityQueue([maxsize]): 优先级列队模式,使用此列队时,项目应该是(priority,data)的形式
(2)、queue队列类的方法
q.put(item[,block,[timeout]]): 将item放入队列,如果block设置为True(默认)时,列队满了,调用者将被阻塞,否则抛出Full异常,timeout设置阻塞超时
q.get([block,[timeout]]): 从列队中取出一个项目
q.qsize():返回列队的大小
q.full(): 如果列队满了返回True,否则为False
q.empty(): 如果列队为空返回True,否则为False
(3)、创建一个队列
[root@node2 threading]# cat q.py #!/usr/local/python3/bin/python3 import queue d=queue.Queue() #队列对象 d.put('reid') #插入数据 d.put('betty') d.put('linux') print(d.get()) #取数据 print(d.get()) print(d.get()) [root@node2 threading]# python3 q.py #先进先出 reid betty linux 报错: [root@node2 threading]# python3 queue.py 脚本名与模块冲突 Traceback (most recent call last): File "queue.py", line 2, in <module> import queue File "/root/py/threading/queue.py", line 3, in <module> d=queue.Queue() AttributeError: module 'queue' has no attribute 'Queue' [root@node2 threading]# mv queue.py q.py 修改脚本名称
(4)、控制队列的量(队列满的情况)
[root@node2 threading]# cat q.py #!/usr/local/python3/bin/python3 import queue d=queue.Queue(2) #2是插入数据的量,如果插入的数量比这个大会造成阻塞,0代表是无限大 d.put('reid') d.put('betty') d.put('linux',0) #加入参数返回队列情况,0通知队列满了,1默认阻塞 print(d.get()) print(d.get()) print(d.get()) [root@node2 threading]# python3 q.py Traceback (most recent call last): File "q.py", line 6, in <module> d.put('linux',0) File "/usr/local/python3/lib/python3.6/queue.py", line 130, in put raise Full queue.Full ##显示队列已经满了
(5)、取数据(取的量多于插入的量)
[root@node2 threading]# cat q.py #!/usr/local/python3/bin/python3 import queue d=queue.Queue(3) d.put('reid') d.put('betty') d.put('linux',0) print(d.get()) print(d.get()) print(d.get()) print(d.get(0)) ##0,没有数据时会返回结果 [root@node2 threading]# python3 q.py reid betty linux Traceback (most recent call last): File "q.py", line 11, in <module> print(d.get(0)) File "/usr/local/python3/lib/python3.6/queue.py", line 161, in get raise Empty queue.Empty ###等待插入数据
(6)、队列的作用
a. 适用于多线程
分析:如果在单线程情况,阻塞住就没办法,直接使用列表更方便,但是一到多线程就有问题,涉及线程安全,列表的数据,那个线程都可以取,如列表中有1,2,3,4个数,两个线程同时取4这个数,都可以同时取得,这时就无法控制
但是在队列中使用时,多线程去取数不会造成同时取得一个数,因为队列中本身就有一把锁,在数据结构上加了一把锁,因为以后会经常的操作数据,还要不断的加锁
使用列表时,线程不安全
[root@node2 threading]# cat thread-list.py #模拟队列,先取,再删除 #!/usr/local/python3/bin/python3 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: print('---',a) t1=threading.Thread(target=pri,args=()) #两个线程同时去取,可能同时取得同一个数,造成线程不安全 t1.start() t2=threading.Thread(target=pri,args=()) t2.start() [root@node2 threading]# python3 thread-list.py 5 ##同时取得两个5 5 4 --- 5 4 3 --- 4 3 2 --- 3 2 1 --- 2 1 --- 1
实例1:三个生产者一个消费者,在有队列的情况下有一把锁 ,三个生产者不会造乱,保证数据安全
[root@node2 threading]# cat qinstance.py #!/usr/local/python3/bin/python3 import threading,queue from time import sleep from random import randint class Production(threading.Thread): def run(self): while True: r=randint(0,100) q.put(r) ##生产一个包子,有三个Production对象向里面生产包子,如果没有锁就会有问题,最大限制保证数据安全 print("produce %s baozi"%r) sleep(1) class Proces(threading.Thread): def run(self): while True: re=q.get() #消费者一直吃包子 print("eat %s baozi"%re) if __name__=="__main__": q=queue.Queue(10) threads=[Production(),Production(),Production(),Proces()] #创建4个线程对象,前三个是生产者,最后一个是消费者 for t in threads: t.start() [root@node2 threading]# python3 qinstance.py produce 100 baozi produce 80 baozi produce 62 baozi eat 100 baozi eat 80 baozi eat 62 baozi
实例2
[root@node2 threading]# cat qinstance1.py #!/usr/local/python3/bin/python3 import time,random import queue,threading q = queue.Queue() def Producer(name): count=0 while count<20: time.sleep(random.randrange(3)) q.put(count) print('Producer %s has produced %s baozi..' %(name,count)) count +=1 def Consumer(name): count=0 while count<20: time.sleep(random.randrange(4)) if not q.empty(): #不为空时 data = q.get() print(data) print('\033[32;1mConsumer %s has eat %s baozi...\033[0m' %(name,data)) else: print("------no baozi anymore ------") count +=1 p1 = threading.Thread(target=Producer,args=('A',)) c1 = threading.Thread(target=Consumer,args=('B',)) p1.start() c1.start() [root@node2 threading]# python3 qinstance1.py Producer A has produced 0 baozi.. Producer A has produced 1 baozi.. Producer A has produced 2 baozi.. 0 Consumer B has eat 0 baozi... Producer A has produced 3 baozi.. Producer A has produced 4 baozi.. 1 Consumer B has eat 1 baozi... 2 Consumer B has eat 2 baozi... 3 Consumer B has eat 3 baozi... 4 Consumer B has eat 4 baozi... Producer A has produced 5 baozi.. Producer A has produced 6 baozi.. 5 Consumer B has eat 5 baozi...