默认线程不安全
一个进程中可以有多个线程,且线程共享所有进程中的资源。
多个线程同时去操作一个”变量”,可能会造成数据混乱,例如:
import threading
count = 10000000
number = 0
def myAdd(count):
global number
for i in range(count):
number += 1
def mySub(count):
global number
for i in range(count):
number -= 1
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,且每次变化
结果不为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 threading
count = 10000000
number = 0
Rlock= threading.RLock() # 定义锁
def myAdd(count):
Rlock.acquire() # 加锁
global number
for i in range(count):
number += 1
Rlock.release() # 释放锁
def mySub(count):
Rlock.acquire() # 加锁
global number
for i in range(count):
number -= 1
Rlock.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 threading
count = 10000000
number = 0
Rlock= threading.RLock() # 定义锁
def myAdd(count):
with Rlock: # 基于上下文管理,内部自动执行 acquire 和 release
global number
for i in range(count):
number += 1
def mySub(count):
with Rlock: # 基于上下文管理,内部自动执行 acquire 和 release
global number
for i in range(count):
number -= 1
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
锁详解
两种锁
从上面可以看出,锁可以用来保证线程安全,程序中的锁一般有两种:Lock 和 RLock。
RLock是递归锁,意思是锁中还可以有锁,但是Lock 就不行。
import threading
import time
RLock = 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 threading
import time
RLock = 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 threading
lock = 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 threading
import time
lock_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()