Python 中的多进程和多线程
多进程和多线程是实现多任务及提升任务执行效率的两种常用方法。两者都遵循 Master-Worker 模式,Master 负责分配任务,Worker 负责执行任务。具体来说,多进程由一个主进程与多个子进程构成,而多线程由一个主线程与多个子线程构成。两者的一个主要区别在于:多进程的每个进程创建在不同的内存空间中,而多线程创建在一个进程中。
一、 多进程与多线程的优缺点对比:
- 多进程的优点:稳定性高,因为一个子进程崩溃了,不会影响主进程和其他子进程(当然主进程崩溃了所有进程就全崩溃了,但是主进程只负责分配任务,崩溃的概率很低)。多进程模式的另一个优点是可以将进程分布到多台机器上。
- 多进程的缺点:创建进程的代价大,一般情况下创建的进程数不超过CPU核数的2倍。
- 多线程的优点:系统开销较小,在Windows下,多线程的效率比多进程要高
- 多线程的缺点:稳定性差,由于多线程创建在一个进程中,这就意味着多个线程共用相同的内存空间,所以当一个线程崩溃,其它线程也会崩溃。此外,多线程不能分布在多台机器上。
- Python解释器由于设计时有GIL全局锁,导致无法利用多线程实现多核任务,但可以通过多进程实现多核任务。多个Python进程有各自独立的GIL锁,互不影响。
二、 多进程的实现
- 通过调用 multiprocessing 模块的 Process 可以创建一个进程对象,创建进程之后,操作系统会自动把当前进程(称为父进程)复制一份(称为子进程),然后,分别在父进程和子进程内返回,子进程永远返回0,而父进程返回子进程的ID。
from multiprocessing import Process
import os
def run_proc(name):
# getpid() 和 getppid() 分别用于获取当前进程及当前进程父进程的 ID
print(\'Run child process %s (%s)...\' % (name, os.getpid()))
print(\'Run parent process %s (%s)...\' % (name, os.getppid()))
if __name__ == \'__main__\':
print(\'Parent process %s.\' % os.getpid())
p = Process(target = run_proc, args = (\'test\',))
p.start() # 进程开始
p.join() # 等待结束
print(\'End\')
- 通过调用 Pool() 模块来创建多个进程
from multiprocessing import Pool
import os, time, random
def long_time_task(name):
print(\'Run task %s (%s)...\' % (name, os.getpid()))
start = time.time()
time.sleep(random.random() * 3)
end = time.time()
print(\'Task %s runs %0.2f seconds.\' % (name, (end - start)))
if __name__==\'__main__\':
print(\'Parent process %s.\' % os.getpid())
p = Pool(4) # 进程池的个数一般取CPU个数的1到2倍
for i in range(8):
p.apply_async(long_time_task, args=(i,))
print(\'Waiting for all subprocesses done...\')
p.close() # close 后不能再添加进程
p.join()
print(\'All subprocesses done.\')
- 进程间共享变量
from multiprocessing import Process, Value, Array #引入多进程版本的value和array
def f(n, a):
n.value = 3.1415927
for i in range(len(a)):
a[i] = -a[i]
if __name__ == \'__main__\':
num = Value(\'d\', 0.0)
arr = Array(\'i\', range(10))
p = Process(target=f, args=(num, arr))
p.start()
p.join()
print(num.value)
print(arr[:])
- 通过在进程间引入 Queue(队列)来实现进程间通信
from multiprocessing import Process, Queue
import os, time, random
def write(q):
print(\'Process to write: %s\' % os.getpid())
for value in [\'A\', \'B\', \'C\']:
print(\'Put %s to queue...\' % value)
q.put(value)
time.sleep(random.random())
def read(q):
print(\'Process to read: %s\' % os.getpid())
while True:
value = q.get(True)
print(\'Get %s from queue.\' % value)
if __name__==\'__main__\':
q = Queue()
pw = Process(target=write, args=(q,))
pr = Process(target=read, args=(q,))
pw.start()
pr.start()
pw.join()
pr.terminate() # pr 是死循环,需要强行停止
- 创建并控制子进程
import subprocess
# 下面的代码相当于直接执行命令`nslookup www.python.org`
r = subprocess.call([\'nslookup\', \'www.python.org\'])
print(\'Exit code:\', r)
# 如果想要输入,需要调用 communicate()
p = subprocess.Popen([\'nslookup\'],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
output, err = p.communicate(b\'set q=mx\\npython.org\\nexit\\n\')
print(output)
print(\'Exit code:\', p.returncode)
三、多线程的实现
- 每调用一次 threading.Thread() 就可以创建一个线程
def loop():
thread_name = threading.current_thread().name # 获取当前线程的名称
print(\'Thread %s is running...\' % thread_name)
n = 0
while n < 5:
n = n + 1
print(\'Thread %s >>> %d\' % (thread_name, n))
print(\'Thread %s ends.\' % thread_name)
thread_name = threading.current_thread().name
print(\'Thread %s is running...\' % thread_name)
t1 = threading.Thread(target = loop, name=\'LoopThread-1\') # 为线程命名,t1执行完之后才会执行t2
t2 = threading.Thread(target = loop, name=\'LoopThread-2\')
t1.start()
t2.start()
t1.join()
t2.join()
print(\'Thread %s ends.\' % thread_name)
- 在多线程中添加线程锁。多进程中,同一个变量各自有一份拷贝存在于每个进程中,互不影响,而多线程中,所有变量都由所有线程共享,所以,任何一个变量都可以被任何一个线程修改。如何各线程正常执行一般不会有什么问题,但是如果某个线程意外中断,就会导致变量被赋予意想不到的数值,为了防止这种情况发生就需要添加线程锁。线程锁的好处就是确保了某段关键代码只能由一个线程从头到尾完整地执行,坏处当然也很多,比如它阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了。
import time, threading
local_school = threading.local() # 定义threadinglocal对象
def process_student(std):
std = local_school.student # 获取当前线程的局部变量
print(\'Hello %s (%s)\\n\' % (std, threading.current_thread().name))
def process_thread(name):
local_school.student = name # 将name传入threadinglocal对象
process_student(name)
t1 = threading.Thread(target = process_thread, args = (\'Tom\', ), name = \'TA\')
t2 = threading.Thread(target = process_thread, args = (\'Jack\', ), name = \'TB\')
print(t1.name)
print(t2.name)
t1.start()
t2.start()
t1.join()
t2.join()
ThreadLocal 最常用的地方就是为每个线程绑定一个数据库连接,HTTP请求,用户身份信息等,这样一个线程的所有调用到的处理函数都可以非常方便地访问这些资源。
版权声明
本文仅代表作者观点,不代表百度立场。
本文系作者授权百度百家发表,未经许可,不得转载。

