python-并发编程之进程


1.进程
# 1.进程的概念
狭义: 进程是正在运行的程序实例
广义: 进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动. 它是操作系统动态执行的基本单元.
# 解释:
程序是一个没有生命的实体, 只有处理器赋予程序生命时(执行), 它才能成为一个活动的实体, 即进程.
# 2.进程与程序
程序是指令和数据的有序集合, 其本身没有任何运行的含义, 是一个静态的资源.
而进程是程序在系统中一次执行的过程, 是动态的.
程序可以作为软件资料长期存在, 而进程是有一定生命周期的.

2.进程调度
# 注意: 以单核cup为例, 同一时刻cpu只能处理一个进程
# 问题:
电脑上的进程非常多, 肯定超过了你的CPU核数了, 难道超出cpu核数的其他进程不运行了吗?
# 系统调度算法:
1.先来先服务调度算法(FCFS):
    先来先服务调度算法是一种最简单的调度算法,用于进程调度。FCFS算法比较有利于长作业(进程),而不利于短作业(进程)。由此可知,本算法适合于CPU繁忙型作业,而不利于I/O繁忙型的作业(进程)
    
2.短作业(进程)优先调度算法(SJ/PF)
	短作业(进程)优先调度算法(SJ/PF)是指对短作业或短进程优先调度的算法,该算法既可用于作业调度,也可用于进程调度。但其对长作业不利;不能保证紧迫性作业(进程)被及时处理;作业的长短只是被估算出来的。

3.时间片轮转法
	时间片轮转(Round Robin,RR)法的基本思路是让每个进程在就绪队列中的等待时间与享受服务的时间成比例。在时间片轮转法中,需要将CPU的处理时间分成固定大小的时间片. 如果一个进程在被调度选中之后用完了系统规定的时间片,但又未完成要求的任务,则它自行释放自己所占有的CPU而排到就绪队列的末尾,等待下一次调度。同时,进程调度程序又去调度当前就绪队列中的第一个进程。

4.多级反馈队列调度算法
	(1) 应设置多个就绪队列,并为各个队列赋予不同的优先级。第一个队列的优先级最高,第二个队列次之,其余各队列的优先权逐个降低。该算法赋予各个队列中进程执行时间片的大小也各不相同,在优先权愈高的队列中,为每个进程所规定的执行时间片就愈小。
	(2) 当一个新进程进入内存后,首先将它放入第一队列的末尾,按FCFS(先来先服务)原则排队等待调度。当轮到该进程执行时,如它能在该时间片内完成,便可准备撤离系统;如果它在一个时间片结束时尚未完成,调度程序便将该进程转入第二队列的末尾,再同样地按FCFS原则等待调度执行;如果它在第二队列中运行一个时间片后仍未完成,再依次将它放入第三队列,……,如此下去,当一个长作业(进程)从第一队列依次降到第n队列后,在第n 队列便采取按时间片轮转的方式运行。
	(3) 仅当第一队列空闲时,调度程序才调度第二队列中的进程运行;仅当第1(i-1)队列均空时,才会调度第i队列中的进程运行。如果处理机正在第i队列中为某进程服务时,又有新进程进入优先权较高的队列(1(i-1)中的任何一个队列),则此时新进程将抢占正在运行进程的处理机,即由调度程序把正在运行的进程放回到第i队列的末尾,把处理机分配给新到的高优先权进程。

3.并发与并行
# 并行:
并行是同一时刻多个任务一起执行

# 并发:
并发是同一时间段多个任务一起执行

# 区别:
并行是微观上在一个精确的时间片刻, 多个程序在执行, 要求有多个处理器.
并发是宏观上, 在一个时间段上看起来是多个程序在执行.

4.进程三状态
程序的运行与系统的调度有关, 在程序运行的过程中涉及到集中状态: 就绪   运行   阻塞

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BdRjCTcs-1608166093401)(image/1018556-20170320183157627-2082017524.png)]

(1).就绪(Ready)状态
	当进程已分配到除CPU以外的所有必要的资源,只要获得处理机便可立即执行,这时的进程状态称为就绪状态。
