Day 34
一、进程调度
# 1 先来先服务
# 2 短作业优先
# 3 时间片轮转
# 4 多级反馈队列
二、进程间数据相互隔离
验证数据隔离
如果主进程中x的值是否改变,没有改变则说明父进程与子进程之间数据隔离
为什么进程之间数据不能共享.
开启两个程序 一个QQ,一个微信, 开启了两个进程.如果进程之间可以通信, 那QQ的进程是不是可以任意访问微信的数据. 这样会造成诸多问题. 所以,进程间理论上是不能通信的. 但是可以借助中间代理(队列)
from multiprocessing import Process
x=1000
def task():
global x # 引用成全局变量
x=2 # 修改全局变量( 由于数据隔离,修改的数据只存在子进程中)
if __name__ == '__main__':
p1=Process(target=task,)
p1.start()
# 如果x的值没有改变,说明父进程与子进程之间数据隔离
print(f'主进程中的x的变量:{x}')
------------------------------------
>>> 主进程中的x的变量:1000
三、进程对象及其他方法
# 1 windows:tasklist |findstr 进程id号
# 2 mac,Linux:ps aux | grep 进程id号
# 3 进程对象:t=Process(target=task, )或者是在进程内部:current_process()
# 4 t.pid或者current_process().pid 获取进程id号
# 5 os.getpid() 同上,获取进程id号
# 6 os.getppid() 获取父进程id号,子进程中获取父进程id,等于父进程的id号
# 7 t.is_alive()或者current_process().is_alive() 查看进程是否存活
# 8 t.terminate() 关闭进程,在主进程关闭
四、守护线程
无论是进程还是线程,都遵循:守护XXX会等待主XXX运行完毕后被销毁
需要强调的是:运行完毕并非终止运行
对于主进程来说,运行完毕指的是主进程代码运行完毕
对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕
详细解释
#1 主进程在其代码结束后就已经算运行完毕了(守护进程在此时就被回收),然后主进程会一直等非守护的子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程),才会结束,
#2 主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。因为主线程的结束意味着进程的结束,进程整体的资源都将被回收,而进程必须保证非守护线程都运行完毕后才能结束。
主进程创建守护进程
1:守护进程会在主进程代码执行结束后就终止
2:守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes are not allowed to have children
注意:进程之间是互相独立的,主进程代码运行结束,守护进程随即终止
class Task(Process):
def __init__(self, n, tag):
super().__init__()
self.n = n
self.tag = tag
def run(self) -> None:
print(f"我是线程{self.tag},我已启动感觉良好......")
sleep(self.n)
print(f"我是线程{self.tag},我已结束......")
if __name__ == '__main__':
print("主线程启动......")
t1 = Task(2, 1)
t2 = Task(3, 2)
t1.daemon = True # 守护线程 主线程结束 就结束 一定要加在前面
t1.start()
t2.start()
sleep(2) # 主线程睡2s
print("主线程结束")
--------------------------------------------
>>> 主线程启动......
>>> 我是线程2,我已启动感觉良好......
>>> 我是线程1,我已启动感觉良好......
>>> 主线程结束
>>> 我是线程2,我已结束...... # 由于主进程结束所以t1并没有执行完整
五、互斥锁
Lock
加一个互斥锁就是让程序变为串行,主要是为了保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行的修改,牺牲速度,换取数据安全。
虽然可以用文件共享数据实现进程间通信,但问题是:
1.效率低(共享数据基于文件,而文件是硬盘上的数据)
2.需要自己加锁处理
{"ticket": 2}
# 通过互斥锁模拟抢票软件
def view_ticket():
with open("ticket", "r", encoding="utf8")as f:
data = json.load(f)
number = data["ticket"]
print(f"余票数量为: {number}")
def buy_ticket(tag):
with open("ticket", "r", encoding="utf8")as f:
data = json.load(f)
number = data["ticket"]
if number > 0: # 存有余票
with open("ticket", "w", encoding="utf8")as f:
sleep(random.random()) # 模拟网络或者手速延迟
data["ticket"] -= 1
print(f"我是线程{tag}, 我抢到票了.....")
json.dump(data, f)
else:
print("余票不足!")
mutex = Lock() # 制造一个线程锁
# 方法1
# def grab_votes(mutex, tag):
# view_ticket()
# mutex.acquire() # 锁的获取
# buy_ticket(tag) # 要锁的线程 任务
# mutex.release() # 锁的释放
# 方法2
def grab_votes(mutex, tag):
view_ticket()
with mutex: # 锁的获取 实现原理是enter和exit的魔法方法
buy_ticket(tag) # 要锁的线程 任务
# 锁的释放
if __name__ == '__main__':
for i in range(10):
task = Process(target=grab_votes, args=(mutex, i))
task.start()
六、队列介绍
Queue是python标准库中的线程安全的列队(FIFO)实现,提供了一个适用于多线程编程的先进先出的数据结构,及队列,用来在生产者和消费者线程之间的信息传递
常用方法
join
阻塞调用线程,直到队列中的所有任务被处理掉。
put(item[, block[, timeout]])
将item放入队列中。
- 如果可选的参数block为True且timeout为空对象(默认的情况,阻塞调用,无超时)。
- 如果timeout是一个正整数,阻塞调用进程最多的timeout秒,如果一直无空空间可用,抛出Full异常(带超时的阻塞调用)
- 如果block为False,如果有空闲空间将数据放入队列,否则立即抛出Full异常
其非阻塞版本为put_nowait
等同于put(item, False)
FIFO即First in First out, 先进先出,Queue提供了一个基本的FIFO容器,使用方法很简单
get([block[, timeout]])
从列队中移除返回一个数据。block根timeout参数通put
方法
其非阻塞方法get_nowait()
相当于get(False)
empty()
如果列队为空,返回True
,反之返回False
full()
如果队列中元素个数达到上限,返回 True,否则返回 False
def mini_chat():
container = Queue(5) # 容器内能容纳的数量
receive = input("请输入你要传输的信息:").strip()
try:
for i in receive:
container.put_nowait(i)
print(container.empty())
except Exception:
print("数列只能存放5个值!")
while True:
display = input(">>>").strip()
try:
print(container.get(timeout=0))
except Exception:
return print("数列中没有元素了!")
七、IPC机制(进程间通信)
不同进程之间(主子)可以共享数据就是进行通信,你往里面放,我往外面取。
def Queue_put(container, n):
receive = "请输入你要传入的信息:" # 信息
try:
for i in receive:
container.put_nowait(i) # 向数列中添加信息
print(container.qsize())
except Exception:
print(f"数列只能存放{n}个值!")
def Queue_get(container, number):
sleep(2)
print(f"子线程{number}提取:\t{container.get(timeout=0)}")
if __name__ == '__main__':
n = 20
container = Queue(n) # 定义数列
p = Process(target=Queue_put, args=(container, n))
p.start() #启动数列添加函数
print(f"主线程提取:\t{container.get()}")
for number in range(10):
g = Process(target=Queue_get, args=(container,number+1))
g.start()
八、自定义上下文管理器
class Context_manager:
def __enter__(self):
print("我是自定义的上线文管理器之-----进入")
count = 0
for i in range(99):
count += i
print(count)
def __exit__(self, exc_type, exc_val, exc_tb):
print("我是自定义的上线文管理器之-----退出")
Context_manager = Context_manager()
with Context_manager:
print("我是你爸爸!")
-----------------------------------------------
>>> 我是自定义的上线文管理器之-----进入
>>> 4851
>>> 我是你爸爸!
>>> 我是自定义的上线文管理器之-----退出