11月1日线程锁

发布时间 2023-11-02 18:34:00作者: songjunwan

线程锁

为什么会有线程锁,首先这里说一个例子

假设我的计算机的CPU略微拉跨一点,然后我有个三个线程进行计算,同时计算量都不小,这时候就有可能出现算错的情况

具体代码如下

from threading import Thread

x = 0


def task():
    global x
    for i in range(2000000000):
        x += 1


if __name__ == '__main__':
    t1 = Thread(target=task)
    t2 = Thread(target=task)
    t3 = Thread(target=task)

    t1.start()
    t2.start()
    t3.start()

    t1.join()
    t2.join()
    t3.join()

    print(x)

这个代码里面启动了三个子线程,然后这三个子进程都运行函数里面的相加,同时每次都会修改全局里面x的值。

会出现计算错误的情况

结果如下

这里的结果就出现错误了,为什么会这样呢?因为它在运行时t1时,t2和t3也在运行这就会出现抢CPU的情况了,然后t1运行好了,t2就抢走了然后它运行后又把t1的定义x的值给打乱了,但是这时候t3也会来抢CPU这就会导致x的值出现误差,这就是多线程经常出现的BUG。

上面的也可以这么理解:就是有三个软件都是来杀病毒软件的,然后软件A抢先一步来杀病毒软件,就在杀毒快完成准备保存正确的结果时,软件B反应了过来抢这个病毒软件进行杀毒,导致软件A的杀毒没有彻底完成,软件B也是快杀完毒准备保存时,软件C当老六抢走了这个病毒软件进行杀毒,软件B也是没有彻底完成。这就会造成软件A又去抢,抢完软件B又来抢,再然后软件C又来抢。

最后的结果就会是病毒没杀掉,电脑先崩了,赔了软件又赔了电脑血亏。

所以这就是为什么要有线程锁这个情况,就是为了防止这个情况

下面用进程锁解决这个问题

首先还是与进程锁用同一个模块Lock

然后再定义一把锁

在主要的函数里面给核心计算功能上锁

当主要功能运行完后再解锁

代码如下

from threading import Thread, Lock

x = 0

suo = Lock()  # 生成一把锁


def task():
    suo.acquire()  # 这里上锁
    global x
    for i in range(200000):
        x += 1

    suo.release()  # 上面的运行完了再把锁给解了


if __name__ == '__main__':
    t1 = Thread(target=task)
    t2 = Thread(target=task)
    t3 = Thread(target=task)

    t1.start()
    t2.start()
    t3.start()

    t1.join()
    t2.join()
    t3.join()

    print(x)

然后结果为

这个结果就体现了线程锁的用法,先给主要的计算功能上锁,哪个线程抢到了它独用此方法,其它的线程只能等它用完了并且解锁了才能使用,这就预防了线程与线程之间出现抢CPU的情况了。

还有一种便捷的格式来实现上锁以及释放锁的功能

代码如下

from threading import Thread, Lock

x = 0

suo = Lock()  # 生成一把锁


def task():
    with suo:  # 这里将上锁以及释放锁的方法给隐式的发生在with suo 换句话就是这里面包含了上锁和释放锁的方式
        global x
        for i in range(2000000000):
            x += 1


if __name__ == '__main__':
    t1 = Thread(target=task)
    t2 = Thread(target=task)
    t3 = Thread(target=task)

    t1.start()
    t2.start()
    t3.start()

    t1.join()
    t2.join()
    t3.join()

    print(x)

这个代码里面让with上下文管理器与锁对象一起使用,提供了一种方便的方式来实现锁的上锁和解锁,这样做会让代码更加清晰,同时防止了忘记释放锁的可能。

with是如何实现的呢?

因为我给这个锁命名的是suo,然后with和它一起使用就能隐式(不会显示)的调用suo对象里面的 _ _ enter _ _ 方法和 _ _ exit _ _ 方法,这样就实现了上锁和释放锁的功能了

修改比喻

这里将上述的比喻修改一下

多个软件(线程)试图同时访问和修改共享资源,例如病毒软件,而没有适当的同步机制。软件A率先访问病毒软件,但在完成清除病毒任务之前,软件B也开始争抢同一个资源。之后,软件C也可能加入竞争。

这种情况下,没有足够的协调和同步,不同的软件之间会竞争共享资源,导致不确定的行为,例如清除任务可能不完整。为了解决这个问题,需要使用同步机制,如,以确保一次只有一个软件能够成功访问共享资源,从而避免竞态条件,确保任务以有序的方式执行。