0%

Python进程

#8 Python进程

相信大家都有过这样的经历:上网搜索一个东西,点了第一个搜索结果后,需要加载一定的时间的才会显示在屏幕上,在这段加载网页的时间你不会傻等,而是会点击第二个搜索结果到一个新的页面,这时候第一个网页可能还没有加载出来,你会惦记第三个搜索结果,这时候第一个网页加载出来了,你切换到第一个网页查看,在查看的时候,第二个网页和第三个网页也逐渐加载出来了,这种操作有效的节约了时间,换种说法,就是你的效率极大的提高了,因为在加载网页的这段时间你没有空闲,依旧在工作状态。你就是CPU,每一个搜索结果就是一个进程,加载网页就是IO操作,每当进程遇到IO操作时,就切换到另一个需要CPU的进程,这样CPU就一直处于工作状态,效率贼高。换种思路,如果你要爬取上千个网页,一个爬虫进程可能太慢了,想象一下你有10个爬虫进程同时爬取,那速度是不是就飞一般的快了呢,掌握Python进程线程将大大提高你的程序效率,话不多说,GO!!!

进程提出

如何高效利用计算机资源尤其是CPU资源一直是操作系统不断优化的方向,多道程序系统的出现使得CPU利用率极大的提高,标志着操作系统逐渐趋于成熟。此时引入进程的概念,以便更好地描述和控制程序的并发性。一张创造性的图片即可说明一切:

进程概念

进程是一个执行中的程序的实例。对这个定义进行拆分解释: - 一个:通过前面所学的类的定义可知,实例化对象是从一个类实例化出来的,不可能从两个类实例华出同一个对象,要想让实例化对象拥有多个类的属性,只能通过类的继承,然后让子类去实例化。 - 执行中的:一个程序要想执行,必须分配给其所需要的资源,可能是CPU资源,也可能是IO资源等等,只有资源满足了,程序才能执行。 - 程序:进程不是凭空捏造的,最基础的是要有程序,这就好比想要实例化一个对象的前提是必须要有类的存在,类都没有,怎么可能去实例化对象呢。 - 实例:也就是说进程可以随时随需要删除,并不会影响程序,程序还是在的。

举个栗子🌰来说明: 你下载了一个QQ,相当于有了一份程序,如果你不去运行这个程序,那就和没下载一样,你永远不可能通过QQ和其他人聊天,如果你运行了,系统为其分配合理的资源,这时候,一个进程就诞生了,如果你再运行QQ,又会产生一个新的进程,就相当于对QQ实例化两次产生两个进程嘛,所以你才能登陆多个账号。你把两个QQ都关掉,就相当于结束两个进程,并不会删除QQ这个软件,你还可以产生新的进程,但如果你删除了QQ这个软件,那就不可能在登陆QQ了,连程序都没有了,还谈什么进程不进程的!

Python管理进程

多进程模块:multiprocessing

1.未使用多进程模块的运行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/usr/bin/env python
#-*- coding:UTF-8 -*-


import time

def test():
print('Start!')
time.sleep(3)
print('End!')

if __name__ == '__main__':
p1 = test()
p2 = test()

2.使用多进程模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/env python
#-*- coding:UTF-8 -*-


import time
from multiprocessing import Process # 从多进程模块导入Process类


def test():
print('Start!')
time.sleep(3)
print('End!')

if __name__ == '__main__':
p1 = Process(target=test) # 创建一个子进程,目标函数为test
p2 = Process(target=test) # 创建一个子进程,目标函数为test

p1.start() # 开始执行进程p1
p2.start() # 开始执行进程p2

可以很明显的看到进程p2没有等待进程p1结束后才执行,说明两个进程是同时进行的

multiprocessing模块详细解读

1.进程间的关系

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
#!/usr/bin/env python
#-*- coding:UTF-8 -*-


import time
from multiprocessing import Process


def test():
print('Start!')
time.sleep(3)
print('End!')

if __name__ == '__main__':
p1 = Process(target=test) # 创建一个子进程,目标函数为test
p2 = Process(target=test) # 创建一个子进程,目标函数为test

p1.start()
p2.start()

print('执行完毕')



预期中的运行结果:两个进程运行完毕后,最后打印执行完毕,但实际上并不是这样:

我们现在通过进程id来确认一下每个进程的身份

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
#!/usr/bin/env python
#-*- coding:UTF-8 -*-


import os
import time
from multiprocessing import Process


def test():
print('Start!')
print('test 进程ID', os.getpid())
print('test 父进程ID', os.getppid())
time.sleep(3)
print('End!')

