Python-进程(1)

操作系统发展史

穿孔卡片

早期程序员使用穿孔卡片编程,

只有一个计算机机房,一次只能读取一个卡片,

所以造成CPU的利用率极低

联机批处理系统

后来出现了联机批处理系统,支持多用户去使用一个计算机机房

统计批处理系统

再后来出现了脱机批处理系统,有了高速磁盘,提高了文件的读取速度,优点是提高了CPU的利用率

单道

基于单核情况下研究分为单道和多道,单道即多个程序在使用CPU时是串行运行

多道技术

当代计算机使用的技术 是多道技术

空间上复用

一个CPU可以提供多个用户去使用

时间上复用

切换 + 保存状态

若CPU 遇到 IO 操作,会立即将当前执行程序CPU使用权断开

优点:CPU利用率高

若一个程序使用CPU的时间过长,会立即将当前执行程序CPU使用权断开

缺点:程序执行效率低

并行与并发

并行:指的是看起来像同时在运行,其实是多个程序不停 切换 + 保存状态

并发:真正意义上的同时运行,在多核(多个CPU)情况下,同时执行多个程序

进程

执行中的程序叫做进程(Process),是一个动态的概念

在linux中查看进程信息:top

61546 root 20 0 15028 1196 836 R 4.4 0.1 0:00.35 top

程序与进程

程序:一堆代码文件

进程:一堆代码文件运行的过程

进程调度

当代操作系统调度:

时间片轮转法 + 分级反馈队列

1、 先来先服务调度

a,b 程序,若a程序先来,先占用CPU

缺点:程序a先使用,程序b必须等待程序a使用CPU完毕之后才能使用

2、 短作业优先调度

a,b程序,谁的用时短,优先调度谁使用CPU

缺点:

若程序a使用时间最长,有N个程序使用时间段,

必须等待所有用时短的程序结束后才能使用

3、 时间片轮转法

CPU执行的时间1秒钟,若加载N个程序,将时间等分成多n个时间片供程序执行

4、 分级反馈队列

将执行优先级分多层级别

1级:优先级最高

2级:优先级第二,以此类推

。。。

进程的三个状态

就绪态

所有程序创建时都会进入就绪态,准备调度

运行态

调度后的进程,进入运行态

阻塞态

凡是遇到IO操作的进程,都会进入阻塞态

若IO结束,必须重新进入就绪态

同步和异步

指的是提交任务的方式

同步:若有两个任务需要提交,在提交第一个任务时,必须等待该任务执行结束后,才能继续提交并执行第二个任务。

同步演示:

# coding=utf-8

import time

def test():
    # 睡一秒 也是IO操作
    time.sleep(1)

    # 计算操作不属于IO操作
    n = 0
    for i in range(1000):
        n += i
    print(n)

if __name__ == '__main__':
    test()		# 先执行完test函数,再执行下面的代码,同步操作
    print("hello world")

异步:

若有两个任务需要提交,在提交第一个任务时,不需要原地等待,立即可以提交并执行第二个任务。

阻塞与非阻塞

阻塞:

阻塞态,遇到IO 一定会阻塞

非阻塞:

就绪态

运行态

创建进程

创建进程的两种方式

'''
创建进程方式一:
'''
from multiprocessing import Process
import time

def task(name):
    print(f"{name} 子任务start...")
    time.sleep(2)
    print(f"{name} 子任务end....")

if __name__ == '__main__':
    p = Process(target=task,args=("qinyj",))
    p.start()
    print("主进程...")

代码解释:

p = Process(target=task,args=("qinyj",)):实例化一个对象p,Process类参数:target=函数名,args=函数参数(必须是元组 + ,)

p.start():向操作系统发送指令开启一个子进程,至于这个子进程什么时候启动,要看机器的硬件性能

打印结果:

主进程... qinyj 子任务start... qinyj 子任务end....

'''
创建进程方式二:
'''
from multiprocessing import Process
import time

class MyProcess(Process):
    def run(self):
        print(f"子任务 start...")
        time.sleep(2)
        print(f"子任务 end....")

if __name__ == '__main__':
    p = MyProcess()
    p.start()
    print("主进程...")

代码解释

p = MyProcess():实例化自己写的一个类,这个类必须继承Process

p.start():向操作系统发送指令开启一个子进程,至于这个子进程什么时候启动,要看机器的硬件性能

打印结果:

主进程... 子任务 start... 子任务 end....

那么为什么我们要用if name == 'main':?,

是因为Windows在执行这个文件的时候,会自动import导入这个文件,导入这个文件相当于又重新执行了一遍里面的代码,这样子进程就重新又被创建了出来,子进程然后又运行到创建子进程的代码。。。无限循环,最后报错。那么在Linux中执行这个文件不会自动import导入,它所创建的子类代码仅仅是执行的函数代码,fork了一块新的内存空间执行这个函数,所以不会报错

