Python3.x:threading module线程锁、递归

作者: james_chang | 来源:发表于2018-03-14 17:54 被阅读22次

    线程锁

    大家已经知道了同一个进程下的线程数据之间可以共享,也知道多线程中有GIL锁,一个时刻只有一线程在运行,所以说就是有很多线程在修改这些共享数据,那么不是同时运行的话修改数据会不会出现错误呢?在Python2中就会出现这种情况,当你开启了很多条线程,然后这些线程一起修改全局变量时,最后得出的结果可能跟期望的不太一样

    在Python2中出现的错误情况

    在这个时候Python就提供了另一把锁,给用户的锁,叫做线程锁,可以在多个线程操作共享数据时更加有规律,来防止操作数据失误的情况出现,下面我们就了解一下如何使用线程锁

    import threading
    
    
    def run():
        # 获取锁
        lock.acquire()
        # 声明全局变量num
        global num
        # num+=1
        num += 1
        # 释放锁
        lock.release()
    
    
    # 生成线程锁实例
    lock = threading.Lock()
    num = 0
    
    # 开启1000个线程
    for i in range(1000):
        t = threading.Thread(target=run)
        t.start()
    
    print("-----all thread has finshed")
    print("num:", num)
    

    这里要注意的事获取锁与释放锁之间的这一段所操作的数据量不是很大,如果数据量很大,需要的时间很多,那么程序就会变成串行

    在Python3中这种情况已经不会出现了,但是在2中的这种情况与处理方法还是要了解,并且,在操作共享数据时,不管Python2还是Python3,都要加上线程锁,这是最好的做法

    递归锁

    接下来我们去了解另一种情况,就是锁中锁(递归锁),我们分出一个线程,使用线程锁之后里面再调用别的函数,然后调用的这个函数中再使用线程锁的话就不能再使用Lock()实例了,不然就会出现死循环错误,这是因为一把锁对应一个钥匙,锁里面再加一把锁就会导致程序分不清哪把钥匙开哪把锁,导致程序一直在锁中出不来,如下

    import threading
    
    
    def run1():
        print("grab the first part data")
        # 获取锁
        lock.acquire()
        global num
        num += 1
        # 释放锁
        lock.release()
        return num
    
    
    def run2():
        print("grab the second part data")
        # 获取锁
        lock.acquire()
        global num2
        num2 += 1
        # 释放锁
        lock.release()
        return num2
    
    
    def run3():
        # 获取锁
        lock.acquire()
        # 去跑run1
        res = run1()
        print('--------between run1 and run2-----')
        # 去跑run2
        res2 = run2()
        # 释放锁
        lock.release()
        print(res, res2)
    
    
    if __name__ == '__main__':
        # 初始化两个为0的变量
        num, num2 = 0, 0
        # 生成lock实例
        lock = threading.Lock()
        # 开始10个线程
        for i in range(10):
            # 线程跑的是run3
            t = threading.Thread(target=run3)
            t.start()
    
    # 判读是否有多个线程,有多个就继续打印,只剩一个说明子线程都执行完了,只剩主线程了,然后跳出循环程序结束
    # 可以用之前学到的join()方法来实现一样的效果
    while threading.active_count() != 1:
        print(threading.active_count())
    else:
        print('----all threads done---')
        print(num, num2)
    
    程序进入死循环

    可以看到,一直打印线程数11,说明一直有11个线程在活跃,说明分出的10个线程一直在运行不结束,这个时候就不能使用Lock()了,而是使用RLock(),让我们修改下再来运行试试



    看来,只要将Lock改为RLock就可以解决这种锁中锁(递归锁)的情况了

    信号量

    信号量的用法跟线程锁非常的相似,其实其中的原理与线程锁并没有多大的区别,只不过线程锁锁住一个线程在运行和修改数据,而信号量可以自己控制同一时刻运行几个线程和几个线程修改数据,也就是设置最大同时运行的线程数

    import threading
    import time
    
    
    def run(n):
        # 获取信号量
        semaphore.acquire()
        print('task %s is running' % n)
        # 暂停1s方便看出一次运行几个线程
        time.sleep(1)
        # 释放信号量
        semaphore.release()
    
    
    if __name__ == '__main__':
        # 生成信号量实例并设置信号量为5
        semaphore = threading.BoundedSemaphore(5)
        # 开启50个线程
        for i in range(50):
            t = threading.Thread(target=run, args=(i,))
            t.start()
    
    # 线程没有运行完就不退出 
    while threading.active_count() != 1:
        pass
    else:
        print('----all threads done---')
    

    这里可以自己运行一下,很容易可以看出一次运行五个线程

    虽然我们看到的是一次执行五个线程,但并不是五个一组五个一组分组执行的,因为这五个线程同时完成,所以我们看不出来,但是其中的过程是执行完一个线程放进去一个线程,假如这五个中有两个先完成,那么就会立刻再放进去两个,也就是说这五个线程之间不会互相等待,这个设置的信号量5不是按5来分组,而是同时运行的线程最大数,可以写多个执行时间不同的函数然后一次执行几个来证明这一点

    转载请注明出处

    python自学技术互助扣扣群:670402334

    相关文章

      网友评论

        本文标题:Python3.x:threading module线程锁、递归

        本文链接:https://www.haomeiwen.com/subject/waezfftx.html