默认线程不安全
一个进程中可以有多个线程,且线程共享所有进程中的资源。
多个线程同时去操作一个”变量”,可能会造成数据混乱,例如:
import threadingcount = 10000000number = 0def myAdd(count):global numberfor i in range(count):number += 1def mySub(count):global numberfor i in range(count):number -= 1t1 = threading.Thread(target=myAdd, args=(count,))t2 = threading.Thread(target=mySub, args=(count,))t1.start()t2.start()t1.join() # t1线程执行完毕,才继续往后走t2.join() # t2线程执行完毕,才继续往后走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了呗
锁保证线程安全
# 加锁的多线程import threadingcount = 10000000number = 0Rlock= threading.RLock() # 定义锁def myAdd(count):Rlock.acquire() # 加锁global numberfor i in range(count):number += 1Rlock.release() # 释放锁def mySub(count):Rlock.acquire() # 加锁global numberfor i in range(count):number -= 1Rlock.release() # 释放锁t1 = threading.Thread(target=myAdd, args=(count,))t2 = threading.Thread(target=mySub, args=(count,))t1.start()t2.start()t1.join() # t1线程执行完毕,才继续往后走t2.join() # t2线程执行完毕,才继续往后走print(number) # 每次的结果都为0
# 加锁的多线程:语法二import threadingcount = 10000000number = 0Rlock= threading.RLock() # 定义锁def myAdd(count):with Rlock: # 基于上下文管理,内部自动执行 acquire 和 releaseglobal numberfor i in range(count):number += 1def mySub(count):with Rlock: # 基于上下文管理,内部自动执行 acquire 和 releaseglobal numberfor i in range(count):number -= 1t1 = threading.Thread(target=myAdd, args=(count,))t2 = threading.Thread(target=mySub, args=(count,))t1.start()t2.start()t1.join() # t1线程执行完毕,才继续往后走t2.join() # t2线程执行完毕,才继续往后走print(number) # 每次的结果都为0
锁详解
两种锁
从上面可以看出,锁可以用来保证线程安全,程序中的锁一般有两种:Lock 和 RLock。
RLock是递归锁,意思是锁中还可以有锁,但是Lock 就不行。
import threadingimport timeRLock = threading.Lock()def task():print("开始")RLock.acquire()RLock.acquire()print(666)RLock.release()RLock.release()for i in range(3):t = threading.Thread(target=task)t.start()
输出:
开始
开始
开始
# 递归锁场景一import threadingimport timeRLock = threading.RLock()def task():print("开始")RLock.acquire()RLock.acquire()print(666)RLock.release()RLock.release()for i in range(3):t = threading.Thread(target=task)t.start()
开始
666
开始
666
开始
666
# 递归锁场景二import threadinglock = threading.RLock()# 程序员A开发了一个函数,函数可以被其他开发者调用,内部需要基于锁保证数据安全。def funA():with lock:pass# 程序员B开发了一个函数,可以直接调用这个函数。def funB():print("run")funA() # 调用程序员A写的func函数,内部用到了锁。print("run")# 程序员C开发了一个函数,自己需要加锁,同时也需要调用func函数。def funC():with lock:print("其他功能")funA() # ----------------> 此时就会出现多次锁的情况,只有RLock支持(Lock不支持)。print("其他功能")
总结:如果怕自己用多个锁,就用递归锁,因为你的程序可能一直要改动
死锁
死锁,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象。
import threadingimport timelock_1 = threading.Lock()lock_2 = threading.Lock()def task6():lock_1.acquire()time.sleep(6)lock_2.acquire() # 锁6还没释放,锁2不能加锁print(66)lock_2.release()print(666)lock_1.release()print(6666)def task2():lock_2.acquire() # 锁2还没释放,锁6不能加锁time.sleep(6)lock_1.acquire()print(22)lock_1.release()print(222)lock_2.release()print(2222)t6 = threading.Thread(target=task6)t6.start()t2 = threading.Thread(target=task2)t2.start()
