进程

进程

多任务原理

多任务的由来(进程 线程的发展):

1, 早期的计算机是没有操作系统的,只有输入,计算,输出。手工输入速度远低于计算机的计算速度。

2, 于是出现了批处理操作系统,通过纸带,磁带等工具预先写入指令,形成一个指令清单(即任务)交给计算机处理。但批处理系统的缺点是只能有一个任务,而且当计算机在进行I/O处理时,CPU是空闲的。

3, 世人发明了进程,一个进程就代表一个任务,多个进程通过分时操作能让用户认为并行操作多任务,进程间的资源是独立单元,但是可以通过介质进行通信。缺点:进程内只进行串行处理,无法很好地分工合作提高处理效率。

4, 于是就有了操作系统调度的最小单元-线程,线程能够使进程内的子任务能够共享进程内的资源,并并行工作,大大提高操作系统的性能。

区别:
线程是任务调度的最小单元,共用进程内的资源。
进程是资源分配的最小单元,与其他进程资源互相独立。

现代操作系统(Windows、Mac OS X、Linux、UNIX等)都支持“多任务”。

什么叫多任务???
操作系统同时可以运行多个任务。

思考:为什么会需要多任务?

单核CPU实现多任务原理:操作系统轮流让各个任务交替执行,QQ执行2us,切换到微信,在执行2us,再切换到陌陌,执行2us……。表面是看,每个任务反复执行下去,但是CPU调度执行速度太快了,导致我们感觉就行所有任务都在同时执行一样。

多核CPU实现多任务原理:真正的并行执行多任务只能在多核CPU上实现,但是由于任务数量远远多于CPU的核心数量,所以,操作系统也会自动把很多任务轮流调度到每个核心上执行。
并发:看上去一起执行,任务数多于CPU核心数。
并行:真正一起执行,任务数小于等于CPU核心数。

实现多任务的方式:
1、多进程模式
2、多线程模式
3、协程模式
4、多进程+多线程模式

进程

对于操作系统而言,一个任务就是一个进程。

进程是系统中程序执行和资源分配的基本单位。每个进程都有自己的数据段、代码段、和堆栈段。

先来体会一下单任务的场景:

from time import sleep

# 任务一
def run():
    while True:
       print('hot day')
       sleep(1.2)



if __name__ == '__main__':
    # 任务二
    while True:
        print('good day')
        sleep(1)

    # 只有上面的while循环结束才会执行到run
    run()

如何让上面代码中的run方法执行呢。使用多进程。

python中多进程的库为multiprocessing.这是一个垮平台版本的多进程模块,提供了一个Process类来代表一个进程对象。

使用最简单的多进程实现上面的多任务场景:

from multiprocessing import Process
from time import sleep


def run():
    while True:
       print('hot day')
       sleep(1.2)


if __name__ == '__main__':
    print('主(父)进程启动')
    # 创建子进程
    p = Process(target=run)
    # 启动进程

    p.start()

    while True:
        print('good day')
        sleep(1)

如果run方法需要接收参数,可以通过args来传递:

p = p = Process(target=run, args=('hot',))

注意args是位置参数,传递位置参数时,必须按照参数位置来传递。如果需要传递关键词参数使用 kwargs.

每个进程都有一个唯一的id号,我们一般称之为pid。

在python代码中获取进程id号使用:os.getpid()。

获取当前进程的父进程ip使用:os.getppid().多出来的p表示parent。

Process常见方法和属性

属性:

字段说明
daemon是否为守护进程, 父进程终止后自动终止,且自己不能产生新进程,必须在start()之前设置
name进程的名字,自定义
exitcode进程的退出状态码(无法自己设置)
pid每个进程有唯一的PID编号(无法自己设置)。

方法:

方法名说明
is_alive()返回进程是否在运行
join([timeout])阻塞(se)当前上下文环境,直到调用此方法的进程终止或者到达指定timeout
start()启动进程,等待CPU调度
terminate()不管任务是否完成,立即停止该进程
run()start()调用该方法,当实例进程没有传入target参数,start()将执行默认的run()方法

另一种创建进程的方式: 派生Process子类

使用Process子类的方式创建进程:

from multiprocessing import Process
from time import sleep


class MyProcess(Process):
    # 定义init方法方便传参数进来
    def __init__(self, id, *args, **kwargs):
        # 调用父类的init方法
        super().__init__(*args, **kwargs)
        # 赋值
        self.id = id

    # 重写run方法
    def run(self):
        print('子进程%d开始运行' % self.id)
        print(self)
        sleep(2)
        print('子进程%d结束运行' % self.id)


