线程冲突
多个线程并发访问同一个变量而相互干扰
解决方式:线程锁
import threading
def fn():
global num
#cpu分配的时间片不足以完成一百万次加法运算
#因此结果还没有被保存到内存中就可能被其他的线程打断
for x in range(1000000):
num += 1
def thread1():
for i in range(5):
t = threading.Thread(target=fn)
t.start()
解决多线程的冲突问题方式一:
import threading
#解决线程冲突问题
#线程锁
lock = threading.Lock()
def fn():
with lock: #会自动加锁,执行完毕之后才会释放锁
global num
#cpu分配的时间片不足以完成一百万次加法运算
#因此结果还没有被保存到内存中就可能被其他的线程打断
for x in range(1000000):
num += 1
print(num)
def thread1():
for i in range(5):
t = threading.Thread(target=fn)
t.start()
if __name__ == '__main__':
num = 0
thread1()
解决多线程冲突问题,方式二
import threading
#解决线程冲突问题
#线程锁,创建锁
lock = threading.Lock()
def fn():
lock.acquire() #锁定
global num
#cpu分配的时间片不足以完成一百万次加法运算
#因此结果还没有被保存到内存中就可能被其他的线程打断
for x in range(1000000):
num += 1
print(num)
lock.release() #释放锁
def thread1():
for i in range(5):
t = threading.Thread(target=fn)
t.start()
if __name__ == '__main__':
num = 0
thread1()
作业:售票问题。
死锁
定义:是指一个资源多次调用,而多次调用方都未能释放该资源就会造成一种互相等待的现象,若无外力作用,它们都将无法推进下去,此时称系统处于死锁状态或者系统产生了死锁。
若存在两个线程:线程A 与线程B
若线程A与线程B都 需要资源1与资源2才能执行
现在线程A拿到了资源1,线程B拿到了资源2,此时就构成了死锁。
若要解决死锁的问题,则此时我们需要使用递归锁。
下面以一个关于线程死锁的经典问题:“哲学家就餐问题”,作为本节最后一个例子。题目是这样的:五位哲学家围坐在一张桌子前,每个人 面前有一碗饭和一只筷子。在这里每个哲学家可以看做是一个独立的线程,而每只筷子可以看做是一个锁。每个哲学家可以处在静坐、 思考、吃饭三种状态中的一个。需要注意的是,每个哲学家吃饭是需要两只筷子的,这样问题就来了:如果每个哲学家都拿起自己左边的筷子, 那么他们五个都只能拿着一只筷子坐在那儿,直到饿死。此时他们就进入了死锁状态。 下面是一个简单的使用死锁避免机制解决“哲学家就餐问题”的实现:
import threading,time
class ZheXueJia():
def __init__(self,left,right):
self.left = left
self.right = right
def run(z,name):
#获取左筷子
f = z.left.acquire()
if f:
print(name,"获取左筷子...")
#获取右筷子
ff = z.right.acquire()
if ff:
print(name,"获取右筷子...")
print("哲学家开始就餐",name)
time.sleep(1)
print("就餐完毕",name)
#释放右筷子
z.right.release()
#释放左筷子
z.left.release()
if __name__ == '__main__':
#每个筷子相当于一把锁
rlock1 = threading.RLock()
rlock2 = threading.RLock()
rlock3 = threading.RLock()
rlock4 = threading.RLock()
rlock5 = threading.RLock()
#必须同时拥有两把锁才能就餐
z1 = ZheXueJia(rlock5, rlock1)
z2 = ZheXueJia(rlock1, rlock2)
z3 = ZheXueJia(rlock2, rlock3)
z4 = ZheXueJia(rlock3, rlock4)
z5 = ZheXueJia(rlock4, rlock5)
#创建五个线程模仿哲学家
t1 = threading.Thread(target=run,args=(z1,"z1"))
t2 = threading.Thread(target=run,args=(z2,"z2"))
t3 = threading.Thread(target=run,args=(z3,"z3"))
t4 = threading.Thread(target=run,args=(z4,"z4"))
t5 = threading.Thread(target=run,args=(z5,"z5"))
#开启线程
t1.start()
t2.start()
t3.start()
t4.start()
t5.start()
或者使用递归锁来解决
# 解决方案:将所有锁改编成递归锁RLock
# 在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。
# 这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。
# 直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁。
import threading
import time
# mutexA = threading.Lock()
# mutexB = threading.Lock()
RLock = threading.RLock()
class MyThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
self.fun1()
self.fun2()
def fun1(self):
RLock.acquire() # 如果锁被占用,则阻塞在这里,等待锁的释放
print ("I am %s , get res: %s" %(self.name, "ResA")
RLock.acquire()
print ("I am %s , get res: %s" %(self.name, "ResB")
RLock.release()
RLock.release()
def fun2(self):
RLock.acquire()
print ("I am %s , get res: %s" %(self.name, "ResB"))
time.sleep(0.2)
RLock.acquire()
print ("I am %s , get res: %s" %(self.name, "ResA")
RLock.release()
RLock.release()
if __name__ == "__main__":
for i in range(0, 10):
my_thread = MyThread()
my_thread.start()
信号量
semaphore是一个内置的计数器
#每当调用acquire()时,内置计数器-1
#每当调用release()时,内置计数器+1
计数器不能小于0,当计数器为0时,acquire()将阻塞线程直到其他线程调用release()。 来看下面的代码:
import time
import threading
def foo():
time.sleep(2) #程序休息2秒
print("ok",time.ctime())
for i in range(20):
t1=threading.Thread(target=foo,args=()) #实例化一个线程
t1.start() #启动线程
程序会在很短的时间内生成20个线程来打印一句话。
如果在主机执行IO密集型任务的时候再执行这种类型的程序时,计算机就有很大可能会宕机。这时候就可以为这段程序添加一个计数器功能,来限制一个时间点内的线程数量。
宕机,指操作系统无法从一个严重系统错误中恢复过来,或系统硬件层面出问题,以致系统长时间无响应,而不得不重新启动计算机的现象。它属于电脑运作的一种正常现象,任何电脑都会出现这种情况。
import time
import threading
s1=threading.Semaphore(5) #添加一个计数器
def foo():
s1.acquire() #计数器获得锁
time.sleep(2) #程序休眠2秒
print("ok",time.ctime())
s1.release() #计数器释放锁
for i in range(20):
t1=threading.Thread(target=foo,args=()) #创建线程
t1.start() #启动线程
或者使用此方法
import time
import threading
sem=threading.Semaphore(5) #添加一个计数器
def foo():
with sem:
time.sleep(2) #程序休眠2秒
print("ok",time.ctime())
for i in range(20):
t1=threading.Thread(target=foo,args=()) #创建线程
t1.start() #启动线程