Python并发编程之多线程(守护进程,互斥锁,信号量Semaohore,死锁与递归锁)

八. 守护线程

1.进程守护进程与线程守护进程

  • 对于主进程来讲, 守护进程守护的是主进程的代码, 主进程代码运行完毕, 则守护进程就终止, 之后如果还有非守护子进程在运行, 那么主进程会一直等待其运行完毕后回收该子进程的资源, 不然就会产生僵尸进程
  • 对于主线程来讲, 守护线程守护的是主线程的整个生命周期, 主线程需要等待其他非守护线程运行完毕才算完毕, 完毕的同时守护线程也被回收, 主线程的结束也就意味着进程的结束, 之后进程整体的资源将被回收, 而进程必须保证非守护线程都运行完毕后才能结束

2.守护线程

from threading import Thread,current_thread
import time

def task():
    time.sleep(4)
    print(f"子线程{current_thread().name}结束")

def task2():
    print(f"子线程{current_thread().name}结束")

if __name__ == '__main__':
    p = Thread(target=task)
    p2 = Thread(target=task2)
    p.setDaemon(True)  # 设置守护线程, 需在 start() 之前设置
    p.start()
    p2.start()

    print("主线程--->")
    print(p.is_alive())

'''输出
子线程Thread-2结束主线程--->
True
'''
🔰主线程和非守护线程都已经结束了,于是把还未运行完的守护进程带走了

九.线程同步锁(互斥锁/排他锁)

与进程概念同步锁相同, 主要用于解决多个人同时操作同一份文件造成的数据安全性问题, 我们还是来举个例子 :

  • 访问一个数据, 先拿到数据访问计数, 访问一次修改计数加1, 再返回修改后的数据
from threading import Thread
import time

box = 0
def children():
    global box
    temp = box     # 拿到数据计数
    time.sleep(1)  # 模拟网络延迟
    temp += 1      # 访问计数加一
    box = temp     # 返回改后数据

if __name__ == '__main__':
    li = []
    for i in range(100):  # 100个用户
        p = Thread(target=children)
        p.start()
        li.append(p)
    for i in li:
        i.join()
    print(box)  # 1

得到的结果是 1? 不应该是 100 吗? 这就是数据的安全性问题, 我们可以通过加锁, 让同一时间只能有一个人进行数据的修改

from threading import Thread,Lock
import time

mutex = Lock()
box = 0
def children():
    mutex.acquire()   # 加锁
    global box
    temp = box        # 拿到数据计数
    time.sleep(0.1)   # 模拟网络延迟
    temp += 1         # 访问计数加一
    box = temp        # 返回改后数据
    mutex.release()   # 解锁

if __name__ == '__main__':
    li = []
    for i in range(100):  # 100个用户
        p = Thread(target=children)
        p.start()
        li.append(p)
    for i in li:
        i.join()
    print(box)  # 100

ps : 加锁的另一种写法(通过上下文管理器)

mutex = Lock()

def children():
    with mutex:
        global box
        temp = box        # 拿到数据计数
        time.sleep(0.1)   # 模拟网络延迟
        temp += 1         # 访问计数加一
        box = temp        # 返回改后数据

十.信号量Semaphore (了解)

线程信号量与进程信号量一模一样, 概念不在赘述, 下面举个例子 :

  • 一个小网吧, 最多容纳3个网瘾少年, 可以同时有十个少年上机玩游戏, 其他人只能等着, 有些人完的时间短下机了, 那么就空出了一个位置(锁), 外边等的人就可以抢这个位置了
from threading import Thread,Semaphore,current_thread
import time,random

sm = Semaphore(3)
def young():
    with sm:
        print(f"少年{current_thread().name}正在打电脑")
        time.sleep(random.randint(1,3))
        print(f"少年{current_thread().name}下机了")

if __name__ == '__main__':
    li = []
    for i in range(7):
        p = Thread(target=young)
        p.start()
        li.append(p)
    for i in li:
        i.join()
    print("网吧老板被抓")
    
'''输出
少年Thread-1正在打电脑
少年Thread-2正在打电脑
少年Thread-3正在打电脑
少年Thread-2下机了
少年Thread-4正在打电脑
少年Thread-4下机了
少年Thread-5正在打电脑
少年Thread-3下机了
少年Thread-1下机了
少年Thread-6正在打电脑
少年Thread-7正在打电脑
少年Thread-5下机了
少年Thread-6下机了
少年Thread-7下机了
网吧老板被抓
Process finished with exit code 0
'''

