Python3基础-GIL、锁、信号量、队列

并发并行与同步异步

并发: 是指系统具有处理多个任务(动作)的能力
并行: 是指系统具体同时处理多个任务(动作)的能力

并发和并行的区别。它们虽然都说是"多个进程同时运行",但是它们的"同时"不是一个概念。并行的"同时"是同一时刻可以多个进程在运行(处于running),
并发的"同时"是经过上下文快速切换,使得看上去多个进程同时都在运行的现象,是一种OS欺骗用户的现象。 同步:当进程执行到一个IO(等待外部数据)的时候---------等: 同步 eg 打电话 异步:当进程执行到一个IO(等待外部数据)的时候 --------不等:一直等到数据接收成功,再回来处理 eg 发短信
  • python的GIL

  • In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)
  • 核心意思:因为有GIL, 所以无论启动多少个线程,有多少个cpu 。  python在执行的时候会淡定的在同一个时刻只允许一个线程运行

对于IO密集型的任务:python 的多线程是有意义的

                                      可以采用多进程+协程

对于计算密集型的任务:python 的多线程就不推荐,python就不适用了

在托管代码中,我们可以调用静态方法 Thread.Sleep。
分析:

Sleep 接口均带有表示睡眠时间长度的参数 timeout。调用以上提到的 Sleep 接口,会有条件地将调用线程从当前处理器上移除,并且有可能将它从线程调度器的可运行队列中移除。
这个条件取决于调用 Sleep 时timeout 参数。 当 timeout
= 0, 即 Sleep(0),如果线程调度器的可运行队列中有大于或等于当前线程优先级的就绪线程存在,操作系统会将当前线程从处理器上移除,调度其他优先级高的就绪线程运行;
如果可运行队列中的没有就绪线程或所有就绪线程的优先级均低于当前线程优先级,那么当前线程会继续执行,就像没有调用 Sleep(0)一样。 当 timeout
> 0 时,如:Sleep(1),会引发线程上下文切换:调用线程会从线程调度器的可运行队列中被移除一段时间,这个时间段约等于 timeout 所指定的时间长度。
为什么说约等于呢?是因为睡眠时间单位为毫秒,这与系统的时间精度有关。通常情况下,系统的时间精度为 10 ms,那么指定任意少于 10 ms但大于 0 ms 的睡眠时间,均会向上求值为 10 ms。 而调用 SwitchToThread() 方法,如果当前有其他就绪线程在线程调度器的可运行队列中,始终会让出一个时间切片给这些就绪线程,而不管就绪线程的优先级的高低与否。 结论: 由上面的分析可以看出,如果我们想让当前线程真正睡眠一下子,最好是调用 Sleep(1) 或 SwitchToThread()。
  • 同步锁

import time
import threading

def addNum():
    global num #在每个线程中都获取这个全局变量
    temp=num
    time.sleep(1)
    num =temp-1 #对此公共变量进行-1操作
    #print('num==',num)

num = 100  #设定一个共享变量
thread_list = []
for i in range(100):
    print("i==",i)
    t = threading.Thread(target=addNum)
    t.start()
    thread_list.append(t)

for t in thread_list: #等待所有线程执行完毕
    t.join()

print('final num:', num )

观察:time.sleep(1)/0.1  /0.001/0.0000001 结果分别是多少?

  • time.sleep(1)     99
  • time.sleep(0.1)    99
  • time.sleep(0.001)  随时变化 89 90  91
  • time.sleep(0.0000001 )  随时变化 89 90  91
  • #time.sleep( )    0

多个线程都在同时操作同一个共享资源,所以造成了资源破坏,怎么办呢?(join会造成串行,失去所线程的意义)

用同步锁解决该问题

R=threading.Lock()

def addNum():
    global num #在每个线程中都获取这个全局变量
    R.acquire()
    temp=num
    time.sleep(1)
    num =temp-1 #对此公共变量进行-1操作
    #print('num==',num)
    R.release()

观察:time.sleep(1)/0.1  /0.001/0.0000001 结果分别是多少?

  • time.sleep(1)     0
  • time.sleep(0.1)    0
  • time.sleep(0.001)  0
  • time.sleep(0.0000001 )  0
  • 线程死锁和递归锁 

在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁,因为系统判断这部分资源都正在使用,所有这两个线程在无外力作用下将一直等待下去。下面是一个死锁的例子:

 

import threading,time

