Python多段程和多过程(二) 进程同歩之互斥锁和

摘要:进程同歩是以便处理多段程程序编写中,因为市场竞争应用資源或改动自变量而导致数据信息不一致的难题举一个案子:# coding=utf-8import disdef add():global aa+=1return adef desc():global aa-=1retur...

进程同歩是以便处理多段程程序编写中,因为市场竞争应用資源或改动自变量而导致数据信息不一致的难题举一个案子:

# coding=utf-8
import dis
def add():
    global a
    a+=1
    return a
def desc():
    global a
    a-=1
    return a

 10           0 LOAD_GLOBAL              0 (a)
              2 LOAD_CONST               1 (1)
              4 INPLACE_ADD
              6 STORE_GLOBAL             0 (a)

 12           8 LOAD_GLOBAL              0 (a)
             10 RETURN_VALUE

 16           0 LOAD_GLOBAL              0 (a)
              2 LOAD_CONST               1 (1)
              4 INPLACE_SUBTRACT
              6 STORE_GLOBAL             0 (a)

 18           8 LOAD_GLOBAL              0 (a)
             10 RETURN_VALUE


大家查询一下 add() 和 desc() 这2个方式的字节数码。

能看出 add() 中 a+=1 能够由4行字节数码构成:
1. 将自变量a载入到运行内存
2. 将变量定义1载入到运行内存
3. 以内存中实行a+1实际操作
4. 将运行内存中的正中间值a+1取值给编码中的自变量a

desc()一样也是有这4步
1. 将自变量a载入到运行内存
2. 将变量定义1载入到运行内存
3. 以内存中实行a-1实际操作
4. 将运行内存中的正中间值a-1取值给编码中的自变量a

python在具体运作的情况下是一行行字节数码运作的。
假如上边将add()和desc()分成2个进程循环系统运作,便可能会出現这类状况:
一刚开始a=0,add()运作完第三行字节数码的情况下,時间片用完,运行内存中的正中间值是a+1=1。轮到desc()实行,而且desc()实行完后4行字节数码,这时脚本制作中a=-1。又轮到add()运作,实行第四行字节数码,将1赋给脚本制作自变量a,获得a=1

全部 add()和desc()各运作1次,应当a=0才对,具体上a=1或是a=-1。造成数据信息不一致。

而导致这一难题的压根缘故便是:字节数码1~4并不是一个分子实际操作。


进程同歩能够确保 a+=1和a-=1內部的字节数码的运作是一个分子实际操作,进而防止数据信息被改乱

 

 


同歩方法1:Lock互斥锁  和  RLock重入锁

 

Lock互斥锁


互斥锁能够确保锁内的市场竞争資源同一時刻只有被一个进程应用。一个进程想应用某一市场竞争資源,就需要先获得锁。假如資源已被进程B加了锁,那麼进程A想获得锁应用資源的情况下便会进到休眠状态,等候进程B释放出来锁。
最底层的关键点便是,进程A会进到休眠状态,积极让出CPU给进程B,让进程B实行完锁内的编码随后释放出来锁,进程A才会被唤起,而且取得锁对資源实际操作。
假如有好几个进程由于等候锁而进到休眠状态,这种进程会被放进一个等候序列中。优秀序列的进程能够先被唤起而获得锁和資源的应用。

进程加了互斥锁,就务必要释放出来锁,不然别的进程会一直等候。


锁的优势:
能够完成进程间同歩,确保进程安全性,資源的井然有序应用,自变量不容易被改乱。

锁的缺陷:
1.加锁和释放出来锁会耗费時间,因此锁会危害程序特性
2.锁将会会造成死链接


事例1:互斥锁的基本应用
应用互斥锁的經典事例:

# coding=utf-8
from threading import Thread,Lock
lock = Lock()   # 界定一个互斥锁
def add(lock):
    global a
    for i in range(1000000):
        lock.acquire()       # 加锁
        a+=1
        lock.release()       # 释放出来锁
def desc(lock):
    global a
    for i in range(1000000):
        lock.acquire()      # 加锁
        a-=1
        lock.release()      # 释放出来锁

PS: 
1、假如不用锁,获得的a并不是0,表明加锁能够确保实际操作的分子性,维护資源不被改乱
2、假如将加锁和释放出来锁放到for循环系统以外,这时一个进程会等候另外一个进程彻底实行完for才会能实行,非常于变为单进程,它是不正确的应用锁的方法。

 

 


事例2:仿真模拟死链接的二种状况

a.死链接的第一种状况,锁嵌套循环(在锁中嵌套循环的获得同一把锁)
 

from threading import Lock

    def __init__(self):         self.queue=[]   # 用目录仿真模拟一个序列,应用锁确保queue是进程安全性的         self.lock = Lock()  # 界定一个互斥锁     def size(self):         self.lock.acquire()         size = len(self.queue)         self.lock.release()     def get(self):         item = None         self.lock.acquire()         if self.size() 0:             item = self.queue.pop(0)         self.lock.release()     def put(self,item):         self.lock.acquire()         self.queue.append(item)         self.lock.release()
    if self.size() 0:
        item = self.queue.pop(0)
    self.lock.release()
   