(2).执行/运行(Running)状态
	当进程已获得处理机,其程序正在处理机上执行,此时的进程状态称为执行状态。
(3).阻塞(Blocked)状态
	正在执行的进程,由于等待某个事件发生而无法执行时,便放弃处理机而处于阻塞状态。引起进程阻塞的事件可有多种,例如,等待I/O完成、申请缓冲区不能满足、等待信件(信号)等。
# 体会一下三个状态
import time

while 1:
    print("--------------------------------------------")
    msg = input('请输入: ')
    for times in range(3):
        print(msg)
    time.sleep(3)

5.python进程的创建
# multiprocess模块
multiprocess模块本质上并不是一个模块, 而是一个关于进程管理和操作的包.
# process模块: 创建进程
import time
from multiprocessing import Process

# 子进程任务
def sub_process():
    while 1:
        print("-----subprocess------")
        time.sleep(1)
        
if __name__=='__main__':
    sbp = Process(target=sub_process, args=(a, ), kwargs={'name': '李晴晴'})
    sbp.start()
    while 1:
        print('----mainprocess-----')
        time.sleep(1)
        
# 说明:
1.整个py文件右键run就会创建一个进程, 这个进程称为主进程
2.在程序中创建了一个进程sbp, sbp就是主进程的子进程
3.创建子进程时使用Process, 只需要传入子进程要执行的函数和函数的参数即可, 利用进程对象的start()方法来启动进程
4.在调用Process时, 需要使用关键字的方式来指定参数

# 注意:
在Windows操作系统中由于没有fork(linux操作系统中创建进程的机制),在创建子进程的时候会自动 import 启动它的这个文件,而在 import 的时候又执行了整个文件。因此如果将process()直接写在文件中就会无限递归创建子进程报错。所以必须把创建子进程的部分使用if __name__ ==‘__main__’ 判断保护起来,import 的时候  ,就不会递归运行了。
# 导包
1.导包是用import关键字, 在导包的过程中, 会默认执行导入的模块
2.在创建子进程时会自动导入当前文件, 如果不进行处理在创建子进程的过程中会发送递归现象, 导致递归错误.

6.深入了解Process
# Process([group [, target [, name [, args [, kwargs]]]]])
# 1.参数说明:
- target:如果传递了函数的引用,可以认为这个子进程就执行这里的代码
- args:给target指定的函数传递的参数,以元组的方式传递, 如果元组只有一个元素, 元素后需要给个逗号
- kwargs:给target指定的函数传递命名参数
- name:给进程设定一个名字,可以不设定
- group:指定进程组,大多数情况下用不到

# 2.Process创建的实例对象的常用方法:
- start():启动子进程实例(创建子进程)
- is_alive():判断进程子进程是否还在活着
- join([timeout]):是否等待子进程执行结束,或等待多少秒
- terminate():不管任务是否完成,立即终止子进程

# 3.Process创建的实例对象的常用属性:
- name:当前进程的别名,默认为`Process-N`,N为从1开始递增的整数
- pid:当前进程的`pid`(进程号)
# -*- coding:utf-8 -*-
# 复杂一点的进程: 创建进程, 传递参数, 查看进程的进程id, 结束子线程, join等待
from multiprocessing import Process
import os
from time import sleep


def run_proc(name, age, **kwargs):
    for i in range(10):
        print('子进程运行中,name= %s,age=%d ,pid=%d...' % (name, age, os.getpid()))
        print(kwargs)
        sleep(0.2)

if __name__=='__main__':
    p = Process(target=run_proc, args=('test',18), kwargs={"m":20})
    p.start()
    sleep(1)  # 1秒中之后,立即结束子进程
    print('我是主线程!!!')
    p.terminate()
    p.join()

7.进程通信
# 需求: 有一个全局变量lst为一个列表, 一个子进程负责向lst中添加一条数据, 主进程也不断的输出lst
from multiprocessing import Process