class myThread(threading.Thread):
    def doA(self):
        lockA.acquire()
        print(self.name,"DoAgotlockA",time.ctime())
        time.sleep(3)
        lockB.acquire()
        print(self.name,"DoAgotlockB",time.ctime())
        lockB.release()
        lockA.release()

    def doB(self):
        lockB.acquire()
        print(self.name,"DoBgotlockB",time.ctime())
        time.sleep(2)
        lockA.acquire()
        print(self.name,"DoBgotlockA",time.ctime())
        lockA.release()
        lockB.release()
    def run(self):
        self.doA()
        self.doB()
if __name__=="__main__":

    lockA=threading.Lock()
    lockB=threading.Lock()
    threads=[]
    for i in range(5):
        print("i===",i)
        threads.append(myThread())
    for t in threads:
        t.start()
    for t in threads:
        t.join()#等待线程结束,后面再讲。
线程死锁

 

执行结果如下:
Thread-1 DoAgotlockA Tue Sep  1 17:37:24 2020
Thread-1 DoAgotlockB Tue Sep  1 17:37:27 2020
Thread-1 DoBgotlockB Tue Sep  1 17:37:27 2020
Thread-2 DoAgotlockA Tue Sep  1 17:37:27 2020

解决办法:使用递归锁

   lockA=   threading.Lock()

   lockB= threading.Lock()                ---》lock=threading.RLock()

为了支持在同一线程中多次请求同一资源,python提供了“可重入锁”:threading.RLock。RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次acquire。直到一个线程所有的acquire都被release,其他的线程才能获得资源。

  • 同步条件 Event

python线程的事件用于主线程控制其他线程的执行,事件主要提供了三个方法wait、clear、set

事件处理的机制:全局定义了一个“Flag”,如果“Flag”值为 False,那么当程序执行 event.wait 方法时就会阻塞,如果“Flag”值为True,那么event.wait 方法时便不再阻塞。

  • clear:将“Flag”设置为False
  • set:将“Flag”设置为True

    event.wait(timeout=None):调用该方法的线程会被阻塞,如果设置了timeout参数,超时后,线程会停止阻塞继续执行;
    event.set():将event的标志设置为True,调用wait方法的所有线程将被唤醒;
    event.clear():将event的标志设置为False,调用wait方法的所有线程将被阻塞;
    event.isSet():判断event的标志是否为True。

import threading,time
class Boss(threading.Thread):
    def run(self):
        print("BOSS:今晚大家都要加班到22:00。")
        print(event.isSet())
        event.set()
        time.sleep(5)
        print("BOSS:<22:00>可以下班了。")
        print(event.isSet())
        event.set()
class Worker(threading.Thread):
    def run(self):
        event.wait()
        print("Worker:哎……命苦啊!")
        time.sleep(1)
        event.clear()
        event.wait()
        print("Worker:OhYeah!")
if __name__=="__main__":
    event=threading.Event()
    threads=[]
    for i in range(5):
        threads.append(Worker())
    threads.append(Boss())
    for t in threads:
        t.start()
    for t in threads:
        t.join()
View Code

 

  •  信号量(Semaphore)

 信号量用来控制线程并发数的,BoundedSemaphore或Semaphore管理一个内置的计数器,每当调用acquire()时-1,调用release()时+1。

  计数器不能小于0,当计数器为0时,acquire()将阻塞线程至同步锁定状态,直到其他线程调用release()。(类似于停车位的概念)

      BoundedSemaphore与Semaphore的唯一区别在于前者将在调用release()时检查计数器的值是否超过了计数器的初始值,如果超过了将抛出一个异常。

import threading,time
class myThread(threading.Thread):
    def run(self):
        if semaphore.acquire():
            print(self.name)
            time.sleep(5)
            semaphore.release()
if __name__=="__main__":
    semaphore=threading.Semaphore(5)
    thrs=[]
    for i in range(100):
        thrs.append(myThread())
    for t in thrs:
        t.start()
View Code
  • 队列

queue列队类的方法

创建一个“队列”对象
import queue #这个queue模块是先进先出
q = queue.Queue(maxsize = 10)
queue.Queue类即是一个队列的同步实现。队列长度可为无限或者有限。可通过Queue的构造函数的可选参数maxsize来设定队列长度。如果maxsize小于1就表示队列长度无限。

