您的位置:首页 > 其它

僵尸进程与孤儿进程、守护进程、互斥锁、消息队列、进程间数据交互、生产与消费者模型、线程理论与实现相关

2022-01-16 21:05 573 查看

昨日内容回顾

  • 操作系统发展史
1.穿孔卡片时代
cpu 利用率极低

2.联机批处理系统
cpu效率有所提升

3.脱机批处理系统
cpu效率极大提升(也是现代计算机雏形)

# 多道技术:
串行:多个任务依次排队执行
多道:切换 + 保存状态
  • 进程理论
1.程序与进程的区别
程序是死的 进程是活的

2.进程的调度算法
先来先服务
短作业优先
时间片轮转法 + 多级反馈队列

3.进程的三状态
就绪态 运行态 阻塞态(只有经过就绪态 进程才能进入运行态)

4.任务的提交方式
同步:
提交完成任务之后 原地等待任务执行结果 期间不做其他任何事
异步:
提交完任务之后 不原地等待 结果由反馈机制反馈(异步回调机制)

5.进程的状态
阻塞态 :阻塞态
非阻塞态 :就绪态 运行态
'''高效的程序 意味着尽量一直处于非阻塞态'''
  • 开启进程的多种方式
.start()
异步提交 告知操作系统开设进程
  • 进程join方法
.join()
主进程等待子进程运行结束之后再继续执行
'''
执行多个进程时 在等待时间最长的进程期间 其他的进程也在等待
'''
  • 进程对象方法
1.获取进程号
current_process 查看进程号
os.getpid() 查看进程号
os.getppid() 查看父进程进程号

2.获取进程名称
p.name

3.杀死进程
p.terminate()  杀死子进程

4.判断进程是否存活
p.is_alive()  判断进程是否存活
'''3/4要结合使用'''
  • 进程间数据默认是隔离的
关键字:
global、nonlocal

名称空间概念

今日内容概要

  • 僵尸进程与孤儿进程
  • 守护进程
  • 互斥锁
  • 消息队列
  • 实现进程间数据交互
  • 生产者和消费者模型
  • 线程理论

内容详细

1、僵尸进程与孤儿进程

# 1.僵尸进程
进程代码运行结束之后 其实并没有真正的结束 因为要等待子进程结束后 由主进程回收掉子进程资源 之后才能真正的结束

# 2.孤儿进程(属于有害的 会造成资源浪费)
主进程已经死亡(非正常死亡)
但是子进程还在运行

2、守护进程

# 守护进程:
就是守护着某个(子)进程 一旦这个(主)进程结束 那么被守护的(子)进程也会随之结束(无论被守护进程是否执行完毕)

import time
from multiprocessing import Process

def test(name):
print('太监总管:%s 开始执行' % name)
time.sleep(3)
print('太监总管:%s 结束了' % name)

if __name__ == '__main__':
p = Process(target=test, args=('jason', ))
p.daemon = True  # 设置为守护进程 必须要写在启动进程start()上面
p.start()
print('皇帝jason寿终正寝了')
time.sleep(0.5)

执行结果:
皇帝jason寿终正寝了
太监总管:jason 开始执行

"""
子进程并没有执行完毕 而是随着主进程的结束直接结束了
"""

3、互斥锁

# 1.问题:
并发情况下操作同一份数据 极其容易造成数据错乱

import json
import time
from multiprocessing import Process
import random

# 模仿12306票务系统
# 先看票
def search(name):
with open(r'a.txt', 'r', encoding='utf8') as f:
data_dict = json.load(f)
ticket_num = data_dict.get('ticket_num')
print('%s:查看余票为:%s张'% (name,ticket_num))

# 买票
def buy(name):
# 买票之前还要先查票
with open(r'a.txt', 'r', encoding='utf8') as f:
data_dict = json.load(f)
ticket_num = data_dict.get('ticket_num')
# 虚拟一个网络延迟
time.sleep(random.random())
# 判断是否有票
if ticket_num > 0:
# 将余票减一
data_dict['ticket_num'] -= 1
# 将剩余票数重新写入数据库
with open(r'a.txt', 'w', encoding='utf8') as f:
json.dump(data_dict, f)
print('%s 买票成功' % name)
else:
print('没有票啦啦啦')

def run(name):
search(name)
buy(name)