def out_data(lst):
    lst.append('666')
    while 1:
        print("子线程中输出列表", lst)


if __name__ == '__main__':
    lst = ['a']
    oadd = Process(target=out_data, args=(lst,))
    oadd.start()
    while 1:
        print('主线程中输出: ', lst)
        
# 结论:
主进程与子进程之间无法共享公共变量数据
# 进程间通信
在进程之间进行通信, 可以使用`multiprocessing`模块的`Queue`实现多进程之间的数据传递,`Queue`本身是一个消息列队程序

# Queue体验:
from multiprocessing import Queue


q=Queue(3)  # 初始化一个Queue对象,最多可接收三条put消息
q.put("消息1") 
q.put("消息2")
print(q.full())  # False
q.put("消息3")
print(q.full())  # True

# 因为消息列队已满下面的try都会抛出异常,第一个try会等待2秒后再抛出异常,第二个Try会立刻抛出异常
try:
    q.put("消息4",True,2)
except:
    print("消息列队已满,现有消息数量:%s"%q.qsize())

try:
    q.put_nowait("消息4")
except:
    print("消息列队已满,现有消息数量:%s"%q.qsize())

# 推荐的方式,先判断消息列队是否已满,再写入
if not q.full():
    q.put_nowait("消息4")

# 读取消息时,先判断消息列队是否为空,再读取
if not q.empty():
    for i in range(q.qsize()):
        print(q.get_nowait())
        
        
# 队列
1.创建:
    q = Queue(3)  # 3是队列的大小
2.添加: q.put(obj, block, timeout)
    obj: 添加的对象
    block: 阻塞状态, 默认为True阻塞状态,False为非阻塞状态
        注意: 如果为非阻塞状态, 如果队列已经满了, 则put会报错, 如果队列为空, get会报错; 如果为阻塞时, 队列已满, 则put会产生阻塞, 直到队列中个元素被取出后, 有空余位置, 在put进去, 如果队列为空, 则get会产生阻塞, 直到有元素被添加进队列在get出来.
    timeout: 阻塞时间
3.取出: q.get()
4.查看队列大小: q.qsize()
5.判断空满: q.empty()  q.full()
# 进程间通信
from multiprocessing import Process, Queue
import os, time, random


# 写数据进程执行的代码:
def write(q):
    for value in ['A', 'B', 'C']:
        print('Put %s to queue...' % value)
        q.put(value)
        time.sleep(random.random())

# 读数据进程执行的代码:
def read(q):
    while True:
        if not q.empty():
            value = q.get(True)
            print('Get %s from queue.' % value)
            time.sleep(random.random())
        else:
            break

if __name__=='__main__':
    # 父进程创建Queue,并传给各个子进程:
    q = Queue()
    pw = Process(target=write, args=(q,))
    pr = Process(target=read, args=(q,))
    # 启动子进程pw,写入:
    pw.start()    
    # 等待pw结束:
    pw.join()
    # 启动子进程pr,读取:
    pr.start()
    pr.join()
    # pr进程里是死循环,无法等待其结束,只能强行终止:
    print('')
    print('所有数据都写入并且读完')

8.进程池
# 进程处理任务的问题
在程序实际处理问题过程中,忙时会有成千上万的任务需要被执行,闲时可能只有零星任务。那么在成千上万个任务需要被执行的时候,我们就需要去创建成千上万个进程么?首先,创建进程需要消耗时间,销毁进程也需要消耗时间。第二即便开启了成千上万的进程,操作系统也不能让他们同时执行,这样反而会影响程序的效率。因此我们不能无限制的根据任务开启或者结束进程。

# 进程池
定义一个池子,在里面放上固定数量的进程,有需求来了,就拿一个池中的进程来处理任务,等到处理完毕,进程并不关闭,而是将进程再放回进程池中继续等待任务。如果有很多任务需要执行,池中的进程数量不够,任务就要等待之前的进程执行任务完毕归来,拿到空闲进程才能继续执行。也就是说,池中进程的数量是固定的,那么同一时间最多有固定数量的进程在运行。这样不会增加操作系统的调度难度,还节省了开闭进程的时间,也一定程度上能够实现并发效果。
# 同步与异步: 同步与异步指的是提交任务的两种方式
同步调用:提交完任务后,就在原地等待,直到任务运行完毕后,拿到任务的返回值,才继续执行下一行代码。
异步调用:提交完任务后,不在原地等待,直接执行下一行代码。

