一、僵尸进程与孤儿进程
1、僵尸进程
-
一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵尸进程。详解如下
-
我们知道在正常情况下子进程是通过父进程创建的,子进程在创建新的进程。子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程到底什么时候结束,子进程结束,父进程没有明确的答复操作系统内核:已收到子进程结束的消息。此时操作系统内核会一直保存该子进程的部分PCB信息,即为僵尸进程
"""
僵尸进程危害场景:
例如有个进程,它定期的产 生一个子进程,这个子进程需要做的事情很少,做完它该做的事情之后就退
出了,因此这个子进程的生命周期很短,但是,父进程只管生成新的子进程,至于子进程 退出之后的事
情,则一概不闻不问,这样,系统运行上一段时间之后,系统中就会存在很多的僵死进程,倘若用ps命令
查看的话,就会看到很多状态为Z的进程。 严格地来说,僵死进程并不是问题的根源,罪魁祸首是产生出
大量僵死进程的那个父进程。因此,当我们寻求如何消灭系统中大量的僵死进程时,答案就是把产生大 量
僵死进程的那个元凶枪毙掉(也就是通过kill发送SIGTERM或者SIGKILL信号啦)。枪毙了元凶进程之后
,它产生的僵死进程就变成了孤儿进 程,这些孤儿进程会被init进程接管,init进程会wait()这些孤儿
进程,释放它们占用的系统进程表中的资源,这样,这些已经僵死的孤儿进程 就能瞑目而去了。
"""
# 测试:
# 手动制造一个产生僵尸进程的程序test.py内容如下
# coding:utf-8
from multiprocessing import Process
import time,os
def run():
print('子',os.getpid())
if __name__ == '__main__':
p=Process(target=run)
p.start()
print('主',os.getpid())
time.sleep(1000)
# 核心思想:父进程的知道子进程的结束,并且明确的回复操作系统,此时操作系统才能回收资源,避免
僵尸进程的产生
2、孤儿进程
-
当父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程,由于进程不可能脱离进程树而独立存在,孤儿进程将被PID为1的init进程所收养,并由init进程对它们完成状态收集工作。孤儿进程被收养后进行正常的释放,没有危害
-
创建完子进程后,主进程所在的这个脚本就退出了,当父进程先于子进程结束时,子进程会被init收养,成为孤儿进程,而非僵尸进程。演示如下:
import os,sys
import time
pid = os.getpid()
ppid = os.getppid()
print 'im father', 'pid', pid, 'ppid', ppid
pid = os.fork()
# 执行pid=os.fork()则会生成一个子进程
# 返回值pid有两种值:
# 如果返回的pid值为0,表示在子进程当中
# 如果返回的pid值>0,表示在父进程当中
if pid > 0:
print 'father died..'
sys.exit(0)
# 保证主线程退出完毕
time.sleep(1)
print 'im child', os.getpid(), os.getppid()
# 执行文件,输出结果:
im father pid 32515 ppid 32015
father died..
im child 32516 1
# 看,子进程已经被pid为1的init进程接收了,所以僵尸进程在这种情况下是不存在的,
# 存在只有孤儿进程而已,孤儿进程声明周期结束自然会被init来销毁。
ps: 简易可以更好的理解
# 僵尸进程
进程代码运行结束之后并没有直接结束而是需要等待回收子进程资源才能结束
# 孤儿进程
即主进程已经死亡(非正常)但是子进程还在运行
二、守护进程
-
守护一个服务,长期驻留在内存中提供服务,不能够受制于终端;
如何让一个进程成为守护进程?
主进程创建守护进程
注意:进程之间是互相独立的,主进程代码运行结束,守护进程随即终止
守护进程:即守护着某个进程 一旦这个进程结束那么也随之结束
from multiprocessing import Process
import os,time
def task():
print("进程%s开启" %os.getpid()) # 2.接着会打印进程开启,可以看到打印结果
time.sleep(10)
print("进程%s结束" %os.getpid()) # 看不到,在time.sleep(10)的时候主进程代码结束
if __name__ == '__main__':
p = Process(target=task)
p.daemon = True # 调用守护进程
p.start()
print("主:%s" %os.getpid()) # 1.先打印"主",主进程睡三秒的时间足够守护进程启动起来
time.sleep(3)
# 下方重复的代码
# ---------------------------------------
from multiprocessing import Process
import time
def test(name):
print('总管:%s is running' % name)
time.sleep(3)
print('总管:%s is over' % name)
if __name__ == '__main__':
p = Process(target=test, args=('jason',))
p.daemon = True # 设置为守护进程(一定要放在start语句上方)
p.start()
print("皇帝jason寿终正寝")
time.sleep(0.1)
三、互斥锁(同步锁)
-
进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的,而共享带来的是竞争,竞争带来的结果就是错乱,如何控制,就是加锁处
问题:并发情况下操作同一份数据 极其容易造成数据错乱
解决措施:将并发变成串行 虽然降低了效率但是提升了数据的安全
锁就可以实现将并发变成串行的效果
行锁、表锁
使用锁的注意事项
在主进程中产生 交由子进程使用
1.一定要在需要的地方加锁 千万不要随意加
2.不要轻易的使用锁(死锁现象)
# 在以后的编程生涯中 几乎不会解除到自己操作锁的情况
import json
from multiprocessing import Process, Lock
import time
import random
# 查票
def search(name):
with open(r'data.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'data.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'data.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()
ps:互斥锁理解
加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行的修改,没错,速度是慢了,但牺牲了速度却保证了数据安全。
虽然可以用文件共享数据实现进程间通信,但问题是:
-
1.效率低(共享数据基于文件,而文件是硬盘上的数据)
-
2.需要自己加锁处理
三、消息队列
-
进程彼此之间互相隔离,要实现进程间通信(IPC),multiprocessing模块支持两种形式:队列和管道,这两种方式都是使用消息传递的
-
创建队列的类(底层就是以管道和锁定的方式实现):
'''
Queue([maxsize]):创建共享的进程队列,Queue是多进程安全的队列,可以使用Queue实现多进程之间的
数据传递。
'''
参数介绍:
maxsize是队列中允许最大项数,省略则无大小限制。
队列:先进先出
队列:先进先出
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())
# q.put(666) # 超出范围原地等待 直到有空缺位置
# 提取数据
print(q.get())
print(q.get())
print(q.get())
print(q.get())
print(q.get())
# print(q.get()) # 没有数据之后原地等待直到有数据为止
print(q.get_nowait()) # 没有数据立刻报错
"""
full和get_nowait能否用于多进程情况下的精确使用
不能!!!
队列的使用就可以打破进程间默认无法通信的情况
"""
四、IPC机制
研究思路 :
-
1.主进程跟子进程借助于队列通信
-
2.子进程跟子进程借助于队列通信
from multiprocessing import Queue, Process
def producer(q):
q.put("子进程p放的数据")
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()
# q.put('主进程放的数据')
# p = Process(target=consumer, args=(q,))
# p.start()
# p.join()
# print(q.get())
# print('主')
五、生产者消费者模型
1、 程序中有两类角色
一类负责生产数据(生产者)
一类负责处理数据(消费者)
2、引入生产者消费者模型为了解决的问题是:
平衡生产者与消费者之间的工作能力,从而提高程序整体处理数据的速度
"""
生产者
负责产生数据(做包子的)
消费者
负责处理数据(吃包子的)
该模型需要解决恭喜不平衡现象
"""
from multiprocessing import Queue, Process, JoinableQueue
import time
import random
def producer(name, food, q):
for i in range(10):
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()
p1 = Process(target=producer, args=('大厨jason', '玛莎拉', q))
p2 = Process(target=producer, args=('印度阿三', '飞饼', q))
p3 = Process(target=producer, args=('泰国阿人', '榴莲', q))
c1 = Process(target=consumer, args=('班长阿飞', q))
p1.start()
p2.start()
p3.start()
c1.daemon = True
c1.start()
p1.join()
p2.join()
p3.join()
q.join() # 等待队列中所有的数据被取干净
print('主')
六、线程理论
-
什么是线程?
-
线程:一个流水线的运行过程
-
进程内代码的运行过程
-
什么是线程?
进程其实是一个资源单位 真正被CPU执行的其实是进程里面的线程
"""
进程类似于是工厂 线程类似于是工厂里面的一条条流水线
所有的进程肯定含有最少一个线程
"""
进程间数据默认是隔离的 但是同一个进程内的多个线程数据是共享的
# 进程与线程的区别
- 1.线程共享创建它的进程的地址空间;进程有自己的地址空间
- 2.线程可以直接访问其进程的数据段;进程有自己的父进程的数据段副本
- 3.线程可以直接与其进程中的其他线程通信;进程必须使用进程间通信来与同级进程通信
- 4.新线程很容易创建;新的进程需要父进程的复制。
- 5.线程可以对同一进程的线程进行相当大的控制;进程只能对子进程进行控制。
- 6.主线程的改变(取消,优先级的改变等)可能会影响进程中其他线程的行为;父进程的更改不会影响子进程
总结:
1、同一进程下的多个线程共享该进程的内存资源
2、开启子线程的开销要远远小于开启子进程
线程对象的其它方法
1.join方法
2.获取进程号(验证同一个进程内可以开设多个线程)
3.active_count统计当前正在活跃的线程数
4.current_thread
七、守护线程
守护进程是守护主进程的代码
守护线程是守护主线程的生命周期
无论是进程还是线程,都遵循:守护xxx会等待主xxx运行完毕后被销毁
需要强调的是:运行完毕并非终止运行
"""
主线程的结束意味着整个进程的结束
所以主线程需要等待里面所有非守护线程的结束才能结束
"""
from threading import Thread
from multiprocessing import Process
import time
def foo():
print(123)
time.sleep(3)
print("end123")
def bar():
print(456)
time.sleep(1)
print("end456")
if __name__ == '__main__':
t1=Thread(target=foo)
t2=Thread(target=bar)
t1.daemon=True
t1.start()
t2.start()
print("main-------")
八、线程数据共享
-
加上互斥锁,哪个线程抢到这个锁我们决定不了,哪个先抢到锁的那么就是哪个线程先执行,没有抢到的线程需要等待
-
加上互斥锁多任务瞬间变成单任务,性能会下降,也就是说同一时刻只能有一个线程去执行
from threading import Thread, Lock
from multiprocessing import Lock
import time
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):
t = Thread(target=test, args=(mutex,))
t.start()
t_list.append(t)
# 确保所有的子线程全部结束
for t in t_list:
t.join()
print(num)
TCP服务端实现并发
import socket
from threading import Thread
from multiprocessing import Process
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'gun dan!')
except ConnectionResetError as e:
print(e)
break
sock.close()
while True:
sock, addr = server.accept()
print(addr)
# 开设多进程或者多线程
t = Thread(target=talk, args=(sock,))
t.start()