Python 多线程中 正确使用共享数据(使用 threading 库里面的锁对象 Lock )

Python 多线程中 正确使用共享数据(使用 threading库里面的锁对象 Lock )

目录

    • 1.一个简单的栗子引入
    • 2.解决多线程中共享数据覆盖的问题
    • 3.总结

做多线程开发,经常遇到这样的情况:多个线程里面的代码 需要访问 同一个 公共的数据对象。

这个公共的数据对象可以是任何类型, 比如一个 列表、字典、或者自定义类的对象。

有的时候,程序 需要 防止线程的代码 同时操作 公共数据对象。 否则,就有可能导致 数据的访问互相冲突影响。

1.一个简单的栗子引入

我们用一个简单的程序模拟一个老师收作业

from threading import Thread
from time import sleep

desk = {
    'name' : 0
}

# 定义一个函数,作为新线程执行的入口函数
def deposit(studentid,amount):
    balance =  desk['name']
    # 执行一些任务,耗费了0.1秒
    sleep(0.1)
    desk['name']  = balance + amount
    print(f'学生 {studentid} 交一本作业,线程 {studentid} 结束')

theadlist = []
for i in range(10):
    thread = Thread(target = deposit,
                    args = (i,1)
                    )
    thread.start()
    # 把线程对象都存储到 threadlist中
    theadlist.append(thread)

for thread in theadlist:
    thread.join()

print('主线程结束')
print(f'最后老师收到的作业为: {desk["name"]}')

开始的时候, 讲台上没有作业本,随后我们启动了10个线程, 每个线程都deposit函数,往讲台放一本作业。

可以预期,执行完程序后,讲台上的作业本应该是 10本。

然而,我们运行程序后,发现结果如下

Python 多线程中 正确使用共享数据(使用 threading 库里面的锁对象 Lock )_第1张图片
问题出现在哪里呢?老师只收了1本作业,很生气
Python 多线程中 正确使用共享数据(使用 threading 库里面的锁对象 Lock )_第2张图片

然后我们用单线程写一下

from time import sleep

desk = {
    'name' : 0
}

def deposit(studentid,amount):
    balance =  desk['name']
    # 执行一些任务,耗费了0.1秒
    sleep(0.1)
    desk['name']  = balance + amount
    print(f'学生 {studentid} 交一本作业')

for i in range(1,11):
    deposit(i,1)
    
print(f'最后老师收到的作业为: {desk["name"]}')

Python 多线程中 正确使用共享数据(使用 threading 库里面的锁对象 Lock )_第3张图片
此时老师收到了10本作业,看来确实是多线程使用不当。

2.解决多线程中共享数据覆盖的问题

现在我们程序代码中,有多个线程,并且在这个几个线程中都会去调用 deposit,就有可能同时操作这个desk对象,就有可能出一个线程覆盖另外一个线程的结果的问题。

这时,可以使用 threading库里面的锁对象 Lock 去保护。

我们来看下代码:

from threading import Thread,Lock
from time import sleep

desk = {
    'name' : 0
}

deskLock = Lock()


def deposit(studentid,amount):
    # 操作共享数据前,申请获取锁
    deskLock.acquire()
    balance =  desk['name']

    sleep(0.1)
    desk['name']  = balance + amount
    print(f'学生 {studentid} 交一本作业,线程 {studentid} 结束')

    # 操作完共享数据后,申请释放锁
    deskLock.release()

theadlist = []
for i in range(1,11):
    thread = Thread(target = deposit,
                    args = (i,1)
                    )
    thread.start()
    # 把线程对象都存储到 threadlist中
    theadlist.append(thread)

for thread in theadlist:
    thread.join()

print('主线程结束')
print(f'最后老师收到的作业为: {desk["name"]}')

Python 多线程中 正确使用共享数据(使用 threading 库里面的锁对象 Lock )_第4张图片

Lock 对象的acquire方法 是申请锁。

每个线程在 操作共享数据对象之前,都应该 申请获取操作权,也就是 调用该 共享数据对象对应的锁对象的acquire方法。

如果线程A 执行如下代码,调用acquire方法的时候,

bankLock.acquire()

别的线程B 已经申请到了这个锁, 并且还没有释放,那么 线程A的代码就在此处 等待 线程B 释放锁,不去执行后面的代码。

直到线程B 执行了锁的 release 方法释放了这个锁, 线程A 才可以获取这个锁,就可以执行下面的代码了。

如果这时线程B 又执行 这个锁的acquire方法, 就需要等待线程A 执行该锁对象的release方法释放锁, 否则也会等待,不去执行后面的代码。

3.总结

到在使用多线程时,如果数据出现和自己预期不符的问题,就可以考虑是否是共享的数据被调用覆盖的问题。

使用 threading库里面的锁对象 Lock去保护

# 先实例化,创建一个对象,name是数据对象的名字
nameLock = Lock()


# 操作共享数据前,申请获取锁
nameLock.acquire()

# 操作完共享数据后,申请释放锁
nameLock.release()

你可能感兴趣的:(Python,python,thread,多线程)