并发编程之进程

并发编程

并发指多个任务同时被执行,在之前的TCP通信中,服务器在建立连接后需要一个循环来与客户端循环的收发数据,但服务器并不知道客户端什么时候会发来数据,导致没有数时服务器进入了一个等待状态,此时其他客户端也无法链接服务器,很明显这是不合理的,学习并发编程就是要找到一种方案,让一个程序中的的多个任务可以同时被处理

进程:正在运行的程序

进程是正在执行的一个过程,是对正在运行程序的抽象,也是操作系统在调度和进行资源分配的基本单位。

进程是实现并发的一种方式进程概念起源于操作系统,so,了解进程之前要了解操作系统,前人有云,不再赘述

一 操作系统的作用:
    1:隐藏丑陋复杂的硬件接口,提供良好的抽象接口
    2:管理、调度进程,并且将多个进程对硬件的竞争变得有序

二 多道技术:
    1.产生背景:针对单核,实现并发
    现在的主机一般是多核,那么每个核都会利用多道技术
    有4个cpu,运行于cpu1的某个程序遇到io阻塞,会等到io结束再重新调度,会被调度到4个cpu中的任意一个,具体由操作系统调度算法决定。
    
    2.空间上的复用:如内存中同时有多道程序
    3.时间上的复用:复用一个cpu的时间片
    强调:遇到io切,占用cpu时间过长也切,核心在于切之前将进程的状态保存下来,这样才能保证下次切换回来时,能基于上次切走的位置继续运行
理论基础

进程与程序

进程是正在运行的程序,程序是程序员编写的一堆代码,也就是一堆字符,当这堆代码被系统加载到内存中并执行时,就有了进程。一个程序是可以产生多个进程的,多次运行文件就会产生多个进程。

from multiprocessing import  Process
def task():
    print("task run")

# windows创建子进程时,子进程会将父进程的代码加载一遍,导致重新加载所有的代码造成递归
# 所以将创建子进程的代码放到main下
if __name__=="__main__":
    p=Process(target=task,name="子进程")  # 创建一个表示进程的对象
# print(p)  # <Process(子进程, initial)>,python解释器无法创建进程,只有操作系统可以
    p.start()  # task run,给操作系统发送通知,要求其开启进程

系统会给每个进程分配一个进程编号 PID,当一个进程a开启了另一个进程b时,a称为b的父进程,python中可通过os模块获取父进程的PID(用PPID)。

python中开启子进程的两种方式(此处属于进程中实现并发)

import os
from multiprocessing import Process

def task():
    print("task run")
    print("self",os.getpid())
    print("parent",os.getppid())

# windows创建子进程时,子进程会将父进程的代码加载一遍,导致重复创建子进程
# 所以将创建子进程的代码放到main下
if __name__ == "__main__":
    p = Process(target=task, name="子进程")
    print("self",os.getpid())
    print("parent",os.getppid())
    p.start()  # task run,给操作系统发送通知,要求其开启进程
# self 17088
# parent 9324
# task run
# self 13368
# parent 17088
实例化Process类
# 创建进程的第二种方式,继承Process,覆盖run方法,在子进程启动之后自动执行run方法
from multiprocessing import Process
class MyProcesses(Process):
    def run(self):
        print("run")

if __name__=="__main__":
    p=MyProcesses()
    p.start()  # run
继承Process,覆盖run方法,在子进程启动之后自动执行run方法
# 可以自定义进程的属性和行为,来完成一些额外任务
from multiprocessing import Process
class MyProcesses(Process):
    def __init__(self,url):
        self.url=url
        super().__init__()

# 子类中的方法只有run会自动执行
    def run(self):
        print("下载文件",self.url)

    def task(self):
        pass

if __name__ == "__main__":
    p = MyProcesses("www.baidu.com/xx.mp4")
    p.start()
# 下载文件 www.baidu.com/xx.mp4

并发与并行

并发:是伪并行,即看起来是同时运行。单个cpu+多道技术就可以实现并发,(并行也属于并发)

并行:同时运行,只有具备多个cpu才能实现并行

同步\异步and阻塞\非阻塞(重点)