那么我们为了统一使用,最好加上if name == 'main':,意思是作为执行文件执行的时候,判断它的名字,条件符合了再执行下面的代码,这样无论哪个平台运行代码都不会报错。

join的用法

# coding=utf-8
from multiprocessing import Process
import time

def task(name):
    print(f"{name} 子任务 start...")
    time.sleep(2)
    print(f"{name} 子任务 end...")

if __name__ == '__main__':
    p1 = Process(target=task,args=("qinyj",))
    p2 = Process(target=task,args=("jack",))
    p3 = Process(target=task,args=("qyj",))

    p1.start()
    p2.start()
    p3.start()

    p1.join()
    p2.join()
    p3.join()
    print("主进程...")

p.join:用来告诉操作系统,让子进程结束后,父进程在结束

进程间数据相互隔离

# coding=utf-8

from multiprocessing import Process

x = 100
def func():
    print("函数执行")
    global x
    x = 200
    print(f"函数内部的x:{x}")

if __name__ == '__main__':
    p = Process(target=func)
    p.start()
    p.join()
    print(f"主进程的x:{x}")

    
函数执行
函数内部的x:200
主进程的x:100

进程对象属性

# coding=utf-8

from multiprocessing import Process
from multiprocessing import current_process
import os
import time

def task(name):
    print(f"{name} 子任务start...",f"子进程进程号:{current_process().pid}")
    print(f"{name} 子任务start...",f"子进程进程号:{os.getpid()}",f"父进程进程号{os.getppid()}")
    time.sleep(200)
    print(f"{name} 子任务end.....",current_process().pid)

if __name__ == '__main__':
    p = Process(target=task,args=("qinyj",))
    p.start()
    time.sleep(1)

    print(p.is_alive())
    p.terminate()
    time.sleep(1)

    print(p.is_alive())

    p.join()
    print("主程序start..",os.getpid())
    print("主主进程start...",os.getppid())

    
    
qinyj 子任务start... 子进程进程号:9132
qinyj 子任务start... 子进程进程号:9132 父进程进程号7348
True
False
主程序start.. 7348
主主进程start... 2812

代码解释:

p.terminate():直接告诉操作系统,终止子进程

print(p.is_alive()):打印子进程的存活状态(True or False)

current_process().pid:获取当前的pid号

os.getpid():获取当前的pid号

os.getppid():获取当前父进程的pid号

cmd中查看进程号:tasklist |findstr 进程号

C:\Users\Administrator>tasklist | findstr 6724		# 查看子进程pid号即python解释器的进程
python.exe                    6724 Console                    1     30,576 K

C:\Users\Administrator>tasklist | findstr 2812		# 查看主进程pid号即pycharm解释器的进程
pycharm64.exe                 2812 Console                    1    909,528 K

由进程产生的pid号会自动 由主进程回收,如果子进程存在,主进程死掉了,那么子进程就是僵尸进程

两种进程号回收的方法条件:

1、 join:主进程可以等待子进程结束 pid一起被回收

2、 主进程正常结束,守护进程开启,子进程与主进程 pid被回收

守护进程

# coding=utf-8
from multiprocessing import Process
from multiprocessing import current_process
import os
import time

def task(name):
    print(f"{name} 子进程start...",current_process().pid)
    time.sleep(5)
    print(f"{name} 子进程end...",current_process().pid)

if __name__ == '__main__':
    p = Process(target=task,args=("qinyj",))
    p.daemon = True
    p.start()
    time.sleep(1)	# 停留1s 让子进程启动
    print("主进程启动...")

    
qinyj 子进程start... 5680
主进程启动...

代码解释:

p.daemon = True:添加守护进程,True

守护进程表示的是 主进程死的时候,无论子进程在干什么,直接干掉子进程,跟着主程序一起死掉

僵尸进程

僵尸进程指的是子进程已经结束,但pid号还存在没有被销毁,可以比喻人死了,身份证还没有注销,那这个pid就会一直被占用,那么操作系统的进程号是有限的,如果产生大量的僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程,此为僵尸进程的危害,应当避免

那么我们可以通过查找这个pid号kill 手动杀掉回收

僵尸进程的缺点:占用pid号,占用操作系统的资源

孤儿进程

孤儿进程指的是子进程还在执行,但父进程意外结束,但是有操作系统优化机制,会提供一个孤儿院,专门帮那些已经死了的主进程 回收那些没有父亲的子进程,在linux操作系统中,这个孤儿院就是init进程,pid号是1

1 root 20 0 19364 644 424 S 0.0 0.1 0:17.45 init

posted @ 2019-10-21 17:32  GeminiMp  阅读(188)  评论(0编辑  收藏  举报