python多进程与多线程

多进程和多线程在编程中是一个重要的特性,本文主要讲解在python中如何实现多线程和多进程。

多线程

python中多线程速度慢,不建议使用!

python中的多线程机制不够完善,用起来效果很鸡肋。以我的实现为例,在不使用多线程的情况下运行一次需要5s左右,使用多线程之后反而需要14s之久。这显然是不可接受的,所以不推荐在python中使用多线程,如果真的需要并发,建议使用多进程代替。

python中的多线程速度慢究其原因在于python中的Global Interpreter Lock(GIL),线程只有获得GIL才可运行,导致的结果是每次最多只有一个线程在运行。GIL导致运行慢的原因不在本文讨论范围,感兴趣可自行搜索。

不过python中的多进程基于多线程实现,两者的接口使用方式几乎是一样的,所以本文还是对多线程实现做出讲解。

实现方法

python中多线程通过threading库实现,创建线程有两种方法,不过根本上都是需要用到threading.Thread类。

方法1:自定义线程的运行内容

这种方法需要自己提前以函数的形式定义好要在线程中运行的程序。为直观起见,这里我只使用了两个线程,两者均运行同一个程序函数do_something。此外,我这里将输出用日志打印,这样可以显示运行时间,便于理解线程的执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import time
import threading
# 日志配置
import logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s %(message)s', datefmt='%H:%M:%S')

# 要在线程中运行的程序
def do_something(name):
logging.info(name + ' is playing toy.')
time.sleep(1)
logging.info(name + ' leaves.')

if __name__ == '__main__':
children = ['Tom', 'John']
# 创建线程
threads = [threading.Thread(target=do_something, args=(child,)) for child in children]

# 开启进程
for thread in threads:
thread.start()
# 等待进程结束
for thread in threads:
thread.join()

运行上述代码,我得到的输出是:

1
2
3
4
18:50:31 Tom have access to toy: beer
18:50:31 John have access to toy: beer
18:50:32 Tom leaves!
18:50:32 John leaves!

可见,两个线程同时开始运行,并在执行结束后分别退出。

方法2:自定义线程

相比于方法1以函数的形式定义线程要运行的程序,方法2直接定义一个要运行的线程,该线程需继承threading.Thread类,并重写其run方法。

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 time
import threading
# 日志配置
import logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s %(message)s', datefmt='%H:%M:%S')

# 线程类
class DoSomething(threading.Thread):
def __init__(self, name, *args, **kargs):
super().__init__(*args, **kargs)
self.name = name

def run(self):
'''运行程序写在这里'''
logging.info(self.name + ' is playing toy.')
time.sleep(1)
logging.info(self.name + ' leaves.')

if __name__ == '__main__':
children = ['Tom', 'John']
threads = [DoSomething(name=child) for child in children]

for thread in threads:
thread.start()

for thread in threads:
thread.join()

运行该程序输出和方法1类似。

共享资源

在运行多线程的时候经常需要多个线程共享资源,要实现这一点只需对上边的程序稍微改进即可,这里以方法1为例说明,我额外创建了一个Shared类用于多线程共享。

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
import time
import threading
import logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s %(message)s', datefmt='%H:%M:%S')

# 共享类
class Shared(object):
toy = 'beer'

def do_something(resource, name):
logging.info(name + ' have access to toy: ' + resource.toy)
time.sleep(1)
logging.info(name + ' leaves!')

if __name__ == '__main__':
# 共享资源
resource = Shared()

children = ['Tom', 'John']
threads = [threading.Thread(target=do_something, args=(resource, child)) for child in children]

for thread in threads:
thread.start()

for thread in threads:
thread.join()

使用方法2共享资源也是同理。

多进程

相对于python中多线程存在的硬伤,多进程则靠谱很多。python任何希望用多线程实现的程序都可以用多进程代替,而且多进程提供了和多线程非常相似的API接口,迁移起来非常方便。

python中多进程通过multiprocessing库实现,实现的类是multiprocessing.Process。由于和多线程接口一致,多进程也有两种实现方式,而且多进程也可以像多线程一样共享资源。

以方法1为例,要想将多线程改写为多进程,只需要导入multiprocessing库并将threading.Thread替换为multiprocessing.Process即可。

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 time
# 导入多进程库
import multiprocessing

import logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s %(message)s', datefmt='%H:%M:%S')

class Shared(object):
toy = 'beer'

def do_something(resource, name):
logging.info(name + ' have access to toy: ' + resource.toy)
time.sleep(1)
logging.info(name + ' leaves!')

if __name__ == '__main__':
resource = Shared()

children = ['Tom', 'John']
# 替换
threads = [multiprocessing.Process(target=do_something, args=(resource, child)) for child in children]

for thread in threads:
thread.start()

for thread in threads:
thread.join()