Python中的进程池和线程池(第7节)


在Python中,我们创建多进程的目的是为了并发执行,如果电脑有多核CPU,通常有几个核就可以开几个进程,进程开启过多,执行效率反而会下降,因为开启进程是需要占用系统资源的,而且系统不可能无限的创建进程,它本身会受到电脑CPU和内存的约束。显然,如果并发执行的任务要远大于CPU核数,这时我们就需要创建一个处理进程的池子来控制进程数目,提高执行的效率,节省内存空间。

1、进程池

进程池顾名思义就是一个装进程的池子,可以提供指定数量的进程给用户使用,即当有新的请求提交到进程池中时,如果进程池未满,则会创建一个新的进程用来执行该请求。相反,如果池中的进程数已经达到规定最大值,那么该请求就会在进程池门口等待,直到进程池中有进程空闲下来,该请求就能进入进程池得到执行。

Python中,通过multiprocessing模块中的Pool类来创建功能强大的进程池,例如:

动手练一练:

import multiprocessing
import time

def worker(n):
    """该函数将在子进程中执行"""
    print("第", n, "个进程开始执行时间:", time.ctime())
    time.sleep(2)
    print("第", n, "个进程结束执行时间:", time.ctime())

def main():
    # 创建进程池,其中processes参数指定了池中的进程数量
    pool = multiprocessing.Pool(processes=3)
    for i in range(4):
        #向进程池中添加要执行的任务
        pool.apply_async(worker, args=(i,))

    #先关闭进程池,不能再有新任务被加入到进程池中
    pool.close()
    #等待所有子进程结束
    pool.join()

if __name__=="__main__":
    main()

执行以上代码,输出结果为:

 1 个进程开始执行时间 Wed May 8 19:26:34 2024
 1 个进程结束执行时间 Wed May 8 19:26:36 2024
 2 个进程开始执行时间 Wed May 8 19:26:34 2024
 2 个进程结束执行时间 Wed May 8 19:26:36 2024
 0 个进程开始执行时间 Wed May 8 19:26:34 2024
 0 个进程结束执行时间 Wed May 8 19:26:36 2024
 3 个进程开始执行时间 Wed May 8 19:26:36 2024
 3 个进程结束执行时间 Wed May 8 19:26:38 2024

上面的例子中,我们创建了一个进程池,其中processes参数指定了池中的进程数量为3个。然后,我们使用pool.apply_async()方法将4个任务提交到进程池中进行处理。从运行的结果可以看出,前面3个任务是同时进行的,第4个任务只能等待前面3个任务完成后才能执行。因为进程池中的进程数已经达到规定的3个数量,那么第4个任务就会在进程池门口等待,直到进程池中有进程结束,第4个任务才能进入进程池得到执行。

为了使代码更加简洁和高效,还可以使用Pool的map函数,例如:

动手练一练:

import multiprocessing
import time

def worker(n):
    """该函数将在子进程中执行"""
    print("第", n, "个进程开始执行时间:", time.ctime())
    time.sleep(2)
    print("第", n, "个进程结束执行时间:", time.ctime())

def main():
    # 创建进程池,其中processes参数指定了池中的进程数量
    pool = multiprocessing.Pool(processes=3)
    # 启动进程池中的进程
    pool.map(worker, range(4))
    #先关闭进程池,不能再有新任务被加入到进程池中
    pool.close()
    #等待所有子进程结束
    pool.join()

if __name__=="__main__":
    main()

执行以上代码,输出结果为:

 1 个进程开始执行时间 Wed May 8 19:53:14 2024
 1 个进程结束执行时间 Wed May 8 19:53:16 2024
 2 个进程开始执行时间 Wed May 8 19:53:14 2024
 2 个进程结束执行时间 Wed May 8 19:53:16 2024
 0 个进程开始执行时间 Wed May 8 19:53:14 2024
 0 个进程结束执行时间 Wed May 8 19:53:16 2024
 3 个进程开始执行时间 Wed May 8 19:53:16 2024
 3 个进程结束执行时间 Wed May 8 19:53:18 2024