self.size()里加了锁,在if self.size 0: 外边又加了同样的锁,因此同样的锁嵌套循环,便会导致里边的锁等候外边的锁释放出来。
可是释放出来锁的编码以内层锁的编码以后,而实行到里层锁的编码时就早已堵塞了,因而始终也不肯实行释放出来锁的编码。
結果一直堵塞。

简易的说便是:
lock1.acquire()
lock1.acquire()     # 该行产生死链接,一直堵塞
do_something()
lock1.release()
lock1.release()

这类状况毫无疑问会死链接

 

 


b.死链接的第二种状况,互相等候資源的锁释放出来。
倘若有进程A,B,資源m,n,锁X,Y 。进程A,B都是采用資源m,n,应用資源m要用锁X维护,应用資源n要用锁Y维护
A的逻辑性规定先实际操作m再实际操作n,并且m和n的实际操作具备分子性
B的逻辑性规定先实际操作n再实际操作m,并且m和n的实际操作具备分子性

# coding=utf-8
from threading import Thread,Lock
lock_X = Lock()
lock_Y = Lock()
m = 0
n = 0
def task1():
    global m,n,lock_X,lock_Y
    for i in range(100000):
        lock_X.acquire()
        m+=1                    # 1
        print("task1 add m")
        lock_Y.acquire()        # 2
        n+=1
        print("task1 add n")
        lock_Y.release()
        lock_X.release()
def task2():
    global m,n,lock_X,lock_Y
    for i in range(100000):
        lock_Y.acquire()
        n-=1                    # 3
        print("task2 desc n")
        lock_X.acquire()        # 4
        m-=1
        print("task2 desc m")
        lock_X.release()
        lock_Y.release()

全过程: task1运作到#1時间片用完,然后CPU转换到task2,task2运作完#3,如今要运作#4,可是锁X被task1获得因此task2要等候锁X释放出来。因此CPU转换会task1,从上一次#1的地区再次实行,实行到 #2发觉锁Y早已被task2获得,因此等候task1释放出来锁Y。

結果:task1等候task2释放出来锁Y,task2等候task1释放出来锁X。彼此互相等候,产生死链接。

状况a和状况b的差别:a是同一把锁嵌套循环,锁等候自身这把锁导致死链接;b是两把不一样的锁嵌套循环,导致互相等候导致死链接

 


事例3:多段程的井然有序开展
 

# coding=utf-8
lock1 = Lock()
lock2 = Lock()
lock3 = Lock()
lock2.acquire()
lock3.acquire()
def task1():
    while True:
        lock1.acquire()
        print("task1")
        sleep(0.5)
        lock2.release()
def task2():
    while True:
        lock2.acquire()
        print("task2")
        sleep(0.5)
        lock3.release()
def task3():
    while True:
        lock3.acquire()
        print("task3")
        sleep(0.5)
        lock1.release()

这一事例的趣味的地方取决于
1.三个进程高并发,但同一時间仅有一把锁是处在释放出来情况,别的2个锁是处在锁住情况。
2.輸出数据信息时候加锁,輸出完以后却会释放出来另外一个进程要获得的锁而并不是释放出来自身的这把锁。这寓意着,这一进程实行完以后,仅有要获得 上一个进程释放出来了的锁 的进程能实行,别的进程不可以实行(由于这种进程想获得的锁被别的进程给锁定了)。根据当中方法能够特定进程的实行次序。

上边的进程是沒有具体实际意义的,由于三个进程非常因此串行通信实行的,对比于单进程无需转换无需加锁和释放出来锁,那样串行通信的多段程高效率反倒比单进程还低些

 


事例4:
大家的目地是那样的:
有4个目录list1~4,list1包括原始原素,list2~4是空的。
希望对list1中的数据信息x开展下列计算 (x+1)*2-3 

原素从list1弹出来,+1,再放进list2,应用进程1进行 |
原素从list2弹出来,*2,再放进list3,应用进程2进行 |--这三个全过程是(高并发的)同时产生的
原素从list3弹出来,-3,再放进list4,应用进程3进行 |

下边大家方案一下,什么全过程必须加锁,什么全过程无需加锁
互斥锁是以便处理資源市场竞争,因此大家必须对进程共享资源的資源加锁就可以。
list1仅有进程1采用了,list4仅有进程3采用了,因此对这2个list的实际操作无需加锁
list2会被进程1和进程22个进程采用,因此进程1,2在实际操作list2时得加锁
list3同样,进程2,3实际操作list3时要得加锁。

list1~3取下来的原素必须开展算术计算,而算术计算是由一个进程独立进行,因此每一个原素计算的情况下总是被一个进程应用,因此原素开展算术计算的全过程不用加锁。
 