if __name__ == '__main__':
    print('父进程开始运行')

    # 创建子进程
    for i in range(4):
        p = MyProcess(name='子进程'+str(i),id=i)
        p.start()

    print('父进程结束')
父子进程的先后顺序

通过一个例子来体会父子进程的执行顺序:

from multiprocessing import Process
from time import sleep


def run():
    print('子进程启动')
    sleep(3)
    print('子进程结束')


if __name__ == '__main__':
    print('父进程启动')
    p = Process(target=run)

    p.start()

    print('父进程结束')

执行结果为:

父进程启动
父进程结束
子进程启动
子进程结束

可以发现父进程并不等待子进程的结束而结束,如果需要实现这一下效果,可以使用p.join()。

如果想给这个等待加一个期限,可以设置一个timeout时间,单位为秒。

全局变量在多个进程中不能共享

通过代码来说明这一现象:

from multiprocessing import Process
from time import sleep

# 定义全局变量
num = 100

# 子进程
def run():
    print('开始执行子进程')
    # 引入全局变量
    global  num
    # 修改全局变量
    num += 1
    print(num)
    print('子进程结束')


if __name__ == '__main__':
    print('父进程开始')

    p = Process(target=run)
    p.start()
    print('父进程结束--%d'%num)

执行结果:

父进程开始
父进程结束--100
开始执行子进程
101
子进程结束

发现子进程中修改的全局变量在父进程中依然还是100.

出现这种现象的原因是子进程不和父进程共享全局变量,而是单独拷贝了一份全局变量。

不光父子进程不共享全局变量,兄弟进程也不共享全局变量。请写程序验证这一点。

使用进程池创建大量进程

先来看一个例子:

# coding: UTF-8
from multiprocessing import Process, Pool
import os, time, random

def run(name):
    print('子进程%d启动--%s' % (name, os.getpid()))
    start = time.time()
    time.sleep(random.choice([1, 2, 3]))
    end = time.time()
    print('子进程%d结束--%s--耗时%.2f' % (name, os.getpid(), end-start))

	

if __name__ == '__main__':
    print('父进程启动')
    # 创建进程池
    # 4表示同时可以执行的进程数量,不写默认是电脑的cpu内核数
    pp = Pool(4)

    for i in range(5):
        pp.apply_async(run, args=(i,))

    # 关闭进程池,调用close之后不能再继续添加新的进程。
    pp.close()
    # 等进程池中的进程都完成再结束进程池, 注意必须在调用close之后调用join
    pp.join()

    print('父进程结束')

使用进程池,需要创建进程池实例对象,指明这个进程池中可以放多少个进程。使用apply_async()方法在进程池中创建子进程。添加完子进程后,需要使用close方法关闭进程,关闭之后无法往进程池中添加新的子进程。使用进程池的join方法必须在调用close之后。

练习:使用进程池,把大量文件从一个目录拷贝到另一个目录。首先实现单进程方式,再用进程池实现多进程方式。对比两者的效率。

进程间通信

进程间通信使用Queue对象。

先来看一个例子:

from multiprocessing import Process, Queue
import time, random, os

def write(q):
    print('开始子进程%s' % (os.getpid()))
    for value in 'abdcef':
        print(time.ctime(), 'put %s to queue' % value)
        q.put(value)
        time.sleep(random.random())

def read(q):
    while True:
        value = q.get()
        print(time.ctime(), 'get %s from queue' % value)



if __name__ == '__main__':
    # 主进程创建queue并传递给子进程
    q = Queue()
    pw = Process(target=write, args=(q,))
    pr = Process(target=read, args=(q,))
    pw.start()
    pr.start()
    pw.join()
    pr.terminate()

使用maxsize,可以为队列设置最大长度,当为maxsize<=0时,队列的最大长度会被设置为一个非常大的值 .

put(self, obj, block=True, timeout=None)
1、block为True,若队列已满,并且timeout为正值,该方法会阻塞timeout指定的时间,直到队列中有出现剩余空间,如果超时,会抛出Queue.Full异常 
2、block为False,若队列已满,立即抛出queue.Full异常
get(self, block=True, timeout=None)
block为True,若队列为空,并且timeout为正值,该方法会阻塞timeout指定的时间,直到队列中有出现新的数据,如果超时,会抛出Queue.Empty异常 
block为False,若队列为空,立即抛出queue.Empty异常
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值