if __name__ == '__main__':
for i in range(1, 11):  # 假设十个人来买票
p = Process(target=run, args=('用户%s'% i, ))
p.start()

'''
数据库:
{"ticket_num": 1}
'''

执行结果:  # 数据错乱
用户3查看余票为:1张
用户1查看余票为:1张
用户2查看余票为:1张
用户4查看余票为:1张
用户5查看余票为:1张
用户6查看余票为:1张
用户7查看余票为:1张
用户8查看余票为:1张
用户9查看余票为:1张
用户10查看余票为:1张
用户7 买票成功
用户5 买票成功
用户6 买票成功
用户1 买票成功
用户3 买票成功
用户9 买票成功
用户8 买票成功
用户10 买票成功
用户4 买票成功
用户2 买票成功

# 2.解决:
将并发变成串行 虽然降低了效率但是提升了数据的安全
锁就可以实现将并发变成串行的效果
行锁:锁定被某个用户查看的某行数据 其他用户同时间无法操作
表锁:锁定被某个用户查看的某个数据表 其他用户同时间无法操作
'''
使用锁的注意事项:
1.在主进程中产生 交由子进程使用
2.一定要在需要的地方加锁 千万不要随意加
3.不要轻易的使用锁(容易出现死锁现象)

在以后的编程生涯中 几乎不会解除到自己操作锁的情况 此处仅作了解内部原理即可
'''

import json
import time
from multiprocessing import Process, Lock
import random

# 模仿12306票务系统
# 先看票
def search(name):
with open(r'a.txt', 'r', encoding='utf8') as f:
data_dict = json.load(f)
ticket_num = data_dict.get('ticket_num')
print('%s:查看余票为:%s张'% (name,ticket_num))

# 买票
def buy(name):
# 买票之前还要先查票
with open(r'a.txt', 'r', encoding='utf8') as f:
data_dict = json.load(f)
ticket_num = data_dict.get('ticket_num')
# 虚拟一个网络延迟
time.sleep(random.random())
# 判断是否有票
if ticket_num > 0:
# 将余票减一
data_dict['ticket_num'] -= 1
# 将剩余票数重新写入数据库
with open(r'a.txt', 'w', encoding='utf8') as f:
json.dump(data_dict, f)
print('%s 买票成功' % name)
else:
print('没有票啦啦啦')

def run(name, mutex):
search(name)
mutex.acquire()  # 抢锁
buy(name)
mutex.release()  # 释放锁

if __name__ == '__main__':
mutex = Lock()  # 主进程产生锁
for i in range(1, 11):  # 假设十个人来买票
p = Process(target=run, args=('用户%s'% i, mutex))
p.start()

执行结果:  # 数据正常
用户1:查看余票为:1张
用户2:查看余票为:1张
用户4:查看余票为:1张
用户5:查看余票为:1张
用户3:查看余票为:1张
用户6:查看余票为:1张
用户7:查看余票为:1张
用户8:查看余票为:1张
用户9:查看余票为:1张
用户10:查看余票为:1张
用户1 买票成功
没有票啦啦啦
没有票啦啦啦
没有票啦啦啦
没有票啦啦啦
没有票啦啦啦
没有票啦啦啦
没有票啦啦啦
没有票啦啦啦
没有票啦啦啦

4、消息队列

# Queue 等待数

from multiprocessing import Queue

q = Queue(5)  # 括号内可以填写最大等待数

# 存放数据
q.put(111)
q.put(222)
print(q.full())  # False  判断队列中数据是否满了
q.put(333)
q.put(444)
q.put(555)
print(q.full())  # True  判断队列中数据是否满了
# q.put(666)  # 如果加入该行以及更多行 则结果会一直卡住 直至有空位才会输出结果
print('我在这')  # 最大等待数为5个及以内 才会输出结果
"""
输出结果:
空 会卡住原地等待
"""
# 取数据
print(q.get())
print(q.get())
print(q.get())
print(q.empty())  # 判断该队列是否为空
print(q.get())
print(q.get())
# print(q.get())  # 没有数据之后 也会原地卡住等待 不会输出结果
print(q.get_nowait())  # 再取数据时 如果已经没有数据了 会直接报错
"""
数据只有五个时 输出结果:
我在这
111
222
333
444
555
"""