# coding=utf-8
from threading import Thread,Lock
from time import sleep,time
lock1 = Lock()
lock2 = Lock()
list2=[]
list3=[]
list4=[]
# 将原素从list1取下,开展+1解决,放进list2
def task1():
    while len(list1):
        item = list1.pop()  # 取下原素
        item+=1
        # 放进list2时要对list2锁上
        lock1.acquire()
        list2.append(item)
        print("task1 append %d" % item)
        lock1.release()

                         # 将原素放进list3要对list3锁上,并且由于list2和list3是2个不一样的資源,因此要用另外一个锁来锁定list3             lock2.acquire()             list3.append(item)             print("task2 append %d" % item)             lock2.release()         else:             sleep(0.0001) # 将原素从list3取下,开展-3解决,放进list4 def task3():     while True:         if len(list3):             lock2.acquire()             item = list3.pop()             print("task3 pop %d" % item)             lock2.release()             item-=3     # 这句话无需放进锁内             list4.append(item)             print("task3 append %d" % item)         else:             sleep(0.0001)


在应用互斥锁的情况下,多段程里加了锁的那段编码是串行通信的。实际上在写程序的情况下,我一直会想全部运作全过程什么全过程是能够同时产生的(高并发的),什么全过程是串行通信的,由于能够同时实行的地区便是多段程比单进程提高了高效率的地区,而串行通信的地区其高效率和单进程一样的。

实际上大家仅用了解什么地区是沒有高并发的便可以了,了解了什么地区是沒有高并发的,那麼别的全部地区全是高并发的

比如:我觉得了解 原素A压进list2,早已从list2弹出来的原素B压进list3,早已从list3弹出来的原素C压进list4 这三个姿势是能够同时产生的吗?
能够。由于从上边加锁的地区了解:原素A压进list2和原素B从list2弹出来不是能同时开展的,原素B压进list3和原素C弹出来list3不可以同时开展。别的实际操作都可以以同时产生。

小结:程序中只需要对好几个进程共享资源的資源开展锁上,对私有的資源不用锁上

 

 

RLock 重入锁

重入锁的特性:
能够容许同一个锁开展嵌套循环,可是释放出来锁的频次一定要相当于获得锁的频次。
一个进程内的重入锁能够嵌套循环而不堵塞,可是进程间应用同一个重入锁就和互斥锁一样会堵塞

比如 将死链接状况a中的Lock换为RLock重入锁,程序也不会死链接

from threading import RLock

    def __init__(self):         self.queue=[]   # 用目录仿真模拟一个序列,应用锁确保queue是进程安全性的         self.lock = RLock()  # 界定一个互斥锁     def size(self):         self.lock.acquire()         size = len(self.queue)         self.lock.release()         return size     def get(self):         item = None         self.lock.acquire()         if self.size() 0:             item = self.queue.pop(0)         self.lock.release()         return item     def put(self,item):         self.lock.acquire()         self.queue.append(item)         self.lock.release()
        self._block = _allocate_lock()      # 获得一把互斥锁         self._owner = None                  # 用以纪录RLock启用acquire()时的进程,是一个整数金额         self._count = 0                     # 纪录某一个进程启用了几回 RLock.acquire()              def acquire(self, blocking=True, timeout=-1):         me = get_ident()        # 获得当今进程的唯一标志,是一个整数金额         if self._owner == me:   # 假如是本进程第二次之上的启用 RLock.acquire() 那麼只把电子计数器+1,可是不容易启用互斥锁self._block的acquire();但假如是其他进程第二次之上的启用RLock.acquire() 那麼便会再启用一次互斥锁self._block的acquire(),该进程进到休眠状态等候情况             self._count += 1             return 1         rc = self._block.acquire(blocking, timeout)   # 进程第一次启用 RLock.acquire()会启用互斥锁的 acquire()         if rc:          # # 进程第一次启用 RLock.acquire()会进到此条件             self._owner = me             self._count = 1         return rc     __enter__ = acquire     def release(self):         if self._owner != get_ident():             raise RuntimeError("cannot release un-acquired lock")         self._count = count = self._count - 1       # 每启用一次RLock.release()便会将电子计数器-1         if not count:       # 仅有当启用 RLock.release()的频次相当于RLock.acquire()的频次才会真实启用 self._block的release()释放出来锁。             self._owner = None             self._block.release()     


小结: RLock是根据互斥锁和电子计数器完成的

张柏沛IT技术性blog > Python多段程和多过程(二) 进程同歩之互斥锁和重入锁

点一下拷贝转截该一篇文章



联系我们

全国服务热线:4000-399-000 公司邮箱:343111187@qq.com

  工作日 9:00-18:00

关注我们

官网公众号

官网公众号

Copyright?2020 广州凡科互联网科技股份有限公司 版权所有 粤ICP备10235580号 客服热线 18720358503