进程

什么是进程

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

狭义定义:进程是正在运行的程序的实例(an instance of a computer program that is being executed)。
广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
 进程的概念
 操作系统引入进程的概念的原因
 进程的特征
 进程与程序中的区别

注意:同一个程序执行两次,就会在操作系统中出现两个进程,所以我们可以同时运行一个软件,分别做不同的事情也不会混乱。

进程调度

要想多个进程交替运行,操作系统必须对这些进程进行调度,这个调度也不是随即进行的,而是需要遵循一定的法则,由此就有了进程的调度算法。

 先来先服务调度算法
 短作业优先调度算法
 时间片轮转法
 多级反馈队列

进程的并行与并发

并行 : 并行是指两者同时执行,比如赛跑,两个人都在不停的往前跑;(资源够用,比如三个线程,四核的CPU )

并发 : 并发是指资源有限的情况下,两者交替轮流使用资源,比如一段路(单核CPU资源)同时只能过一个人,A走一段后,让给B,B用完继续给A ,交替使用,目的是提高效率。

区别:

并行是从微观上,也就是在一个精确的时间片刻,有不同的程序在执行,这就要求必须有多个处理器。
并发是从宏观上,在一个时间段上可以看出是同时执行的,比如一个服务器同时处理多个session。

同步异步阻塞非阻塞

状态介绍

  在了解其他概念之前,我们首先要了解进程的几个状态。在程序运行的过程中,由于被操作系统的调度算法控制,程序会进入几个状态:就绪,运行和阻塞。

  (1)就绪(Ready)状态

  当进程已分配到除CPU以外的所有必要的资源,只要获得处理机便可立即执行,这时的进程状态称为就绪状态。

  (2)执行/运行(Running)状态当进程已获得处理机,其程序正在处理机上执行,此时的进程状态称为执行状态。

  (3)阻塞(Blocked)状态正在执行的进程,由于等待某个事件发生而无法执行时,便放弃处理机而处于阻塞状态。引起进程阻塞的事件可有多种,例如,等待I/O完成、申请缓冲区不能满足、等待信件(信号)等。

      

同步和异步

      所谓同步就是一个任务的完成需要依赖另外一个任务时,只有等待被依赖的任务完成后,依赖的任务才能算完成,这是一种可靠的任务序列。要么成功都成功,失败都失败,两个任务的状态可以保持一致。

  所谓异步是不需要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工作,依赖的任务也立即执行,只要自己完成了整个任务就算完成了。至于被依赖的任务最终是否真正完成,依赖它的任务无法确定,所以它是不可靠的任务序列

 例子

阻塞与非阻塞

      阻塞和非阻塞这两个概念与程序(线程)等待消息通知(无所谓同步或者异步)时的状态有关。也就是说阻塞与非阻塞主要是程序(线程)等待消息通知时的状态角度来说的

 例子

同步/异步与阻塞/非阻塞

  1. 同步阻塞形式

  效率最低。拿上面的例子来说,就是你专心排队,什么别的事都不做。

  1. 异步阻塞形式

  如果在银行等待办理业务的人采用的是异步的方式去等待消息被触发(通知),也就是领了一张小纸条,假如在这段时间里他不能离开银行做其它的事情,那么很显然,这个人被阻塞在了这个等待的操作上面;

  异步操作是可以被阻塞住的,只不过它不是在处理消息时阻塞,而是在等待消息通知时被阻塞。

  1. 同步非阻塞形式

  实际上是效率低下的。

  想象一下你一边打着电话一边还需要抬头看到底队伍排到你了没有,如果把打电话和观察排队的位置看成是程序的两个操作的话,这个程序需要在这两种不同的行为之间来回的切换,效率可想而知是低下的。

  1. 异步非阻塞形式

  效率更高,

  因为打电话是你(等待者)的事情,而通知你则是柜台(消息触发机制)的事情,程序没有在两种不同的操作中来回切换

  比如说,这个人突然发觉自己烟瘾犯了,需要出去抽根烟,于是他告诉大堂经理说,排到我这个号码的时候麻烦到外面通知我一下,那么他就没有被阻塞在这个等待的操作上面,自然这个就是异步+非阻塞的方式了。

  

很多人会把同步和阻塞混淆,是因为很多时候同步操作会以阻塞的形式表现出来,同样的,很多人也会把异步和非阻塞混淆,因为异步操作一般都不会在真正的IO操作处被阻塞

进程的创建与结束

