zoukankan      html  css  js  c++  java
  • Python线程的常见的lock

    IO阻塞分析:

    下面该需求很简单将一个数值100做自减处到0.主函数中有0.1秒的IO阻塞

    import threading
    import time
    def sub():
        global num   #  掌握为什么加global num
        temp=num
        time.sleep(0.1)
        num=temp-1
    
        time.sleep(2)
    num=100
    l=[]
    for i in range(100):       
        t=threading.Thread(target=sub,args=())
        t.start()
        l.append(t)
    for t in l:        #100个线程的IO阻塞
        t.join()
    print(num)
    
    "D:Program Filespython.exe" E:/py_code/多线程/互斥锁.py
    99
    
    Process finished with exit code 0
    
    
    

    分析:
    通过代码我们可以看到因为主函数中增加了一段IO阻塞的代码,所以我们考虑用到了线程。
    1.因为Python GIL的特性原因,对于多线程并不能真正的实现并行而只能并发
    2.当第一个线程抢到了GIL后(temp=100),其他的线程暂时不可以执行,但当第一个线程执行到time.sleep(0.1)时(IO阻塞),此时cup是闲置的,第二个线程可以抢GIL。

    3.因为第一个线程取到temp=100还没有执行到num=temp-1所以此时第二个线程取到的值还是100,接着也执行到time.sleep(0.1),同理第三个线程抢到GIL。依次类推可以以知道这一百个线程在执行到time.sleep(0.1)之前得到的值都是100,当0.1秒过后全部都依次执行了num=temp-1,所以得出的结果是99.

    所以很显然上面的代码没办法得到我们想要的结果,造成的原因就是IO阻塞和GIL相互作用的原因,所以下面会有另外的途径去解决

    互斥锁

    
    import threading
    import time
    def sub():
        global num   #  掌握为什么加global num
        lock.acquire()    #加锁直到释放锁下个线程才可以抢锁
        temp=num
        time.sleep(0.1)
        num=temp-1
        lock.release()    #释放锁下个线程可以抢锁了
        time.sleep(2)
    num=100
    l=[]
    lock=threading.Lock()   #生成锁对象
    for i in range(100):       #生成100个线程
        t=threading.Thread(target=sub,args=())
        t.start()
        l.append(t)
    for t in l:        #100个线程的IO阻塞
        t.join()
    print(num)
    
    
    "D:Program Filespython.exe" E:/py_code/多线程/互斥锁.py
    0
    
    Process finished with exit code 0
    

    分析:

    1.全局定义一把锁,因为进程处理的这个数据num是个公共数据,100个线程进行处理
    2.主程序中处理数据的代码只有下面的这三行:

    temp=num
    time.sleep(0.1)
    num=temp-1
    

    所以将这三行代码锁起来,使其在执行完之前其他的线程无法进行干预和取值执行。

    3.所以将代码前加锁,数据做完运算后释放锁。当锁释放后其他线程就可以去抢占这把锁。

    4.线程的运行流程是:
    第一个线程抢到GIL,并拿到值temp=100,开始执行代码,当执行到lock.acquire() 时,下面的代码加锁运行,一直执行到time.sleep(0.1),遇到IO阻塞,第二个线程开始抢GIL,抢到后执行代码执行到lock.acquire(),此时该锁已经被第一个线程抢占还未释放,所以第二个线程只能等第一个线程释放。

    5.当0.1s过后第一个线程继续执行代码num=temp-1,此时temp=99,接着执行到lock.release(),锁释放第二个线程抢到开始执行temp=num,而此时的temp=99,接着执行到time.sleep(0.1),第三个线程抢占GIL,但因为第二个线程未释放定义锁,所以无法继续执行,此时第二个线程执行num=temp-1,此时的num=98.依此类推接下来的线程均依据进行运行。所以最后的执行结果等于0

    递归锁

    死锁:

    所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程

    import threading
    import time
    class MyThread(threading.Thread):
        def __init__(self):
            threading.Thread.__init__(self)
        def run(self):
            self.foo()
            self.bar()
        def foo(self):
            LockA.acquire()
            print("I am %s GET LOCKA---->%s"%(self.name,time.ctime()))
            LockB.acquire()
            print("I am %s GET LOCKB---->%s"%(self.name,time.ctime()))
            LockB.release()
            LockA.release()
        def bar(self):
            #time.sleep(2)
            LockB.acquire()
            print("I am %s GET LOCKB---->%s"%(self.name,time.ctime()))
            LockA.acquire()
            print("I am %s GET LOCKA---->%s"%(self.name,time.ctime()))
            LockA.release()
            LockB.release()
    LockA=threading.Lock()
    LockB=threading.Lock()
    for i in range(10):
        t=MyThread()
        t.start()
    
    "D:Program Filespython.exe" E:/py_code/多线程/互斥锁.py
    I am Thread-1 GET LOCKA---->Tue Jul 18 18:46:27 2017
    I am Thread-1 GET LOCKB---->Tue Jul 18 18:46:27 2017
    I am Thread-1 GET LOCKB---->Tue Jul 18 18:46:27 2017
    I am Thread-2 GET LOCKA---->Tue Jul 18 18:46:27 2017
    
    

    分析:
    1.上述的代码定义了两把锁LockA和LockB,并定义一个类,类中继承线程,则MyThread中就可以调用线程的所有方法。

    2.在foo函数中在A锁中嵌套了B锁这这里第一个线程进入执行的逻辑是,执行第一个函数foo,执行第一个打印然后启动B锁执行第二个打印释放B锁释放A锁进入到bar函数,于此同时第二个线程进入到要执行foo,当第一个线程释放A锁的那一刻,第二个线程马上取到A锁,但是此时第一个线程已经进入到bar函数也同一时刻取到B锁执行第一个bar函数的第一个打印,当其执行到启动A锁时就会卡住,因为此时的A锁已经被第二个线程拿到,同理第二个线程此时在foo函数中想启动B锁,但是B锁已经被第一个线程拿到,所以这个时候整个进程就会卡住

    3.这是在互斥锁中常见的问题,所以我们需要引进第二把锁递归锁:

    递归锁

    在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁

    import threading
    import time
    class MyThread(threading.Thread):
        def __init__(self):
            threading.Thread.__init__(self)
        def run(self):
            self.foo()
            self.bar()
        def foo(self):
            RLock.acquire()
            print("I am %s GET LOCKA---->%s"%(self.name,time.ctime()))
            RLock.acquire()
            print("I am %s GET LOCKB---->%s"%(self.name,time.ctime()))
            RLock.release()
            RLock.release()
        def bar(self):
    
            RLock.acquire()
            print("I am %s GET LOCKB---->%s"%(self.name,time.ctime()))
            RLock.acquire()
            print("I am %s GET LOCKA---->%s"%(self.name,time.ctime()))
            RLock.release()
            RLock.release()
    
    RLock=threading.RLock()
    for i in range(10):
        t=MyThread()
        t.start()
    
    
    
     "D:Program Filespython.exe" E:/py_code/多线程/互斥锁.py
    I am Thread-1 GET LOCKA---->Tue Jul 18 19:19:17 2017
    I am Thread-1 GET LOCKB---->Tue Jul 18 19:19:17 2017
    I am Thread-1 GET LOCKB---->Tue Jul 18 19:19:17 2017
    I am Thread-1 GET LOCKA---->Tue Jul 18 19:19:17 2017
    I am Thread-2 GET LOCKA---->Tue Jul 18 19:19:17 2017
    I am Thread-2 GET LOCKB---->Tue Jul 18 19:19:17 2017
    I am Thread-2 GET LOCKB---->Tue Jul 18 19:19:17 2017
    I am Thread-2 GET LOCKA---->Tue Jul 18 19:19:17 2017
    I am Thread-4 GET LOCKA---->Tue Jul 18 19:19:17 2017
    I am Thread-4 GET LOCKB---->Tue Jul 18 19:19:17 2017
    I am Thread-4 GET LOCKB---->Tue Jul 18 19:19:17 2017
    I am Thread-4 GET LOCKA---->Tue Jul 18 19:19:17 2017
    I am Thread-5 GET LOCKA---->Tue Jul 18 19:19:17 2017
    I am Thread-5 GET LOCKB---->Tue Jul 18 19:19:17 2017
    I am Thread-5 GET LOCKB---->Tue Jul 18 19:19:17 2017
    I am Thread-5 GET LOCKA---->Tue Jul 18 19:19:17 2017
    I am Thread-6 GET LOCKA---->Tue Jul 18 19:19:17 2017
    I am Thread-6 GET LOCKB---->Tue Jul 18 19:19:17 2017
    I am Thread-6 GET LOCKB---->Tue Jul 18 19:19:17 2017
    I am Thread-6 GET LOCKA---->Tue Jul 18 19:19:17 2017
    I am Thread-3 GET LOCKA---->Tue Jul 18 19:19:17 2017
    I am Thread-3 GET LOCKB---->Tue Jul 18 19:19:17 2017
    I am Thread-3 GET LOCKB---->Tue Jul 18 19:19:17 2017
    I am Thread-3 GET LOCKA---->Tue Jul 18 19:19:17 2017
    I am Thread-8 GET LOCKA---->Tue Jul 18 19:19:17 2017
    I am Thread-8 GET LOCKB---->Tue Jul 18 19:19:17 2017
    I am Thread-8 GET LOCKB---->Tue Jul 18 19:19:17 2017
    I am Thread-8 GET LOCKA---->Tue Jul 18 19:19:17 2017
    I am Thread-9 GET LOCKA---->Tue Jul 18 19:19:17 2017
    I am Thread-9 GET LOCKB---->Tue Jul 18 19:19:17 2017
    I am Thread-9 GET LOCKB---->Tue Jul 18 19:19:17 2017
    I am Thread-9 GET LOCKA---->Tue Jul 18 19:19:17 2017
    I am Thread-7 GET LOCKA---->Tue Jul 18 19:19:17 2017
    I am Thread-7 GET LOCKB---->Tue Jul 18 19:19:17 2017
    I am Thread-7 GET LOCKB---->Tue Jul 18 19:19:17 2017
    I am Thread-7 GET LOCKA---->Tue Jul 18 19:19:17 2017
    I am Thread-10 GET LOCKA---->Tue Jul 18 19:19:17 2017
    I am Thread-10 GET LOCKB---->Tue Jul 18 19:19:17 2017
    I am Thread-10 GET LOCKB---->Tue Jul 18 19:19:17 2017
    I am Thread-10 GET LOCKA---->Tue Jul 18 19:19:17 2017
    
    Process finished with exit code 0
    
    

    分析:

    1.上述的代码在全局定义了一把递归锁

    2.当第一个线程进入执行时先进入到foo函数,启动第一把锁并记录一次接着执行第一个打印,然后启动第二把锁执行第二次打印并释放两把锁。此时包括第一个线程在内这10个线程都能进行抢锁,这里RLock采取的就近原则,所以还是第一个线程抢到并进入到bar函数,此时其他线程是抢不到锁只能继续等待线程一释放,线程一顺序执行完bar函数并释放RLock锁,此刻线程一已经完整的执行完整个工作,并且线程2抢到锁执行过程和线程一相同。

    3.依次类推这10个线程会先后执行完各自的工作。如上述代码执行的结果所示

    信号量

    Semaphore管理一个内置的计数器,
    每当调用acquire()时内置计数器-1;
    调用release() 时内置计数器+1;
    计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release()

    import threading
    import time
    semaphore=threading.Semaphore(5)
    
    def foo():
        semaphore.acquire()
        time.sleep(2)
        print("ok")
        semaphore.release()
    
    
    for i in range(100):
        t=threading.Thread(target=foo,args=())
        t.start()
        
    

    分析:

    1.全局定义一个信号量,并限制最大接入的线程数为5个

    2.执行逻辑,当第一个线程进入后semaphore内置计数减1,则这里可以接收4个线程,此时第一个线程打印第一个OK,接着第二个线程进入打印第二个OK,一直到第五个线程进入后,这时第六个线程在第五个线程没有执行完semaphore.release()时是不可以进入的,所以按照这个逻辑,该端代码的执行结果是每5个OK打印一次一共打印100个OK.

  • 相关阅读:
    iPhone网络编程初体验
    va_list实现不定参数C函数
    iphone网络编程总结(链接)
    一周好文(4)
    10个迷惑新手的Cocoa&Objectivec开发问题
    Flash通过Action Script调用C++接口
    easyui表单对numberbox控件进行赋值需要注意
    asp.net生成缩略图及给原始图加水印
    用超链接在页面之间传值的注意事项(来自:http://www.souzz.net)
    超酷的分类导航代码
  • 原文地址:https://www.cnblogs.com/lijian-22huxiaoshan/p/7202512.html
Copyright © 2011-2022 走看看