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...

  

 

posted @ 2018-07-03 10:37  Reid21  阅读(225)  评论(0编辑  收藏  举报