进程的创建

  但凡是硬件,都需要有操作系统去管理,只要有操作系统,就有进程的概念,就需要有创建进程的方式,一些操作系统只为一个应用程序设计,比如微波炉中的控制器,一旦启动微波炉,所有的进程都已经存在。

  而对于通用系统(跑很多应用程序),需要有系统运行过程中创建或撤销进程的能力,主要分为4中形式创建新的进程:

  1. 系统初始化(查看进程linux中用ps命令,windows中用任务管理器,前台进程负责与用户交互,后台运行的进程与用户无关,运行在后台并且只在需要时才唤醒的进程,称为守护进程,如电子邮件、web页面、新闻、打印)

  2. 一个进程在运行过程中开启了子进程(如nginx开启多进程,os.fork,subprocess.Popen等)

  3. 用户的交互式请求,而创建一个新进程(如用户双击暴风影音)

  4. 一个批处理作业的初始化(只在大型机的批处理系统中应用)

  无论哪一种,新进程的创建都是由一个已经存在的进程执行了一个用于创建进程的系统调用而创建的。  

 创建进程

进程的结束

  1. 正常退出(自愿,如用户点击交互式页面的叉号,或程序执行完毕调用发起系统调用正常退出,在linux中用exit,在windows中用ExitProcess)

  2. 出错退出(自愿,python a.py中a.py不存在)

  3. 严重错误(非自愿,执行非法指令,如引用不存在的内存,1/0等,可以捕捉异常,try...except...)

  4. 被其他进程杀死(非自愿,如kill -9)

在python程序中的进程操作

multiprocessing模块:详细来说该模块是一个包,因含模块比较多,在此分为四部分:创建进程部分;进程同步部分;进程池部分;进程间通讯部分。

multiprocess.process模块

process模块用来创建进程。

Process([group [, target [, name [, args [, kwargs]]]]]),由该类实例化得到的对象,表示一个子进程中的任务(尚未启动)

强调:
1. 需要使用关键字的方式来指定参数
2. args指定的为传给target函数的位置参数,是一个元组形式,必须有逗号

参数介绍:
1 group参数未使用,值始终为None
2 target表示调用对象,即子进程要执行的任务
3 args表示调用对象的位置参数元组,args=(1,2,'egon',)
4 kwargs表示调用对象的字典,kwargs={'name':'egon','age':18}
5 name为子进程的名称

process进程的方法:

1 p.start():启动进程,并调用该子进程中的p.run()
2 p.run():进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法 
3 p.terminate():强制终止进程p,不会进行任何清理操作,如果p创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。如果p还保存了一个锁那么也将不会被释放,进而导致死锁
4 p.is_alive():如果p仍然运行,返回True
5 p.join([timeout]):主线程等待p终止(强调:是主线程处于等的状态,而p是处于运行的状态)。timeout是可选的超时时间,需要强调的是,p.join只能join住start开启的进程,而不能join住run开启的进程

 

process进程的属性:

1 p.daemon:默认值为False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随之终止,并且设定为True后,p不能创建自己的新进程,必须在p.start()之前设置
2 p.name:进程的名称
3 p.pid:进程的pid
4 p.exitcode:进程在运行时为None、如果为–N,表示被信号N结束(了解即可)
5 p.authkey:进程的身份验证键,默认是由os.urandom()随机生成的32字符的字符串。这个键的用途是为涉及网络连接的底层进程间通信提供安全性,这类连接只有在具有相同的身份验证键时才能成功(了解即可)

在Windows操作系统下使用该模块应注意:

在Windows操作系统中由于没有fork(linux操作系统中创建进程的机制),在创建子进程的时候会自动 import 启动它的这个文件,而在 import 的时候又执行了整个文件。因此如果将process()直接写在文件中就会无限递归创建子进程报错。所以必须把创建子进程的部分使用if __name__ ==‘__main__’ 判断保护起来,import 的时候  ,就不会递归运行了。

 使用process创建进程

import time
from multiprocessing import Process

def pr(name):
    print('hello,%s'%name)
    print('This is the first process.')
if __name__=='__main__':
    p=Process(target=pr,args=('jim',))  #创建进程
    p.start()             #运行进程
    time.sleep(1)
    print('执行主进程的内容了!')
python第一个进程
import time
from multiprocessing import Process

def pr(name):
    print('hello,%s'%name)
    time.sleep(1)
    print('我是子进程.')
