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

昨日内容回顾

  • 操作系统发展史
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能否用于多进程情况下的精确使用
    不能!!!

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

image

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的数据

image

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()

执行结果:
服务端可以同时对多个客户端提供服务
实现了并发效果
posted @ 2022-01-16 21:05  Deity_JGX  阅读(68)  评论(0)    收藏  举报