美文网首页
python多线程同步(1)——锁

python多线程同步(1)——锁

作者: mudssky | 来源:发表于2019-06-08 02:15 被阅读0次

    多线程同步(1)——锁

    多线程编程中,总会有一些函数或者代码块我们不希望它被多个线程同时执行。线程之间发生竞争,会导致数据的变动不能正确发生。

    为了控制同时只有一个进程访问某一资源,有一些同步的方法。比如锁,信号量等。其中锁比较容易理解,是最简单,最低级的机制。

    锁有两种状态,锁定和未锁定,同时只支持两个方法,获得锁和释放锁。线程们争夺锁,只有争夺到锁的线程才能执行,执行完毕或执行一段时间释放锁交给其他线程。这样保证获得锁和释放锁之间的代码同时只能有一个线程执行。

    下面我们先示例一个多线程读写发生出错的情况,来表现线程发生竞争时造成的读写错误。也就是典型的全局变量的线程安全问题(race condiion)

    %%time
    import threading
    # import time
    
    # 线程轮流修改一个全局变量,看看变量的结果是否正常
    # 做个简单的,模拟类似银行转账的操作,往一个账户上+10元,在-10元,按理说最后账户余额,无论执行多少次都是0。这是期望的运行结果
    # 执行了几次没看到期望结果,原来是因为一进一出互相抵消了,所以说即使中间出错最终结果也是对的。
    account=0
    def test_thread(id,repeat_num):
        for _ in range(repeat_num):
            global account
            account+=1
    #         time.sleep(0.1)
    #         account-=1
    #         print('thread{0},acount:{1}'.format(id,account))
    threads=[]
    def main():
    #     启动10个线程,每个线程执行10次+1
        for i in range(10):
    #       测试了几次终于找到了合适的会出错的数字,我这个性能的电脑 (i7 8700k)执行1000000次级别以上就很容易出错了。
            t=threading.Thread(target=test_thread,args=(i,1000000))
            threads.append(t)
            t.start()
        for t in threads:
            t.join()
        print('final account:',account)
    if __name__=='__main__':
        main()
    
    final account: 4214990
    Wall time: 611 ms
    

    结果发现多线程竞争造成的全局变量安全问题很难测出来,试了n次没有一次有误差的。最后把每个线程+1的次数调到1000000,最终误差比较明显了

    同样是上面的代码,下面我们用锁进行改造,使之成为线程安全的程序:

    %%time
    import threading
    account=0
    # 创建一个锁的实例
    lock=threading.Lock()
    def test_thread(id,repeat_num):
        for _ in range(repeat_num):
            global account
            lock.acquire()
    #         这段对accout全局变量进行读写的操作需要加锁
            account+=1
            lock.release()
    
    threads=[]
    def main():
        for i in range(10):
            t=threading.Thread(target=test_thread,args=(i,1000000))
            threads.append(t)
            t.start()
        for t in threads:
            t.join()
        print('final account:',account)
    if __name__=='__main__':
        main()
    
    final account: 10000000
    Wall time: 37.9 s
    

    上锁之后,运行结果正常了,但是速度就慢了很多。可见上锁这一步消耗时间还是挺多的。

    使用上下文管理,python2.5及以上,可以不用acquire()和release()方法,使用with语句进一步简化代码
    threading模块的对象Lock,Rlock,Condition,Semaphore,BoundedSemaphore都包含上下文管理器,也就是都可以使用with语句
    with语句就是上下文管理器带来的语法糖,作用是给一段代码添加上下文,通常用来异常处理的情况,比如说文件with open()...这个的作用是当发生异常时,自动帮我们关闭文件句柄,所以文件操作就可以就不用你写异常处理了。代码更清晰一些。还有一种就是这种多线程加锁的情况。

    如下,把加锁部分用with语句处理'

    %%time
    import threading
    account=0
    # 创建一个锁的实例
    lock=threading.Lock()
    def test_thread(id,repeat_num):
        for _ in range(repeat_num):
            global account
    #         with语句加锁
            with lock:
                account+=1
    
    threads=[]
    def main():
        for i in range(10):
            t=threading.Thread(target=test_thread,args=(i,1000000))
            threads.append(t)
            t.start()
        for t in threads:
            t.join()
        print('final account:',account)
    if __name__=='__main__':
        main()
    
    final account: 10000000
    Wall time: 37 s
    

    相关文章

      网友评论

          本文标题:python多线程同步(1)——锁

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