if __name__ == '__main__':
p1 = Process(target=test) # 创建一个子进程,目标函数为test
p2 = Process(target=test) # 创建一个子进程,目标函数为test

p1.start()
p2.start()

print('执行完毕')
print('main 进程ID', os.getpid())
print('main 父进程ID', os.getppid())

从运行结果可以清晰的看出,程序中产生的两个进程p1,p2是mian进程的两个子进程,main进程的父进程是谁呢?

1
2
3
minutesheep @ MS-MacBook-Pro $ ps aux | grep 84853
minutesheep 84853 2.1 0.1 4298860 5416 s005 Ss 3:07PM 0:07.66 -zsh
minutesheep 91186 0.0 0.0 4284664 664 s005 S+ 8:51PM 0:00.01 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn 84853

原来main进程的父进程是shell,这个很容易理解,应为python是从这个shell中运行的,自然就是这个shell的子进程。

2.控制进程执行顺序 有时候需要进程的执行有一定的顺序,尤其是在通信类进程间,通过join()方法就可以轻易实现,join()的作用是只有这个进程执行完毕才会继续执行后面的代码,看例:

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
#!/usr/bin/env python
#-*- coding:UTF-8 -*-


import os
import time
from multiprocessing import Process


def test():
print('Start!')
print('test 进程ID', os.getpid())
print('test 父进程ID', os.getppid())
time.sleep(3)
print('End!')

if __name__ == '__main__':
p1 = Process(target=test) # 创建一个子进程,目标函数为test
p2 = Process(target=test) # 创建一个子进程,目标函数为test

p1.start()
p1.join() # 只有p1执行完毕后才会执行后面的代码
p2.start()

print('执行完毕')
print('main 进程ID', os.getpid())
print('main 父进程ID', os.getppid())

效果已经很明显了,一眼就明白了哇

3.调用含参数的函数 很简单,Process(target, args)即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/env python
#-*- coding:UTF-8 -*-


import os
import time
from multiprocessing import Process


def test(name):
print(name)

if __name__ == '__main__':
p1 = Process(target=test, args=('Minute',)) # 参数是元组类型,需要加括号
p2 = Process(target=test, args=('Sheep',)) # 一个参数必须加逗号,多个则不用

p1.start()
p2.start()

p2.join()
print('执行完毕')

4.进程池的使用 如果你想开启多个进程,可以使用for循环,然而multiprocessing已经为你提供了很好的多进程方法,那就是Pool——进程池

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
#!/usr/bin/env python
#-*- coding:UTF-8 -*-


from multiprocessing import Pool

def test(msg):
print(msg)

if __name__ == '__main__':
# Pool([processes[, initializer[, initargs[, maxtasksperchild[, context]]]]])
# processes: 指定进程数目,默认为计算机核数os.cpu_count()
# 如果 initializer 不为 None,则每个工作进程将会在启动时调用 initializer(*initargs)。
# maxtasksperchild 是一个工作进程在它退出或被一个新的工作进程代替之前能完成的任务数量,为了释放未使用的资源。默认的 maxtasksperchild 是 None,意味着工作进程寿与池齐。
# context 可被用于指定启动的工作进程的上下文。
pool1 = Pool(4) # 指定4个进程数
for i in range(5):
msg = 'hi ' + str(i)
pool1.apply(test, (msg,)) # 返回结果前会阻塞,所以结果是顺序的
pool1.close() # close()的作用是关闭进程池,不能向进程池里添加进程了,必须在join()之前
pool1.join()

print('分割线'.center(20, '-'))

pool2 = Pool() # 指定进程数,默认为计算机核数,我的是4
for i in range(5):
msg = 'hello ' + str(i)
pool2.apply_async(test, (msg,)) # 返回结果前不会阻塞,其结果是无序的,更适合并行工作,返回结果对象,又返回值需要用get()方法取出
pool2.close()
pool2.join()

print('分割线'.center(20, '-'))

pool3 = Pool()
pool3.map(test, ['cool ' + str(i) for i in range(5)]) # 结果无序,对于长的迭代对象很消耗内存,对于长的迭代对象推荐使用imap()
pool3.close()
pool3.join()

print('分割线'.center(20, '-'))

pool4 = Pool()
pool4.map_async(test, ['hot ' + str(i) for i in range(5)]) # 与map类似,但返回结果对象,需要用get()方法取出
pool4.close()
pool4.join()

可以看到后三种方法结果是无序的,都比较适合并发执行工作(ps:因为只有5组数据,很难同时出现不一样顺序的情况,为了截出不一样结果的图像,录了大概20多次屏才达到这个效果)

小结

之后将记录进程间的通信问题,还有Python中线程的知识,下次见~