简述python多线程中的互斥锁

        编程中,为了保证共享数据操作的完整性,引入了互斥锁的概念。每个对象都对应于一个可称为" 互斥锁" 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。在python中由于多线程的存在,并且对全局变量作用时有可能产生全局变量紊乱问题,所以也加入了同步和互斥等方法,但是我们主要讲解互斥锁:

如下代码定义了一个my_num的全局变量,而函数sum1和函数sum2都执行对my_num加1操作,并且都执行100万次+1,在下面的代码里,我们创建了两个分别执行两个函数的线程,然后调用start()启动线程

import threading
import time

# 定义全局变量
my_num = 0


# 循环给全局变量每次加上1
def sum1():
    global my_num
    for i in range(1000000):
        my_num += 1

    print("sum1:", my_num)


# 循环给全局变量每次加上1
def sum2():
    global my_num
    for i in range(1000000):
        my_num += 1

    print("sum2:", my_num)


if __name__ == '__main__':
    # 创建两个子线程
    first_thread = threading.Thread(target=sum1)
    second_thread = threading.Thread(target=sum2)

    # 启动线程执行对应的任务
    first_thread.start()# 主线程延时1.5秒
    # time.sleep(1.5)
    # 主线程等待第一个线程执行完成以后,代码再继续往下执行,启动第二个线程执行任务
    # first_thread.join()  # 线程同步: 按照预先定义好的顺序一个任务执行完成另外一个任务再执行
    # print("第一个线程执行完了")
    second_thread.start()

得到执行结果如下:

sum1: 1239023
sum2: 1341576

显然这结果不是我们想要的,我们希望的结果是函数1和函数2都执行100万次加一,最终结果是想变成200万,当然,出现这样的结果,我们也很容易想到原因是:当全局变量my_num 刚好等于1000(某些数字时,这里只是假设)的时候,两个线程同时拿到了my_num的值,各自对他进行了+1操作,此时明显全局my_num的到的只是加一而不是+2操作(不往细了说)

明显看出:在一个进程内非常方便多个线程对全局变量的数据共享,而对全局变量进行随意修改可能会造成全局变量的混乱,即多个线程同时对一个全局变量操作,会出现资源竞争问题,从而数据结果不正确。

同样,下面的代码中我们加入了互斥锁

互斥锁为资源引入一个状态:锁定/非锁定

某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。

当一个线程调用锁的acquire()方法获得锁时,锁就进入“locked”状态。

每次只有一个线程可以获得锁。如果此时另一个线程试图获得这个锁,该线程就会变为“blocked”状态,称为“阻塞”,直到拥有锁的线程调用锁的release()方法释放锁之后,锁进入“unlocked”状态。

线程调度程序从处于同步阻塞状态的线程中选择一个来获得锁,并使得该线程进入运行(running)状态。

import threading

# 定义全局变量
my_num = 0

# 创建互斥锁
lock = threading.Lock()

# def show():
#     print("哈哈")
# # 变量保存的是函数在内存中的地址,保存的函数引用
# a = show
# a()


# 循环给全局变量每次加上1
def sum1():
    # 上锁
    lock.acquire()
    global my_num
    for i in range(1000000):
        my_num += 1

    print("sum1:", my_num)
    # 释放锁
    lock.release()


# 循环给全局变量每次加上1
def sum2():
    # 上锁
    lock.acquire()
    global my_num
    for i in range(1000000):
        my_num += 1

    print("sum2:", my_num)
    # 释放锁
    lock.release()

if __name__ == '__main__':
    # 创建两个子线程
    first_thread = threading.Thread(target=sum1)
    second_thread = threading.Thread(target=sum2)

    # 启动线程执行对应的任务
    first_thread.start()
    second_thread.start()

    # 互斥锁:能保证统一时刻只有一个线程执行,那个线程抢到这个互斥锁我们决定不了,但是能保证数据最终不会有问题
    # 提示:加上互斥锁把多任务瞬间变成单任务,执行效率会下降。

可以看到最后的结果,加入互斥锁后,其结果与预期相符。

得到结果:

sum1: 1000000
sum2: 2000000

锁的好处:

  • 确保了某段关键代码只能由一个线程从头到尾完整地执行

锁的坏处:

  • 阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了
  • 由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁



猜你喜欢

转载自blog.csdn.net/feilzhang/article/details/80190576
今日推荐