由于全局解释器锁的存在,python中的多线程使用有许多的局限性,因此部分场合使用多进程会优于多线程,比如在cpu计算密集型的环境下。
python中使用multiprocessing模块实现多进程。python多进程中引入了多线程中没有的Queue、Pool等模块,方便我们更好的使用多进程。
我们常说对于I/O密集型使用多线程,对于CPU密集型使用多进程,但其实I/O密集型的场景下也可以使用多进程,只不过多进程是十分耗费系统资源的,因此多线程是一个更优的选择。下面我们通过一段代码来感受一下多进程的优势
import threading
import multiprocessing
import time
# 向列表中添加1-100000000的数字,
def func(start,stop):
lst = []
for i in range(start,stop):
lst.append(i)
if __name__ == "__main__":
# 多线程添加
thread_list = []
start_time = time.time()
t1 = threading.Thread(target=func,args=(1,50000000))
t2 = threading.Thread(target=func,args=(50000000,100000001))
thread_list.append(t1)
thread_list.append(t2)
t1.start()
t2.start()
for thread in thread_list:
thread.join() # 阻塞主线程
end_time = time.time()
print("多线程用时:{}秒".format(end_time-start_time))
# 单线程添加
start_time = time.time()
func(1,100000001)
end_time = time.time()
print("单线程用时:{}秒".format(end_time - start_time))
# 多进程添加
start_time = time.time()
process_list = []
m1 = multiprocessing.Process(target=func,args=(1,50000000))
m2 = multiprocessing.Process(target=func,args=(50000000,100000001))
process_list.append(m1)
process_list.append(m2)
m1.start()
m2.start()
for p in process_list:
p.join()
end_time = time.time()
print("多进程用时:{}秒".format(end_time-start_time))
想列表中添加1亿个数字,单线程用时18秒,多线程用时和单线程相比并没有什么优势的。多进程仅仅用时11秒,可以看出多进程在cpu密集型的情况下优势还是很明显的。
多进程的实现还可以通过继承的方式实现,
import multiprocessing
import time
class Myprocessing(multiprocessing.Process):
def __init__(self,_start,stop): # 防止与内置方法重名,start加下划线或改名字
super(Myprocessing, self).__init__()
self._start = _start
self.stop = stop
# 复写父类的run方法
def run(self):
lst = []
for i in range(self._start,self.stop):
lst.append(i)
if __name__ == "__main__":
start_time = time.time()
process_list = []
m1 = Myprocessing(1,50000000)
m2 = Myprocessing(50000000,100000001)
process_list.append(m1)
process_list.append(m2)
m1.start()
m2.start()
for p in process_list:
p.join()
end_time = time.time()
print("多进程用时:{}秒".format(end_time - start_time))
如果我们有100个任务需要处理,创建100个子进程是非常耗费资源的,我们可以指定10个子进程去处理这100个任务,python多进程中引入了进程池,创建进程池对象的时候可以指定一个最大进程数,当有新的请求提交到进程池中,如果池中的进程数还没有满,那么就会创建一个新的进程用来执行该请求, 但是如果池中的进程数满了,该请求就会等待,知道进程池中的进程有结束的了, 才会使用这个结束的进程来执行新的任务。
from multiprocessing import Pool # 导入进程池类
import time
def func(start,stop):
lst = []
for i in range(start,stop):
lst.append(i)
if __name__ == "__main__":
start_time = time.time()
pool = Pool(2) # 创建进程池对象,最多创建两个子进程
num_list = [(1,50000000),(50000000,100000001)]
for num in num_list:
pool.apply_async(func,num) # 添加子进程
pool.close() # 等待所有子进程执行完毕关闭进程池
pool.join() # 阻塞主进程,必须写在close()之后
end_time = time.time()
print("多进程用时:{}秒".format(end_time - start_time))
进程可以理解为复制了一份程序加载到内存中,进程之间的执行是相互独立的,那么我们如何进行进程之间的通信呢?python多进程中有一个模块Queue队列就是来处理进程之间的通信的,队列是一种先进先出的数据结构。当两个进程进行通信时,只需要一个进程往queue队列里写内容,另一个进程从queue中取内容就可以了。
Queue有个参数maxsize,表示队列中允许的最大项数,默认为无限大。
Queue中有一些常用的参数:
put():向队列中存放数据,如果队列已满将阻塞,直到队列释放空间。
get():返回队列的一条数据,如果队列为空将阻塞,直到队列有数据可用为止。
qsize():获取队列中数据的数量。
empty():判断队列是否为空,返回True为空,返回False不为空。
full():判断队列是否已满,返回True已满,返回False未满。
get_nowait():取数据时不等待,如果队列为空直接抛出异常。
put_nowait():存数据时不等待,如果队列已满,直接抛出异常。
close():关闭队列。
import multiprocessing
import random
import time
def func1(queue):
for i in range(1,6):
sec = random.randrange(1,5)
time.sleep(sec)
queue.put(i) # 向队列中添加数据
print("{} 第{}个数字加入队列".format(time.strftime("%H:%M:%S",time.localtime()),i))
def func2(queue):
for i in range(1,6):
queue.get() # 取出队列中的数据
print("{} 取出队列中第{}个数字".format(time.strftime("%H:%M:%S"),i))
if __name__ == "__main__":
queue = multiprocessing.Queue(3) # 队列中最多可添加三个数据
m1 = multiprocessing.Process(target=func1,args=(queue,)) # 创建进程,并将queue作为参数传入
m2 = multiprocessing.Process(target=func2,args=(queue,))
m1.start() # 启动进程
m2.start()