Day 17 17.2 多线程实现之互斥锁

多线程实现之互斥锁

问题引入

  • 假如存在一个列表:
    • A线程对其修改,B进程对其访问
    • 假如A,B线程同时去修改和访问则会出现问题
      • 一个数据明明要被A线程删掉的,结果B线程抢先一步就把这个数据访问到了
  • 那么如何解决这个问题呢?可以使用线程同步

1.1 线程同步:

  • 多个线程访问一个对象时,请线程们先排好队,一个一个使用

1.2 互斥锁

  • 可以使用互斥锁来实现线程同步。

  • 互斥锁的原理就是:先把需要访问的数据上锁,留下一把钥匙。

    • 当很多线程来访问时,哪个线程拿到了钥匙,谁就去开锁访问数据。
    • 注意钥匙是多个线程抢来的,而不是智能分配的。
    # 使用互斥锁的案例
    from threading import Thread, Lock
    from time import sleep
     
     
    class Account:
        def __init__(self, money, name):
            self.money = money
            self.name = name
     
     
    # 模拟提款的操作
    class Drawing(Thread):
        def __init__(self, drawingNum, account):
            Thread.__init__(self)
            self.drawingNum = drawingNum
            self.account = account
            self.expenseTotal = 0
     
        def run(self):
            lock1.acquire()  # 获得钥匙
            if self.account.money < self.drawingNum:
                print("账户余额不足!")
                return
            sleep(1)  # 判断完可以取钱,则阻塞。就是为了测试发生冲突问题
            self.account.money -= self.drawingNum
            self.expenseTotal += self.drawingNum
            lock1.release()  # 交出钥匙
            print(f"账户:{self.account.name},余额是:{self.account.money}")
            print(f"账户:{self.account.name},总共取了:{self.expenseTotal}")
     
     
    if __name__ == '__main__':
        a1 = Account(1000, "python")
        lock1 = Lock()
        draw1 = Drawing(600, a1)  # 使用组合,定义一个取钱的线程
        draw2 = Drawing(450, a1)  # 使用组合,定义一个取钱的线程
        draw1.start()
        draw2.start()
    # 账户:python,余额是:400账户余额不足!
    #
    # 账户:python,总共取了:600

1 详解-数据破坏问题

  • 每个线程互相独立,相互之间没有任何关系。
  • 现在假设这样一个例子:
    • 有一个全局的计数num,
    • 每个线程获取这个全局的计数,
    • 根据num进行一些处理,
    • 然后将num加1。
import threading

num = 0  # 全局变量
def work1(number):
    global num
    for i in range(number):
        num += 1
    print('此时num的值为',num)


def work2(number):
    global num
    for i in range(number):
        num += 1
    print('此时num的值为',num)


if __name__ == '__main__':

    t1 = threading.Thread(target=work1,args=(100000000,))
    t2 = threading.Thread(target=work1,args=(100000000,))

    t1.start()
    t2.start()

结果如下:

此时num的值为 198328174
此时num的值为 200000000
  • 这个结果显然并不是我们想要的结果,一定程度上对我们想要的数据造成了影响,甚至是破坏
  • 问题产生的原因就是没有控制多个线程对同一资源的访问,对数据造成破坏,使得线程运行的结果不可预期。这种现象称为“线程不安全”。

2 引入互斥锁解决数据破坏问题

  • 线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。
  • 互斥锁为资源引入一个状态:锁定/非锁定。
    • 某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;
    • 直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。
  • 互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。
#创建锁
lock = threading.Lock()
#锁定
lock.acquire(timeout = 100)  # 防止死锁 
#释放
lock.release()
  • 其中,锁定方法acquire可以有一个超时时间的可选参数timeout。
    • 如果设定了timeout,则在超时后通过返回值可以判断是否得到了锁,从而可以进行一些其他的处理。
`import threading

num = 0  # 全局变量
def work1(number):
    lock.acquire(timeout=100)
    global num
    for i in range(number):
        num += 1
    print('此时num的值为',num)
    lock.release()


def work2(number):
    lock.acquire(timeout=100)
    global num
    for i in range(number):
        num += 1
    print('此时num的值为',num)
    lock.release()

lock = threading.Lock()
if __name__ == '__main__':

    t1 = threading.Thread(target=work1,args=(100000000,))
    t2 = threading.Thread(target=work1,args=(100000000,))

    t1.start()
    t2.start()

结果:

此时num的值为 100000000
此时num的值为 200000000
  • 可以看到,加入互斥锁后,运行结果与预期相符。

2 同步阻塞

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

    • 每次只有一个线程可以获得锁。

    • 如果此时另一个线程试图获得这个锁,该线程就会变为“blocked”状态,称为“同步阻塞”。

    • 直到拥有锁的线程调用锁的release()方法释放锁之后,锁进入“unlocked”状态。

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

多线程问题之死锁

  • 死锁是指:当一个线程需要多把钥匙时,而另一个线程也需要相同的钥匙,由于错位产生的程序抱死。
    • 如:A线程需要1,2两把钥匙才能运行,B线程也需要1,2两把钥匙才能运行,
      • 当A线程拿到钥匙1而B线程拿到了钥匙2,
      • 导致A线程永远拿不到钥匙2,B线程永远拿不到钥匙1,
      • 两个线程相互挂起,程序死机。
    • 解决方法就是:避免一个线程拿多把钥匙。
from threading import Thread, Lock
from time import sleep
 
 
def fun1():
    lock1.acquire()  # 这个线程拿到第一把钥匙
    print('fun1拿到菜刀')
    sleep(2)
    lock2.acquire()  # 这个线程打算拿第二把钥匙,但是第二把钥匙已经被另一个线程拿走了,该线程会在这里一直挂起
    print('fun1拿到锅')
 
    lock2.release()
    print('fun1释放锅')
    lock1.release()
    print('fun1释放菜刀')
 
 
def fun2():
    lock2.acquire()  # 这个线程拿到第二把钥匙
    print('fun2拿到锅')
    lock1.acquire()
    print('fun2拿到菜刀')
    lock1.release()
    print('fun2释放菜刀')
    lock2.release()
    print('fun2释放锅')
 
 
if __name__ == '__main__':
    lock1 = Lock()
    lock2 = Lock()
 
    t1 = Thread(target=fun1)
    t2 = Thread(target=fun2)
    t1.start()
    t2.start()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值