将一个值放入队列中
q.put(10)
调用队列对象的put()方法在队尾插入一个项目。put()有两个参数,第一个item为必需的,为插入项目的值;第二个block为可选参数,默认为
1。如果队列当前为空且block为1,put()方法就使调用线程暂停,直到空出一个数据单元。如果block为0,put方法将引发Full异常。

将一个值从队列中取出
q.get()
调用队列对象的get()方法从队头删除并返回一个项目。可选参数为block,默认为True。如果队列为空且block为True,get()就使调用线程暂停,直至有项目可用。如果队列为空且block为False,队列将引发Empty异常。

Python Queue模块有三种队列及构造函数:
1、Python Queue模块的FIFO队列先进先出。   class queue.Queue(maxsize)
2、LIFO类似于堆,即先进后出。               class queue.LifoQueue(maxsize)
3、还有一种是优先级队列级别越低越先出来。        class queue.PriorityQueue(maxsize)

此包中的常用方法(q = Queue.Queue()):
q.qsize() 返回队列的大小
q.empty() 如果队列为空,返回True,反之False
q.full() 如果队列满了,返回True,反之False
q.full 与 maxsize 大小对应
q.get([block[, timeout]]) 获取队列,timeout等待时间
q.get_nowait() 相当q.get(False)
非阻塞 q.put(item) 写入队列,timeout等待时间
q.put_nowait(item) 相当q.put(item, False)
q.task_done() 在完成一项工作之后,q.task_done() 函数向任务已经完成的队列发送一个信号
q.join() 实际上意味着等到队列为空,再执行别的操作
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()
View Code
#先进先出模式
import  queue
q = queue.Queue(maxsize = 2)
q.put(10)
q.put("hello")

while 1:
    data = q.get()
    print(data)

===10
hello
先进先出模式
#先进后出模式
import  queue
q = queue.LifoQueue(maxsize = 2)
q.put(10)
q.put("hello")

while 1:
    data = q.get()
    print(data)

====
hello
10
先进后出模式
import  queue
q = queue.PriorityQueue()
q.put([3,12])
q.put([2,"hello"])
q.put([4,{"name":"yuan"}])

while 1:
    data = q.get()
    print(data)

===============
[2, 'hello']
[3, 12]
[4, {'name': 'yuan'}]
优先级
==》吃完再生产
def Producer(name):
  count = 1
  while count <10:
    print("生产者开始生产包子.......")
    time.sleep(random.randrange(3))
    q.put(count)
    print('Producer %s has produced %s baozi..' %(name, count))
    count +=1
    #q.task_done()
    print("hahhhhhh--等待生产包子")
    q.join()
    print("ok......")
def Consumer(name):
  count = 0
  while count <10:
    time.sleep(random.randrange(4))
    #print("wait----")
    #q.join()
    data = q.get()
    time.sleep(4)
    print("eating----")
    q.task_done()
    print(data)
    print('\033[32;1mConsumer %s has eat %s baozi...\033[0m' %(name, data))
    count +=1


p1 = threading.Thread(target=Producer, args=('A',))
c1 = threading.Thread(target=Consumer, args=('B',))
c2 = threading.Thread(target=Consumer, args=('C',))
c3 = threading.Thread(target=Consumer, args=('D',))

p1.start()
c1.start()
c2.start()
c3.start()
生产者与消费者1
#生产完再吃
import time,random
import queue,threading

q = queue.Queue()

def Producer(name):
  count = 1
  while count <10:
    print("生产者开始生产包子.......")
    time.sleep(random.randrange(3))
    q.put(count)
    print('Producer %s has produced %s baozi..' %(name, count))
    count +=1
    q.task_done()
    print("hahhhhhh--等待生产包子")
    #q.join()
    print("ok......")
def Consumer(name):
  count = 0
  while count <10:
    time.sleep(random.randrange(4))
    print("wait----")
    q.join()
    data = q.get()
    time.sleep(4)
    #print("eating----")
    #q.task_done()
    print(data)
    print('\033[32;1mConsumer %s has eat %s baozi...\033[0m' %(name, data))
    count +=1


p1 = threading.Thread(target=Producer, args=('A',))
c1 = threading.Thread(target=Consumer, args=('B',))
c2 = threading.Thread(target=Consumer, args=('C',))
c3 = threading.Thread(target=Consumer, args=('D',))

p1.start()
c1.start()
c2.start()
c3.start()
生产者与消费者2

 

posted @ 2020-09-15 16:24  槑槑DE  阅读(123)  评论(0)    收藏  举报