if __name__=='__main__':
    p=Process(target=pr,args=('jim',)) 
    p.start()             
    p.join()              #等待p进程完毕后再运行父进程
    print('我是父进程!')
join方法
import os
from multiprocessing import Process

def f(x):
    print('子进程ID:',os.getpid(),'父进程ID:',os.getppid()) #第二个getppid()双p
    print(x*'*')
if __name__=='__main__':
    print('主进程ID:',os.getpid())
    for i in range(5):
        p = Process(target=f, args=(i,))
        p.start()
查看子进程和主进程的进程号

进阶,多个进程同时运行(注意,子进程的执行顺序不是根据启动顺序决定的

import time
from multiprocessing import Process

def f(name):
    print('hello',name)
    time.sleep(1)
if __name__=='__main__':
    p_list=[]
    for i in range(5):
        p = Process(target=f, args=('jim%s'%i,))
        p.start()
多进程同时进行
import time
from multiprocessing import Process

def f(name):
    print('hello',name)
    time.sleep(2)
if __name__=='__main__':
    p_list=[]
    for i in range(5):
        p = Process(target=f, args=('jim%s'%i,))
        p.start()
        p_list.append(p)
        p.join()
    [p.join() for p in p_list]     #子进程异步运行,最后和主进程同步
    print('主进程在执行!')
多进行同时进行,再谈join方法2
import time
from multiprocessing import Process

def f(name):
    print('hello',name)
    time.sleep(2)
if __name__=='__main__':
    p_list=[]
    for i in range(5):
        p = Process(target=f, args=('jim%s'%i,))
        p.start()
        p_list.append(p)
        p.join()           #子进程同步运行,最后和主进程同步

    print('主进程在执行!')
多进程,join方法1

 除了上面这些开启进程的方法,还有一种以继承Process类的形式开启进程的方式

import os
from multiprocessing import Process

class MyProcess(Process):
    def __init__(self,name):
        super().__init__()
        self.name=name

    def run(self):
        print(os.getpid())
        print('%s正在直播'%self.name)

if __name__=='__main__':
    p1=MyProcess('Lilei')
    p2=MyProcess('天昊')
    p1.start()    #start会自动调用
    p2.start()
    p1.join()
    p2.join()
print('主程序')
类方法开启进程

 

进程之间的数据隔离问题

 进程之间的数据隔离问题

守护进程

会随着主进程的结束而结束。

主进程创建守护进程

  其一:守护进程会在主进程代码执行结束后就终止

  其二:守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes are not allowed to have children

注意:进程之间是互相独立的,主进程代码运行结束,守护进程随即终止

 守护进程的启动
 主进程代码执行结束守护进程立即结束

socket聊天实现并发实例

import time
from multiprocessing import Process
from socket import *
sever=socket(AF_INET,SOCK_STREAM)
sever.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
sever.bind(('127.0.0.1',8088))
sever.listen(5)

def talk(conn,client_addr):
    while True:
        try:
            msg=conn.recv(1024)
            if not msg:break
            conn.send(msg.upper())
        except Exception:
            break
if __name__=='__main__':         #windows下start进程一定要写到这下面
    while True:
        conn,adrr=sever.accept()
        p=Process(target=talk,args=(conn,adrr))
        p.start()
server端
import socket
client=socket.socket()
client.connect(('127.0.0.1',8088))

while True:
    msg=input('>>>').strip()
    if not msg:continue
    client.send(msg.encode('utf-8'))
    ret=client.recv(1024)
    print(ret.decode('utf-8'))
client端

多进程中的其他方法

 进程对象的其他方法:terminate,is_alive
 进程对象的其他属性:pid和name

 进程同步控制  锁,信号量,事件

锁:multiprocess.Lock

当多进程同时使用一份数据资源时,就会引发数据安全与混乱。我们以抢票为例,看看锁的使用:

#文件db的内容为:{"count":1}
#注意一定要用双引号,不然json无法识别
#并发运行,效率高,但竞争写同一文件,数据写入错乱
from multiprocessing import Process,Lock
import time,json
def show():
    dic=json.load(open('db'))
    print('\033[43m剩余票数%s\033[0m' %dic['count'])
def buy_ticket():
    dic = json.load(open('db'))
    time.sleep(0.1)    #模拟读数据网络延迟
    if dic['count']>0:
        dic['count']-=1
        time.sleep(0.2) #模拟写数据网络延迟
        json.dump(dic,open('db','w'))
        print('\033[43m购票成功\033[0m')
def work():
    show()
    buy_ticket()

if __name__=='__main__':
    for i in range(100):  #模拟100个人抢票
        p=Process(target=work)
        p.start()
多进程同时抢购余票
#文件db的内容为:{"count":3}
#注意一定要用双引号,不然json无法识别
#并发运行,效率高,但竞争写同一文件,数据写入错乱
from multiprocessing import Process,Lock
import time,json
def show():
    dic=json.load(open('db'))
    print('\033[43m剩余票数%s\033[0m' %dic['count'])
def buy_ticket(i):
    dic = json.load(open('db'))
    time.sleep(0.1)    #模拟读数据网络延迟
    if dic['count']>0:
        dic['count']-=1
        time.sleep(0.2) #模拟写数据网络延迟
        json.dump(dic,open('db','w'))
        print('\033[43m%s购票成功\033[0m'%i)
def work(lock,i):
    lock.acquire()
    show()
    buy_ticket(i)
    lock.release()

if __name__=='__main__':
    lock = Lock()
    for i in range(10):  #模拟10个人抢票
        p=Process(target=work,args=(lock,i+1))
        p.start()
使用锁来保证数据安全
#加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行的修改,没错,速度是慢了,但牺牲了速度却保证了数据安全。
虽然可以用文件共享数据实现进程间通信,但问题是:
1.效率低(共享数据基于文件,而文件是硬盘上的数据)
2.需要自己加锁处理

#因此我们最好找寻一种解决方案能够兼顾:1、效率高(多个进程共享一块内存的数据)2、帮我们处理好锁问题。这就是mutiprocessing模块为我们提供的基于消息的IPC通信机制:队列和管道。
队列和管道都是将数据存放于内存中
队列又是基于(管道+锁)实现的,可以让我们从复杂的锁问题中解脱出来,
我们应该尽量避免使用共享数据,尽可能使用消息传递和队列,避免处理复杂的同步和锁问题,而且在进程数目增多时,往往可以获得更好的可获展性。

信号量:multiprocess.Semaphore
#多进程的组件
#一套资源,同一时间,只能被n个人访问
#一段代码,同一时间,只能被n个进程执行
from multiprocessing import Process,Semaphore
import time,random
def ktv(i,sem):
    sem.acquire()
    print('\033[43m第%s人进入KTV\033[0m'%i)
    time.sleep(random.randint(1,3))
    print('\033[42m第%s人走出KTV\033[0m' % i)
    sem.release()
if __name__=='__main__':
    sem=Semaphore(4)
    for i in range(10):
        p=Process(target=ktv,args=(i,sem))
        p.start()
同步控制—信号量

 

事件:multiprocess.Event
事件方法简介
#事件
from multiprocessing import Process,Event
#一个信号可以是所有进程都进入堵塞状态
#也可以控制所有进程解除堵塞
#一个事件创建后,默认为堵塞状态
e=Event()  #创建一个事件
print(e.is_set())  #查看一个事件的状态,默认为堵塞
e.set()  #将这个事件的状态改为Ture
print(e.is_set())
e.wait() #根据is_set的值决定是否堵塞
print('123456')
e.clear()#将这个事件的状态改为False
print(e.is_set())
e.wait()  #is_set值为False,堵塞
print('*'*10)


#set和clear
    #分别修改一个事件的状态,True 和 False
#is_set 查看一个事件的状态
#wait  根据事件的状态决定是否堵塞
#      False堵塞  True不堵塞
Event方法简介
from multiprocessing import Process,Event
import time,random
def cars(e,i):
    if not e.is_set():
        print('\33[31mcar%s is waiting\33[0m'%i)
        e.wait()    # 阻塞 直到得到一个 事件状态变成 True 的信号
    print('\33[32mcar%s is crossing\33[0m'%i)

def light(e):
    while True:
        if e.is_set():
            e.clear()
            print('\33[31m红灯亮了\33[0m')
        else:
            e.set()
            print('\33[32m绿灯亮了\33[0m')
        time.sleep(2)
if __name__=='__main__':
    e=Event()
    light1 = Process(target=light, args=(e, ))
    light1.start()
    for i in range(20):
        car=Process(target=cars,args=(e,i))
        car.start()
        time.sleep(random.random())
红绿灯事件—Event

 

 

 

posted @ 2019-04-05 09:12  【小桥流水人家】  阅读(208)  评论(0)    收藏  举报