1. 进程定义

  1. 进程是资源分配最小单位
  2. 当一个可执行程序被系统执行(分配内存等资源)就变成了一个进程
    进程定义拓展
  3. 程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,这种执行的程序就称之为进程
  4. 程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念
  5. 在多道编程中,我们允许多个程序同时加载到内存中,在操作系统的调度下,可以实现并发地执行。
  6. 进程的出现让每个用户感觉到自己独享CPU,因此,进程就是为了在CPU上实现多道编程而提出的。
  7. 进程之间有自己独立的内存,各进程之间不能相互访问
  8. 创建一个新线程很简单,创建新进程需要对父进程进行复制
    多道编程概念
    多道编程: 在计算机内存中同时存放几道相互独立的程序,他们共享系统资源,相互穿插运行
    单道编程: 计算机内存中只允许一个的程序运行

进程,是执行中的计算机程序。也就是说,每个代码在执行的时候,首先本身即是一个进程。
一个进程具有:就绪,运行,中断,僵死,结束等状态(不同操作系统不一样)。

生命周期:

用户编写代码(代码本身是以进程运行的)

启动程序,进入进程“就绪”状态

操作系统调度资源,做“程序切换”,使得进程进入“运行”状态

结束/中断

特性

每个程序,本身首先是一个进程

运行中每个进程都拥有自己的地址空间、内存、数据栈及其它资源。

操作系统本身自动管理着所有的进程(不需要用户代码干涉),并为这些进程合理分配可以执行时间。

进程可以通过派生新的进程来执行其它任务,不过每个进程还是都拥有自己的内存和数据栈等。

进程间可以通讯(发消息和数据),采用 进程间通信(IPC) 方式。

说明

多个进程可以在不同的 CPU 上运行,互不干扰

同一个CPU上,可以运行多个进程,由操作系统来自动分配时间片

由于进程间资源不能共享,需要进程间通信,来发送数据,接受消息等

多进程,也称为“并行”。

2.进程间通信

进程彼此之间互相隔离,要实现进程间通信(IPC),multiprocessing模块支持两种形式:队列和管道,这两种方式都是使用消息传递的。

1、进程队列queue

不同于线程queue,进程queue的生成是用multiprocessing模块生成的。

在生成子进程的时候,会将代码拷贝到子进程中执行一遍,及子进程拥有和主进程内容一样的不同的名称空间。

multiprocess.Queue 是跨进程通信队列

常用方法

q.put方法用以插入数据到队列中,put方法还有两个可选参数:blocked和timeout。如果blocked为True(默认值),并且timeout为正值,该方法会阻塞timeout指定的时间,直到该队列有剩余的空间。如果超时,会抛出Queue.Full异常。如果blocked为False,但该Queue已满,会立即抛出Queue.Full异常。
q.get方法可以从队列读取并且删除一个元素。同样,get方法有两个可选参数:blocked和timeout。如果blocked为True(默认值),并且timeout为正值,那么在等待时间内没有取到任何元素,会抛出Queue.Empty异常。如果blocked为False,有两种情况存在,如果Queue有一个值可用,则立即返回该值,否则,如果队列为空,则立即抛出Queue.Empty异常.
q.get_nowait():同q.get(False)
q.put_nowait():同q.put(False)
q.empty():调用此方法时q为空则返回True,该结果不可靠,比如在返回True的过程中,如果队列中又加入了项目。
q.full():调用此方法时q已满则返回True,该结果不可靠,比如在返回True的过程中,如果队列中的项目被取走。
q.qsize():返回队列中目前项目的正确数量,结果也不可靠,理由同q.empty()和q.full()一样

2、管道pipe

管道就是管道,就像生活中的管道,两头都能进能出

默认管道是全双工的,如果创建管道的时候映射成False,左边只能用于接收,右边只能用于发送,类似于单行道

import multiprocessing

def foo(sk):
    sk.send('hello world')
    print(sk.recv())

if __name__ == '__main__':
    conn1,conn2=multiprocessing.Pipe()    #开辟两个口,都是能进能出,括号中如果False即单向通信
    p=multiprocessing.Process(target=foo,args=(conn1,))  #子进程使用sock口,调用foo函数
    p.start()
    print(conn2.recv())  #主进程使用conn口接收
    conn2.send('hi son') #主进程使用conn口发送

常用方法

conn1.recv():接收conn2.send(obj)发送的对象。如果没有消息可接收,recv方法会一直阻塞。如果连接的另外一端已经关闭,那么recv方法会抛出EOFError。
conn1.send(obj):通过连接发送对象。obj是与序列化兼容的任意对象
注意:send()和recv()方法使用pickle模块对对象进行序列化

Queue和pipe只是实现了数据交互,并没实现数据共享,即一个进程去更改另一个进程的数据。

注:进程间通信应该尽量避免使用共享数据的方式

