八. 守护线程
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
'''
🔰正常完成任务