十一.死锁与递归锁

1.什么是死锁

所谓死锁 : 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程

  • 老板与员工交易示例, 揭秘公司两个财务为何大打出手(剧情为便于理解纯属虚构)
#🔹老板向员工发工资, 员工向老板还钱, 财务负责处理金额问题, 处理财务必须获得个人令牌,
#🔹老板--->员工 : 财务1先获得了老板令牌, 再获得员工两人的令牌, 进行第一次金额交易后归还了各自令牌
#🔹员工--->老板 : 紧接着财务1看看账目, 发现员工要向老板还钱, 于是先获取员工的令牌, 于此同时财务2在另外一本账目上处理一笔老板向员工的转账
#🔹于是乎财务2将老板的令牌拿走了, 财务1没拿到,而财务2想拿员工的令牌, 发现令牌在财务1手上,于是两人争执不过就大打出手了
from threading import Thread,Lock
import time

boss = Lock()
staff = Lock()

class Mythread(Thread):
    def __init__(self,name):
        super().__init__()
        self.name = name

    def deal1(self):
        boss.acquire()    # 加老板锁
        print(f"{self.name}获取了老板令牌")
        staff.acquire()   # 加员工锁
        print(f"{self.name}获取了员工令牌")
        print(f"{self.name}操控了交易")
        staff.release()   # 释放老板锁
        boss.release()    # 释放员工锁

    def deal2(self):
        staff.acquire()   # 加员工锁
        print(f"{self.name}获得了员工令牌")
        print("正在去老板办公室...")
        time.sleep(1)  
        boss.acquire()    # 加老板锁
        print(f"{self.name}获得了老板令牌")
        print(f"没内鬼,{self.name}继续交易")
        boss.release()    # 释放老板锁
        staff.release()   # 释放员工锁

    def run(self):
        self.deal1()
        self.deal2()

if __name__ == '__main__':
    p1 = Mythread("财务1")
    p2 = Mythread("财务2")
    p1.start()
    p2.start()
    
'''输出
财务1获取了老板令牌
财务1获取了员工令牌
财务1操控了交易
财务1获得了员工令牌
正在去老板办公室...
财务2获取了老板令牌
'''
🔰出现死锁, 程序就此停在这无法再运行下去

解决方法使用递归锁

2.什么是递归锁

递归锁, 在Python中为了支持在同一线程中多次请求同一资源, python提供了可重入锁RLock, 这个RLock内部维护着一个Lock和一个counter变量, counter记录了acquire的次数,从而使得资源可以被多次require, require一次技术就加1, 直到一个线程所有的acquire都被release,其他的线程才能获得资源

  • 使用递归锁解决财务纠纷问题
from threading import Thread,Lock,RLock
import time

boss = staff = RLock() # 让老板锁和员工锁变成了一把锁

class Mythread(Thread):
    def __init__(self,name):
        super().__init__()
        self.name = name

    def deal1(self):
        boss.acquire()    # 锁计数 +1 = 1
        print(f"{self.name}获取了老板令牌")
        staff.acquire()   # 锁计数 +1 = 2
        print(f"{self.name}获取了员工令牌")
        print(f"有内鬼,{self.name}继续交易")
        staff.release()   # 锁计数 -1 = 1
        boss.release()    # 锁计数 -1 = 0

    def deal2(self):
        staff.acquire()    # 锁计数 +1 = 1
        print(f"{self.name}获得了员工令牌")
        print("正在去老板办公室...")
        time.sleep(1)
        boss.acquire()     # 锁计数 +1 = 2
        print(f"{self.name}获得了老板令牌")
        print(f"没内鬼,{self.name}继续交易")
        boss.release()     # 锁计数 -1 = 1
        staff.release()    # 锁计数 -1 = 0

    def run(self):
        self.deal1()
        self.deal2()

if __name__ == '__main__':
    p1 = Mythread("财务1")
    p2 = Mythread("财务2")
    p1.start()
    p2.start()
    
'''输出
财务1获取了老板令牌
财务1获取了员工令牌
有内鬼,财务1继续交易
财务1获得了员工令牌
正在去老板办公室...
财务1获得了老板令牌
没内鬼,财务1继续交易
财务2获取了老板令牌
财务2获取了员工令牌
有内鬼,财务2继续交易
财务2获得了员工令牌
正在去老板办公室...
财务2获得了老板令牌
没内鬼,财务2继续交易
Process finished with exit code 0
'''
🔰正常完成任务

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

给你骨质唱疏松

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值