上面的例子中,我们创建了一个进程池,其中processes参数指定了池中的进程数量为3个,然后通过调用map方法来启动进程池中的进程。map()函数会自动将range(4)序列中的每个元素当作参数分别传入到worker()函数中,然后将worker()函数执行的任务分别传递给进程池中的进程。最后,调用close方法关闭进程池,并调用join方法等待所有进程结束。

2、线程池

多线程实现线程池的方法和多进程实现进程池的方法类似,multiprocessing模块中的multiprocessing.dummy模块提供了Pool类,它同样会预先创建指定数量的线程给用户使用,即当有新的任务提交到线程池中时,如果线程池未满,则会创建一个新的线程用来执行该任务。相反,如果池中的线程数已经达到规定最大值,那么该任务就会在线程池门口等待,直到线程池中有线程空闲下来,该任务就能进入线程池得到执行。这种方式可以固定线程的数量,并且可以避免并发任务的过载,提高程序的效率。线程池的实现过程同样可以使用map()函数。例如:

动手练一练:

import multiprocessing.dummy
import time

def worker(n):
    """该函数将在子线程中执行"""
    print("第", n, "个线程开始执行时间:", time.ctime())
    time.sleep(2)
    print("第", n, "个线程结束执行时间:", time.ctime())

def main():
    # 虽然参数叫process,但是创建的是线程
    pool = multiprocessing.dummy.Pool(processes=3)
    # 启动线程池中的线程
    pool.map(worker, range(4))
    #先关闭线程池,不能再有新任务被加入到线程池中
    pool.close()
    #等待所有子线程结束
    pool.join()

if __name__=="__main__":
    main()

执行以上代码,输出结果为:

 0 个线程开始执行时间 Wed May 8 20:41:10 2024
 1 个线程开始执行时间 Wed May 8 20:41:10 2024
 2 个线程开始执行时间 Wed May 8 20:41:10 2024
 0 个线程结束执行时间 Wed May 8 20:41:12 2024
 3 个线程开始执行时间 Wed May 8 20:41:12 2024
 2 个线程结束执行时间 Wed May 8 20:41:12 2024
 1 个线程结束执行时间 Wed May 8 20:41:12 2024
 3 个线程结束执行时间 Wed May 8 20:41:14 2024

在Python中,多线程和多进程是实现并发编程的两种常见方式。然而,直接使用线程或进程可能会导致资源利用率低或者线程/进程数量过载导致系统崩溃。为了解决这些问题,Python提供了线程池和进程池的功能,可以明显提高程序的效率,特别是对于那些需要大量计算或者需要处理大型数据集的程序,它可以控制程序中的任务在多个进程中并行地执行,从而提高整个程序的执行效率。除此之外,Pool类还可以用于网络中执行任务、爬虫、并发请求等方面。

3、多进程在网络爬虫中的运用

在Python爬虫中,一般用多进程进行爬取数据,因为多线程并不能提高CPU的使用率,而且多线程其实是交替执行,多进程才是并发执行。多进程是一种并发编程技术,它可以让程序同时运行多个进程,有效提高爬取速度,因为多个进程可以同时从不同的网页上下载数据,即使有子进程崩溃了,主进程和其他进程依然可以继续爬取数据,以下是使用Python的多进程模块来实现爬取网页内容的示例代码:

动手练一练:

import requests
import multiprocessing

def worker(url):
    response = requests.get(url)
    if response.status_code == 200:
        return response.text
    else:
        return None

def main():
    urls = ['http://网址.com/page1', 'http://网址.com/page2', 'http://网址.com/page3']
    # 创建3个进程
    with multiprocessing.Pool(processes=3) as pool:
        results = pool.map(worker, urls)
    # 并发爬取网页
    for result in results:
        print(result)

if __name__ == '__main__':
    main()

上面的例子中,我们把“网址”内容替换成要爬取的网址,就可以使用multiprocessing模块来创建多个进程爬取网页数据,每个进程负责爬取一组URL中的一个网页。以爬取某电商网站的商品信息为例,我们可以创建多个进程,每个进程负责爬取一部分商品的信息。这样,我们就可以在同一时间内爬取更多的商品信息,大大提高了爬虫的效率。但是,在实际爬取的过程中,很多网址可能会有限制,这就需要深入了解目标网站的反爬取策略,才能让爬虫顺利工作。