"""
full和get_nowait和empty能否用于多进程情况下的精确使用
不能!!!

队列的使用就可以打破进程间默认无法通信的情况
"""

5、ipc机制

# 1.主进程与子进程的数据交互

from multiprocessing import Queue, Process

def consumer(q):
print(q.get())  # 获取到了主进程数据
q.put('子进程的数据')

if __name__ == '__main__':
q = Queue()
q.put('主进程的数据')
p = Process(target=consumer, args=(q, ))
p.start()
p.join()
print(q.get())  # 获取到了子进程数据
print('主')

执行结果:
主进程的数据
子进程的数据
主

# 2.子进程与子进程数据交互

from multiprocessing import Queue, Process

def producer(q):
q.put('子进程producer的数据')

def consumer(q):
print('子进程C取得数据', q.get())

if __name__ == '__main__':
q = Queue()
p = Process(target=producer, args=(q, ))
c = Process(target=consumer, args=(q, ))
p.start()
c.start()

执行结果:
子进程C取得数据 子进程producer的数据

6、生产者消费者模型

"""
生产者:
负责产生数据(做包子的)

消费者:
负责处理数据(吃包子的)

该模式需要解决供需不平衡现象
"""

from multiprocessing import Process, Queue, JoinableQueue
import time
import random

def producer(name, food, q):
for i in range(5):
print('%s生产了%s'%(name,food))
q.put(food)
time.sleep(random.random())

def consumer(name, q):
while True:
data = q.get()
print('%s吃了%s'%(name, data))
q.task_done()  # 检测队列还有多少数据没被取

if __name__ == '__main__':
# q = Queue()
q = JoinableQueue()
p = Process(target=producer, args=('厨师长jsaon', '酱猪蹄', q))
p1 = Process(target=producer, args=('印度阿三', '印度飞饼', q))
p2 = Process(target=producer, args=('泰国唐仁', '榴莲饼', q))
c = Process(target=consumer, args=('小明', q))

p.start()
p1.start()
p2.start()
c.daemon = True
c.start()

p.join()
p1.join()
p2.join()
q.join()  # 等待队列中所有的数据被取干净
print('主')

'''
执行结果:
在数据被取完之后 主进程自动停止 不会卡住等待
'''

7、线程理论

# 什么是线程
进程其实就是一个资源单位 真正被CPU执行的 其实是进程里面的线程

进程间数据默认是隔离的 但是同一个进程内的多个线程数据是共享的

'''
进程类似于工厂
线程类似于工厂里面的一条条流水线

所有的进程肯定至少含有一条线程
'''

8、开设线程的两种方式

# 进程
"""
开设进程的操作:
1.重新申请一块内存空间
2.将所需的资源全部导入
"""

# 线程
"""
开设线程两个步骤都不需要
所以开设线程消耗的资源 远比开设进程消耗的资源要少
"""

# 开设线程的方式一:

from threading import Thread
import time

def test(name):
print('%s 已经开启' % name)
time.sleep(3)
print('%s 已经关闭' % name)

t = Thread(target=test, args=('jason', ))
t.start()
print('主')

# 开设线程的方式二:
from threading import Thread
import time

class MyClass(Thread):
def __init__(self, name):
super().__init__()  # 因为源代码有__init__ 所以要再用类去调用 避免源代码不完整
self.name = name

def run(self):
print('%s正在开启' % self.name)
time.sleep(3)
print('%s正在关闭' % self.name)

obj = MyClass('jason')
obj.start()
print('主')

'''执行结果与方式一一致'''

9、线程对象的其他方法

1.join()方法

from threading import Thread
import time

def test(name):
print('%s 已经开启' % name)
time.sleep(3)
print('%s 已经关闭' % name)

t = Thread(target=test, args=('jason', ))
t.start()
t.join()  # 主线程等待子线程运行结束之后 再继续运行
print('主')

2.获取进程号(验证同一个进程内可以开设多个线程)
3.active_count统计当前正在活跃的线程数

from threading import Thread, active_count
import time
import os

def test(name):
print(os.getpid())  # 13428 所有的子线程同属一个进程 所以进程号都一致
print('%s 已经开启' % name)
time.sleep(3)
print('%s 已经关闭' % name)

