线程冲突、死锁与信号量

线程冲突、死锁与信号量

线程冲突

多个线程并发访问同一个变量而相互干扰

解决方式:线程锁

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()  #启动线程

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值