进程

进程概念

  • 进程(Process)是资源分配的最小单位,是线程的容器。

  • 程序是固定不变的,而进程会根据运行需要,让操作系统动态分配各种资源的CPU的时间片轮转,在不同的时间段切换执行不同的进程,但是切换进程是比较耗时的;就引来了轻量级进程,也就是所谓的线程,一个进程中包括多个线程(代码流,其实也就是进程中同时跑的多个方法体)

  • 程序:例xxx.py这是程序,是一个静态的

  • 进程:一个程序运行起来后,代码+用到的资源称之为进程,它是操作系统分配资源的基本单元。

进程状态

  • 工作中,任务数往往大于cpu的核数,即一定有一些任务正在执行,而另外一些任务在等待cpu进行执行,因此导致了有了不同的状态

  • 进程状态

    • 就绪态:运行的条件都已经满足,正在等在cpu执行
    • 执行态:cpu正在执行其功能
    • 等待态:等待某些条件满足,例如一个程序sleep了,此时就处于等待态

进程的创建 - multiprocessing

  • multiprocessing模块就是跨平台版本的多进程模块,提供了一个Process类来代表一个进程对象,这个对象可以理解为是一个独立的进程,可以执行另外的事情。

  • ·创建子进程跟创建线程十分类似,只需要传入一个执行函数和函数的参数,创建一个Process实例,用start0方法后动

  • Process语法结构如下: Process([group [,target[,name,[args[,kwargs]]])

    • target:如果传递了函数的引用,这个子进程就执行这里(函数)的代码
    • args:给target指定的函数传递的参数,以元组的方式传递
    • kwargs:给target指定的函数传递命名参数
    • name:给进程设定一个名字,可以不设定
    • group:指定进程组,大多数情况下用不到
  • Process创建的实例对象的常用方法:

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

    • name:当前进程的别名,默认为Process-N,N为从1开始递增的整数
    • pid:当前进程的pid(进程号)
  • linux系统可使用kill命令结束进程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import multiprocessing
import os
import time


def work1():
    for i in range(10):
        # 获取进程的父id
        print("正在运行work1  第{}次..进程id{},父进程id{}".format(i, os.getpid(), os.getppid()))
        time.sleep(2)


if __name__ == '__main__':
    # 获取主进程名称
    print("主进程名称", multiprocessing.current_process())

    # 获取进程的编号
    # 1.获取进程id multiprocessing.current_process().pid
    print("主进程编号", multiprocessing.current_process().pid)

    # 2. 模块os
    # print(os.getpid())

    process_obj = multiprocessing.Process(target=work1, name="进程一")
    process_obj.start()

    print("XXXX")
    
out:
    主进程名称 <_MainProcess(MainProcess, started)>
    主进程编号 31776
    XXXX
    正在运行work1  第0次..进程id43088父进程id31776
    正在运行work1  第1次..进程id43088父进程id31776
    正在运行work1  第2次..进程id43088父进程id31776
    正在运行work1  第3次..进程id43088父进程id31776
    正在运行work1  第4次..进程id43088父进程id31776
    正在运行work1  第5次..进程id43088父进程id31776
    正在运行work1  第6次..进程id43088父进程id31776
    正在运行work1  第7次..进程id43088父进程id31776
    正在运行work1  第8次..进程id43088父进程id31776
    正在运行work1  第9次..进程id43088父进程id31776

进程参数,全局变量

  • 进程间不能共享全局变量
  • 子进程在运行时候,会将主进程的内容复制到自己进程中,修改只限自己进程有效。不影响其他进程和主进程
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import multiprocessing

# 定义全局变量
import time

g_num = 10

def work1():
    global g_num
    for i in range(10):
        g_num += 1
    print("work1---------", g_num)

def work2():
    print("work1---------", g_num)


if __name__ == '__main__':
    p1 = multiprocessing.Process(target=work1)
    p2 = multiprocessing.Process(target=work2)

    p1.start()
    p2.start()

    time.sleep(3)
    print("---------main-----------", g_num)

out:
    work1--------- 20
    work1--------- 10
    ---------main----------- 10

守护进程

  • p1.daemon=True设置子进程p1守护主进程,当主进程结束的时候,子进程也随之结束
  • p1.terminate()终止进程执行,并非是守护进程
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import multiprocessing
import time


def work1():
    for i in range(10):
        print("正在运行work1...{}".format(time.ctime()))
        time.sleep(0.5)


if __name__ == '__main__':
    process_obj = multiprocessing.Process(target=work1)
    # 开启守护
    # process_obj.daemon = True
    process_obj.start()

    print("我睡两秒就结束", time.ctime())
    time.sleep(2)
    # 终止进程
    process_obj.terminate()
    exit()
out:
    我睡两秒就结束   Thu Nov 14 16:04:29 2019
    正在运行work1...Thu Nov 14 16:04:29 2019
    正在运行work1...Thu Nov 14 16:04:30 2019
    正在运行work1...Thu Nov 14 16:04:30 2019
    正在运行work1...Thu Nov 14 16:04:31 2019

进程线程对比

  • 功能
    • 进程,能够完成多任务,比如在一台电脑上能够同时运行多个QQ
    • 线程,能够完成多任务,比如一个QQ中的多个聊天窗口
  • 使用区别
    • 进程是系统进行资源分配和调度的一个独立单位
    • 线程是进程的一介实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.
    • 一个程序至少有一个进程,一个进程至少有一个线程.
    • 线程的划分尺度小于进程(资源比进程少),使得多线程程序的并发性高。
    • 进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率
  • 线程不能独立运行,必须依赖于线程
对比维度多进程多线程总结
数据共享,同步数据共享复杂,需要用IPC;数据是分开因为共享进程数据,数据共享简单因为共享进程数据,数据共享简单,但也是因为这个原因导致同步复杂各有千秋
内存、CPU占用内存多,切换复杂,CPU利用率低占用内存少,切换简单,PU利用率高CPU线程占优
创建销毁、切换创建销毁、切换复杂,速度慢创建销毁、切换简单,速度很快线程占优
编程,调试编程简单,调试简单编程复杂,调试复杂进程占优
可靠性进程间不会互相影响一个线程挂掉将导致整个进程挂掉进程占优
分布式适应于多核、多机分布式;如果一台机器不够,扩展到多台机器比较简单应用于多核分布式进程占优

进程线程取舍

  • 需要频繁创建销毁的先使用线程;(如:Web服务器)
  • 线程的切换速度快,所以在需要大量计算,切换频繁时用线程(如图像处理、算法处理)
  • 因为对CPU系统的效率使用上线程更占优,所以可能要发展到多机分布的用进程,多核分布用线程;
  • 需要更稳定安全时,适合选择进程;需要速度时,选择线程更好。
  • 都满足需求的情况下,用你最熟悉、最拿手的方式 需要提醒的是:虽然有这么多的选择原则,但实际应用中基本上都是“进程+线程”的结合方式
  • 在Python的原始解释器CPython中存在着GlL(Global Interpreter Lock,全局解释器锁),因此在解释执行python代码时,会产生互斥锁来限制线程对共享资源的访问,直到解释器遇到I/O操作或者操作次数达到一定数目时才会释放GlL。造成了即使在多核CPU中,多线程也只是做着分时切换而已

消息队列

  • 可以使用multiprocessing模块的Queue实现多进程之间的数据传递
  • Queue本身是一个消息列队程序
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import multiprocessing


# 创建队列(指定长度)
queue = multiprocessing.Queue(5)

# 向队列中放值
queue.put("Luenci")
queue.put(1)
queue.put([5, 2])
queue.put((9, 8, 7))
queue.put({"a": 6, "b": 8})

# 判断队列是否全满
isfull = queue.full()
print("队列为全满:", isfull)


# 长度为5,放入第6个数据后,队列就进行入了阻塞状态,默认会等待以列先取出值再放入新的值
# queue.put(22)
# 不等待的放入
# queue.put_nowait(11)

# 向队列中取值
# 当队列为空的时候,再取值的时候,程序会进行阻塞状态,等待队列中有值再取

value = queue.get()
print(value)
# 当队列为空的时候,再取值的时候,程序会直接报错
# value = queue.get_nowait()

# 队列元素个数
num = queue.qsize()
print(num)

# 判断队列是否为空
empty = queue.empty()
print("队列为空?:", empty)

进程池

  • 进程池概述
  • 当需要创建的子进程数量不多时,可以直接利用multiprocessing中的Process动态成生多个进程,但如果是上百甚至上千个目标,手动的去创建进程的工作量巨大,此时就可以用到multiprocessing模块提供的Pool方法。

image-20191114203943978

  • 初始化Pool时,可以指定一个最大进程数,当有新的请求提交到Pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到指定的最大值,那么该请求就会等待,直到池中有进程结束,才会用之前的进程来执行新的任务。

进程池实现方式

  • 同步方式:pool.apply()
  • 异步方式pool.apply_async(copy_work)

核心方法

  • multiprocessing.Pool常用函数解析:
    • apply():–进程池中进程以同步方式执行任务

image-20191114204338304

  • apply_async(func[,args[,kwds]]):–使用非阻塞方式调用func(并行执行,堵塞方式必须等待上一个进程退出才能执行下一个进程),args为传递给func的参数列表,kwds为传递给func的关键字参数列表

image-20191114204556059

进程池中的进程通信

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import multiprocessing
import time


def write_queue(queue):
    for i in range(10):
        if queue.full():
            print("队列已满")
            break
        else:
            queue.put(i)
            print("成功添加:", i)
            time.sleep(0.5)


def read_queue(queue):
    while True:
        if queue.qsize() == 0:
            print("队列已空")
            break
        value = queue.get()
        print("已经读取", value)


if __name__ == '__main__':

    # 创建进程池
    pool = multiprocessing.Pool(3)

    # 创建进程池中的队列
    queue = multiprocessing.Manager().Queue(10)

    # 同步的方式执行
    # pool.apply(write_queue, args=(queue,))
    # pool.apply(read_queue, args=(queue,))

    # 异步的方式执行
    # apply_async()返回值ApplyResult对象,该对象有一个wait()的方法
    result = pool.apply_async(write_queue, args=(queue,))
    result.wait()
    pool.apply_async(read_queue, args=(queue,))

    # 不再接收新的任务
    pool.close()
    pool.join()
out:
    成功添加 0
    成功添加 1
    成功添加 2
    成功添加 3
    成功添加 4
    成功添加 5
    成功添加 6
    成功添加 7
    成功添加 8
    成功添加 9
    已经读取 0
    已经读取 1
    已经读取 2
    已经读取 3
    已经读取 4
    已经读取 5
    已经读取 6
    已经读取 7
    已经读取 8
    已经读取 9
    队列已空