在发出一个功能调用时,在没有得到结果之前,该调用就不会返回。按照这个定义,其实绝大多数函数都是同步调用。但是一般而言,我们在说同步、异步的时候,特指那些需要其他部件协作或者需要一定时间完成的任务。
举例:
1. multiprocessing.Pool下的apply #发起同步调用后,就在原地等着任务结束,根本不考虑任务是在计算还是在io阻塞,总之就是一股脑地等任务结束
2. concurrent.futures.ProcessPoolExecutor().submit(func,).result()
3. concurrent.futures.ThreadPoolExecutor().submit(func,).result()
同步
异步的概念和同步相对。当一个异步功能调用发出后,调用者不能立刻得到结果。当该异步功能完成后,通过状态、通知或回调来通知调用者。如果异步功能用状态来通知,那么调用者就需要每隔一定时间检查一次,效率就很低(有些初学多线程编程的人,总喜欢用一个循环去检查某个变量的值,这其实是一 种很严重的错误)。如果是使用通知的方式,效率则很高,因为异步功能几乎不需要做额外的操作。至于回调函数,其实和通知没太多区别。
举例:
1. multiprocessing.Pool().apply_async() #发起异步调用后,并不会等待任务结束才返回,相反,会立即获取一个临时结果(并不是最终的结果,可能是封装好的一个对象)。
2. concurrent.futures.ProcessPoolExecutor(3).submit(func,)
3. concurrent.futures.ThreadPoolExecutor(3).submit(func,)
异步
阻塞调用是指调用结果返回之前,当前线程会被挂起(如遇到io操作)。函数只有在得到结果之后才会将阻塞的线程激活。有人也许会把阻塞调用和同步调用等同起来,实际上他是不同的。对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回而已。
举例:
1. 同步调用:apply一个累计1亿次的任务,该调用会一直等待,直到任务返回结果为止,但并未阻塞住(即便是被抢走cpu的执行权限,那也是处于就绪态);
2. 阻塞调用:当socket工作在阻塞模式的时候,如果没有数据的情况下调用recv函数,则当前线程就会被挂起,直到有数据为止。
阻塞
非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前也会立刻返回,同时该函数不会阻塞当前线程。
非阻塞
1. 同步与异步针对的是函数/任务的调用方式:同步就是当一个进程发起一个函数(任务)调用的时候,一直等到函数(任务)完成,而进程继续处于激活状态。而异步情况下是当一个进程发起一个函数(任务)调用的时候,不会等函数返回,而是继续往下执行当,函数返回的时候通过状态、通知、事件等方式通知进程任务完成。

2. 阻塞与非阻塞针对的是进程或线程:阻塞是当请求不能满足的时候就将进程挂起,而非阻塞则不会阻塞当前进程

进程的创建

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

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

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

    扫描二维码关注公众号,回复: 4685340 查看本文章
  2. 一个进程在运行过程中开启了子进程(如nginx开启多进程,os.fork,subprocess.Popen等)

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

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

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

  1. 在UNIX中该系统调用是:fork,fork会创建一个与父进程一模一样的副本,二者有相同的存储映像、同样的环境字符串和同样的打开文件(在shell解释器进程中,执行一个命令就会创建一个子进程)

  2. 在windows中该系统调用是:CreateProcess,CreateProcess既处理进程的创建,也负责把正确的程序装入新进程。

关于创建的子进程,UNIX和windows

  1.相同的是:进程创建后,父进程和子进程有各自不同的地址空间(多道技术要求物理层面实现进程之间内存的隔离),任何一个进程的在其地址空间中的修改都不会影响到另外一个进程。

  2.不同的是:在UNIX中,子进程的初始地址空间是父进程的一个副本,提示:子进程和父进程是可以有只读的共享内存区的。但是对于windows系统来说,从一开始父进程与子进程的地址空间就是不同的。

进程的终止/销毁

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

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

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

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

进程的层次结构

 无论UNIX还是windows,进程只有一个父进程,不同的是:

  1. 在UNIX中所有的进程,都是以init进程为根,组成树形结构。父子进程共同组成一个进程组,这样,当从键盘发出一个信号时,该信号被送给当前与键盘相关的进程组中的所有成员。

  2. 在windows中,没有进程层次的概念,所有的进程都是地位相同的,唯一类似于进程层次的暗示,是在创建进程时,父进程得到一个特别的令牌(称为句柄),该句柄可以用来控制子进程,但是父进程有权把该句柄传给其他子进程,这样就没有层次了。

进程的状态

进程并发的实现

两种方式

进程间内存相互隔离

from multiprocessing import Process

a=100
def task():
    global a
    a=0
    print("子进程的",a)

if __name__=="__main__":
    p=Process(target=task)
    p.start()

    print("自己的",a)
# 自己的 100
# 子进程的 0
# 子进程里改变的数据不会影响父进程,先打印自己的是因为子进程创建需要时间
子进程里改变的数据不会影响父进程

join函数

from multiprocessing import Process
import time

def task():
    print("上传文件")
    time.sleep(2)
    print("上传结束")

if __name__=="__main__":
    p=Process(target=task)
    p.start()
    p.join()  # 提高子进程优先级,使其比父进程高,当CPU切换时,优先切子进程。故不用 p.wait()。
    print("上传成功")
提高子进程优先级
from multiprocessing import Process
import time

def task(num):
    print("现在是%s号"%num)
    time.sleep(2)
    # print("上传结束")

if __name__ == "__main__":
    start_time=time.time()
    for i in range(5):
        p = Process(target=task,args=(i,))
        p.start()
        # p.join()  # 并不会并发,逐个执行
    p.join()  # 并不会并发,只有最后一个才会等,统计的是最后一个的时间

    print(time.time()-start_time)
    print("over")
并不会并发
from multiprocessing import Process
import time


def task(num):
    print("现在是%s号"%num)
    time.sleep(2)
    # print("上传结束")


if __name__ == "__main__":
    start_time=time.time()
    ps=[]
    for i in range(5):
        p = Process(target=task,args=(i,))
        p.start()
        ps.append(p)
    for p in ps:
        p.join()
    print(time.time()-start_time)
    print("over")

 

Process对象常用属性

from multiprocessing import Process
def task(n):
    print('%s is runing' %n)
    time.sleep(n)

if __name__ == '__main__':
    start_time=time.time()

    p1=Process(target=task,args=(1,),name='任务1')
    p1.start()

    print(p1.pid)
    print(p1.name)
    p1.terminate()
    p1.join()
    print(p1.is_alive())

    print('')

猜你喜欢

转载自www.cnblogs.com/dongzhihaoya/p/10191150.html