# 阻塞与非阻塞:
阻塞:遇到 `I/O` 就发生阻塞,程序一旦遇到阻塞操作就会停在原地,并且立刻释放`CPU`资源
非阻塞(就绪态或运行态):没有遇到 `I/O` 操作,或者通过某种手段让程序即便是遇到 `I/O` 操作也不会停在原地,执行其他操作,力求尽可能多的占有`CPU`
# 进程池模块: multiprocessing.Pool
1.使用Pool创建进程池
p = Pool([numprocess  [,initializer [, initargs]]])
	(1).numprocess:要创建的进程数,如果省略,将默认使用cpu_count()的值
	(2).initializer:是每个工作进程启动时要执行的可调用对象,默认为None
	(3).initargs:是要传给initializer的参数组
2.进程池对象的主要方法:
	(1).p.apply(func [, args [, kwargs]]):在一个池工作进程中执行func(*args,**kwargs),然后返回结果。
	(2).p.apply_async(func [, args [, kwargs]]):在一个池工作进程中执行func(*args,**kwargs),然后返回结果。  
	(3).p.close():关闭进程池,防止进一步操作。
	(4).P.jion():等待所有工作进程退出。此方法只能在close()之后调用
import time
import os
import random
from multiprocessing import Pool


def func(param, name=0):
    print('进程%s--打印传入的参数---%s' % (os.getpid(), param))
    time.sleep(random.random()*2)
    print(param, 'end')


if __name__ == '__main__':
    po = Pool(3)
    for i in range(1, 10):
        po.apply_async(func, args=(i,))
    print('我是主进程, 啦啦啦!')
    po.close()
    po.join()
# 感受一下apply添加任务
import time
import os
import random
from multiprocessing import Pool


def func(param):
    print('进程%s--打印传入的参数---%s' % (os.getpid(), param))
    time.sleep(random.random()*2)
    print(param, 'end')


if __name__ == '__main__':
    po = Pool(3)
    for i in range(1, 10):
        po.apply(func, args=(i,))
    print('我是主进程, 啦啦啦!')
    po.close()
    po.join()

9.进程池中进程的通信变化
使用`Pool`创建进程,需要使用`multiprocessing.Manager()`中的`Queue()`,而不是multiprocessing.Queue()`,否则会得到一条如下的错误信息:
`RuntimeError: Queue objects should only be shared between processes through inheritance`.
# -*- coding:utf-8 -*-

# 修改import中的Queue为Manager
from multiprocessing import Manager,Pool
import os,time,random

def reader(q):
    print("reader启动(%s),父进程为(%s)" % (os.getpid(), os.getppid()))
    for i in range(q.qsize()):
        print("reader从Queue获取到消息:%s" % q.get(True))

def writer(q):
    print("writer启动(%s),父进程为(%s)" % (os.getpid(), os.getppid()))
    for i in "itcast":
        q.put(i)

if __name__=="__main__":
    print("(%s) start" % os.getpid())
    q = Manager().Queue()  # 使用Manager中的Queue
    po = Pool()
    po.apply_async(writer, (q,))

    time.sleep(1)  # 先让上面的任务向Queue存入数据,然后再让下面的任务开始从中取数据

    po.apply_async(reader, (q,))
    po.close()
    po.join()
    print("(%s) End" % os.getpid())
(11095) start
writer启动(11097),父进程为(11095)
reader启动(11098),父进程为(11095)
reader从Queue获取到消息:i
reader从Queue获取到消息:t
reader从Queue获取到消息:c
reader从Queue获取到消息:a
reader从Queue获取到消息:s
reader从Queue获取到消息:t
(11095) End
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值