默认线程不安全

一个进程中可以有多个线程,且线程共享所有进程中的资源。

多个线程同时去操作一个”变量”,可能会造成数据混乱,例如:

  1. import threading
  2. count = 10000000
  3. number = 0
  4. def myAdd(count):
  5. global number
  6. for i in range(count):
  7. number += 1
  8. def mySub(count):
  9. global number
  10. for i in range(count):
  11. number -= 1
  12. t1 = threading.Thread(target=myAdd, args=(count,))
  13. t2 = threading.Thread(target=mySub, args=(count,))
  14. t1.start()
  15. t2.start()
  16. t1.join() # t1线程执行完毕,才继续往后走
  17. t2.join() # t2线程执行完毕,才继续往后走
  18. print(number) # 结果不为0,且每次变化

结果不为0。
这是因为可能有很多两个线程同时在操作同一个变量number。
比如当number为5000的时候,线程t1正在执行number += 1,这个操作如果没结束,number的值还是5000,此时线程t2来个1次number -= 1操作,然后线程t1等线程t2操作完,再执行number += 1操作。那结果会如何?

正常情况,值应该是 5000 + 1 - 1 = 5000
但是这种多线程抢占资源的情况,值应该是线程t2的5000 -1 = 4999,然后是线程t1的5000 + 1 = 5001,发现问题没有,虽然线程t2已经对number 进行了减1操作,但是此时线程t2已经获得的number是5000而不是4999,可以理解为线程t1就是大哥线程,完全不顾别人对这个变量是否做了操作。

这只是竞争一次资源的结果,那如果有很多次发生这种竞争呢?那结果就不为0了呗

锁保证线程安全

  1. # 加锁的多线程
  2. import threading
  3. count = 10000000
  4. number = 0
  5. Rlock= threading.RLock() # 定义锁
  6. def myAdd(count):
  7. Rlock.acquire() # 加锁
  8. global number
  9. for i in range(count):
  10. number += 1
  11. Rlock.release() # 释放锁
  12. def mySub(count):
  13. Rlock.acquire() # 加锁
  14. global number
  15. for i in range(count):
  16. number -= 1
  17. Rlock.release() # 释放锁
  18. t1 = threading.Thread(target=myAdd, args=(count,))
  19. t2 = threading.Thread(target=mySub, args=(count,))
  20. t1.start()
  21. t2.start()
  22. t1.join() # t1线程执行完毕,才继续往后走
  23. t2.join() # t2线程执行完毕,才继续往后走
  24. print(number) # 每次的结果都为0
  1. # 加锁的多线程:语法二
  2. import threading
  3. count = 10000000
  4. number = 0
  5. Rlock= threading.RLock() # 定义锁
  6. def myAdd(count):
  7. with Rlock: # 基于上下文管理,内部自动执行 acquire 和 release
  8. global number
  9. for i in range(count):
  10. number += 1
  11. def mySub(count):
  12. with Rlock: # 基于上下文管理,内部自动执行 acquire 和 release
  13. global number
  14. for i in range(count):
  15. number -= 1
  16. t1 = threading.Thread(target=myAdd, args=(count,))
  17. t2 = threading.Thread(target=mySub, args=(count,))
  18. t1.start()
  19. t2.start()
  20. t1.join() # t1线程执行完毕,才继续往后走
  21. t2.join() # t2线程执行完毕,才继续往后走
  22. print(number) # 每次的结果都为0

锁详解

两种锁

从上面可以看出,锁可以用来保证线程安全,程序中的锁一般有两种:Lock 和 RLock。

RLock是递归锁,意思是锁中还可以有锁,但是Lock 就不行。

  1. import threading
  2. import time
  3. RLock = threading.Lock()
  4. def task():
  5. print("开始")
  6. RLock.acquire()
  7. RLock.acquire()
  8. print(666)
  9. RLock.release()
  10. RLock.release()
  11. for i in range(3):
  12. t = threading.Thread(target=task)
  13. t.start()

输出:
开始
开始
开始

  1. # 递归锁场景一
  2. import threading
  3. import time
  4. RLock = threading.RLock()
  5. def task():
  6. print("开始")
  7. RLock.acquire()
  8. RLock.acquire()
  9. print(666)
  10. RLock.release()
  11. RLock.release()
  12. for i in range(3):
  13. t = threading.Thread(target=task)
  14. t.start()

开始
666
开始
666
开始
666

  1. # 递归锁场景二
  2. import threading
  3. lock = threading.RLock()
  4. # 程序员A开发了一个函数,函数可以被其他开发者调用,内部需要基于锁保证数据安全。
  5. def funA():
  6. with lock:
  7. pass
  8. # 程序员B开发了一个函数,可以直接调用这个函数。
  9. def funB():
  10. print("run")
  11. funA() # 调用程序员A写的func函数,内部用到了锁。
  12. print("run")
  13. # 程序员C开发了一个函数,自己需要加锁,同时也需要调用func函数。
  14. def funC():
  15. with lock:
  16. print("其他功能")
  17. funA() # ----------------> 此时就会出现多次锁的情况,只有RLock支持(Lock不支持)。
  18. print("其他功能")

总结:如果怕自己用多个锁,就用递归锁,因为你的程序可能一直要改动

死锁

死锁,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象。

  1. import threading
  2. import time
  3. lock_1 = threading.Lock()
  4. lock_2 = threading.Lock()
  5. def task6():
  6. lock_1.acquire()
  7. time.sleep(6)
  8. lock_2.acquire() # 锁6还没释放,锁2不能加锁
  9. print(66)
  10. lock_2.release()
  11. print(666)
  12. lock_1.release()
  13. print(6666)
  14. def task2():
  15. lock_2.acquire() # 锁2还没释放,锁6不能加锁
  16. time.sleep(6)
  17. lock_1.acquire()
  18. print(22)
  19. lock_1.release()
  20. print(222)
  21. lock_2.release()
  22. print(2222)
  23. t6 = threading.Thread(target=task6)
  24. t6.start()
  25. t2 = threading.Thread(target=task2)
  26. t2.start()