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

2019-06-08  本文已影响0人  mudssky

多线程同步(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
上一篇下一篇

猜你喜欢

热点阅读