t = Thread(target=test, args=('jason', ))
t.start()
print(os.getpid())  # 13428 所有的子线程同属一个进程 所以进程号都一致
print(active_count())  # 2 自带的主线程与开设的一条子线程
print('主')

4.current_thread

from threading import Thread, active_count, current_thread
import time
import os

def test(name):
print(os.getpid())  # 13428 所有的子线程同属一个进程 所以进程号都一致
print(current_thread().name)  # Thread-1 子线程
print('%s 已经开启' % name)
time.sleep(3)
print('%s 已经关闭' % name)

t = Thread(target=test, args=('jason', ))
t.start()
# t.join()  # 主线程等待子线程运行结束之后 再继续运行
print(os.getpid())  # 13428 所有的子线程同属一个进程 所以进程号都一致
print(active_count())  # 2 自带的主线程与开设的一条子线程
print(current_thread().name)  # MainThread 主线程
print('主')

10、守护线程

"""
主线程的结束意味着整个进程的结束
所以主线程需要等待里面所有非守护线程的结束才能结束!!!
"""

from threading import Thread
import time

def test(name):
print('%s 正在启动'% name)
time.sleep(3)
print('%s 已经结束'% name)

if __name__ == '__main__':
t = Thread(target=test, args=('线程1', ))
t.daemon = True  # 守护线程
t.start()
print('主')

执行结果:
线程1 正在启动
主

# 例题:

from threading import Thread
import time

def foo():
print(123)
time.sleep(1)
print("end123")
def bar():
print(456)
time.sleep(3)
print("end456")
if __name__ == '__main__':
t1=Thread(target=foo)
t2=Thread(target=bar)
t1.daemon=True
t1.start()
t2.start()
print("main-------")

"""
执行结果:
123
456
main-------
end123
end456

主线程等待所有非守护线程执行结束才会结束 所有结果全部打印
如果将:
foo time.sleep(3)
bar time.sleep(1)
执行结果:
123
456
main-------
end456

因为 bar子线程比 foo子线程结束的早
所以主线程结束 foo子线程还没有执行完毕 只打印了启动信息 就随着主线程守护结束了
"""

11、线程数据共享

from threading import Thread

money = 100

def test():
global money  # 更改全局信息
money = 999

t = Thread(target=test)
t.start()
t.join()
print(money)  # 999  进程的主线程获取到了子线程的数据信息

12、线程互斥锁

1.问题:多线程同时访问数据会错乱

from threading import Thread
import time

num = 100

def test():
global num
# 先获取num的数值
tmp = num
# 模拟延迟效果
time.sleep(1)
# 修改数值
tmp -= 1
num = tmp

t_list = []
for i in range(100):  # 让100个人去操作数据
t = Thread(target=test)
t.start()
t_list.append(t)
# 确保所有的子线程全部结束
for t in t_list:
t.join()
print(num)  # 主线程打印 结果 99(理论上100个人操作 结果应该为0)

2.解决:将并发变成串行

from threading import Thread
import time
from multiprocessing import Lock

num = 100

def test(mutex):
global num
mutex.acquire()
# 先获取num的数值
tmp = num
# 模拟延迟效果
time.sleep(0.1)
# 修改数值
tmp -= 1
num = tmp
mutex.release()

t_list = []
mutex = Lock()
for i in range(100):  # 让100个人去操作数据
t = Thread(target=test, args=(mutex, ))
t.start()
t_list.append(t)
# 确保所有的子线程全部结束
for t in t_list:
t.join()
print(num)  # 主线程打印 结果 0

13、TCP服务端实现并发

# 客户端client:
import socket

client = socket.socket()
client.connect(('127.0.0.1', 8080))

while True:
msg = input('>>>:')
client.send(msg.encode('utf8'))
data = client.recv(1024)
print(data.decode('utf8'))

# 服务端service:
import socket
from threading import Thread

server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)

def talk(sock):
while True:
try:
data = sock.recv(1024)
if len(data) == 0:break
print(data.decode('utf8'))
sock.send(data + b'big baby')
except ConnectionError as e:
print(e)
break
sock.close()

while True:
sock, addr = server.accept()
print(addr)
# 开设多线程
t = Thread(target=talk, args=(sock, ))
t.start()

执行结果:
服务端可以同时对多个客户端提供服务
实现了并发效果
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