3、共享数据manage

4、借助redis中间件进行数据共享

3.进程池

开多进程是为了并发,通常有几个cpu核心就开几个进程,但是进程开多了会影响效率,主要体现在切换的开销,所以引入进程池限制进程的数量。

进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进进程,那么程序就会等待,直到进程池中有可用进程为止。

from  multiprocessing import Process,Pool
import time,os
def foo(i):
    time.sleep(2)
    print("in the process",os.getpid()) #打印子进程的pid
return i+100

def call(arg):
print('-->exec done:',arg,os.getpid())

if __name__ == '__main__':
    pool = Pool(3)                      #进程池最多允许5个进程放入进程池
    print("主进程pid:",os.getpid())     #打印父进程的pid
    for i in range(10):
       #用法1 callback作用是指定只有当Foo运行结束后就执行callback调用的函数,父进程调用的callback函数
        pool.apply_async(func=foo, args=(i,),callback=call)
        #用法2 串行 启动进程不在用Process而是直接用pool.apply()
        # pool.apply(func=foo, args=(i,))
    print('end')
    pool.close()    #关闭pool
    pool.join()     #进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭。

4.有了进程为什么还要线程?

1. 进程优点:
提供了多道编程,让我们感觉我们每个人都拥有自己的CPU和其他资源,可以提高计算机的利用率
2. 进程的两个重要缺点
a. 第一点:进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。
b. 第二点:进程在执行的过程中如果阻塞,即使进程中有些工作不依赖于输入的数据,也将无法执行(例如等待输入,整个进程就会挂起)。
c. 例如,我们在使用qq聊天, qq做为一个独立进程如果同一时间只能干一件事,那他如何实现在同一时刻 即能监听键盘输入、又能监听其它人给你发的消息
d. 你会说,操作系统不是有分时么?分时是指在不同进程间的分时呀
e. 即操作系统处理一会你的qq任务,又切换到word文档任务上了,每个cpu时间片分给你的qq程序时,你的qq还是只能同时干一件事呀

5. 进程和程序的区别

  1. 程序只是一个普通文件,是一个机器代码指令和数据的集合,所以,程序是一个静态的实体
  2. 而进程是程序运行在数据集上的动态过程,进程是一个动态实体,它应创建而产生,应调度执行因等待资源或事件而被处于等待状态,因完成任务而被撤消
  3. 进程是系统进行资源分配和调度的一个独立单位
  4. 一个程序对应多个进程,一个进程为多个程序服务(两者之间是多对多的关系)
  5. 一个程序执行在不同的数据集上就成为不同的进程,可以用进程控制块来唯一地标识每个进程

进程和线程的区别

一个进程中的各个线程与主进程共享相同的资源,与进程间互相独立相比,线程之间信息共享和通信更加容易(都在进程中,并且共享内存等)。

线程一般以并发执行,正是由于这种并发和数据共享机制,使多任务间的协作成为可能。

进程一般以并行执行,这种并行能使得程序能同时在多个CPU上运行;

区别于多个线程只能在进程申请到的的“时间片”内运行(一个CPU内的进程,启动了多个线程,线程调度共享这个进程的可执行时间片),进程可以真正实现程序的“同时”运行(多个CPU同时运行)。
简单来说就是

  1. 进程包含线程
  2. 线程共享内存空间
  3. 进程内存是独立的(不可互相访问)
  4. 进程可以生成子进程,子进程之间互相不能互相访问(相当于在父级进程克隆两个子进程)
  5. 在一个进程里面线程之间可以交流。两个进程想通信,必须通过一个中间代理来实现
  6. 创建新线程很简单,创建新进程需要对其父进程进行克隆。
  7. 一个线程可以控制或操作同一个进程里面的其它线程。但进程只能操作子进程。
  8. 父进程可以修改不影响子进程,但不能修改。
  9. 线程可以帮助应用程序同时做几件事

进程和线程的常用应用场景

一般来说,在Python中编写并发程序的经验:

计算密集型任务使用多进程

IO密集型(如:网络通讯)任务使用多线程,较少使用多进程.

这是由于 IO操作需要独占资源,比如:

网络通讯(微观上每次只有一个人说话,宏观上看起来像同时聊天)每次只能有一个人说话

文件读写同时只能有一个程序操作(如果两个程序同时给同一个文件写入 ‘a’, ‘b’,那么到底写入文件的哪个呢?)

都需要控制资源每次只能有一个程序在使用,在多线程中,由主进程申请IO资源,多线程逐个执行,哪怕抢占了,也是逐个运行,感觉上“多线程”并发执行了。

如果多进程,除非一个进程结束,否则另外一个完全不能用,显然多进程就“浪费”资源了。

当然如上解释可能还不足够立即理解问题所在,让我们通过不断的实操来体验其中的“门道”。