Python(2)---并发编程


目录

1. Python多线程与多进程知识
  1.1 并发与并行
  1.2 线程(thread)与进程(process)
  1.3 IO密集型与CPU密集型
  1.4 GIL(Global Interpreter Lock)
2. Python多线程与多进程编程实战
  2.1 测试环境
  2.2 导入库并定义浏览器伪装函数
  2.3 定义HTTP访问函数
  2.4 单线程计时函数
  2.5 多线程计时函数
  2.6 多进程计时函数
  2.7 执行效率可视化
  2.8 函数调用
  2.9 结果分析
3. 后记


1 Python多线程与多进程

1.1 并发与并行

** (1)并发 **
  对应多线程,相当于一个人轮流喂两个孩子,看上去好像是喂两个孩子,实际上每次只有一个孩子在吃东西。
  在单处理器上,并发程序虽然有多个上下文运行环境,但某一个时刻只有一个任务在运行;在多处理器上,因为有了多个执行单元,就可以同时有数个任务在跑,这就是为什么多线程可以提高速度的原因之一。
** (2)并行 **
  对应多进程,相当于两个人喂两个孩子,这才是真正意义上的并行。和并发相比,并行更加强调同一时刻有多个任务同时运行。

1.2 线程(thread)与进程(process)

进程好比一个工厂里面的某个车间,线程好比该车间内部的不同工人协同完成任务。教科书上最经典的一句话是:“进程是资源分配的最小单位,线程是CPU调度的最小单位”推荐以下文章进一步阅读。

1.3 IO密集型与CPU密集型

(1)IO密集型任务
  磁盘IO、网络IO占主要的任务,计算量很小。比如请求网页、读写文件等。当然我们在Python中可以利用sleep达到IO密集型任务的目的。
(2)计算密集型任务
  CPU计算占主要的任务,CPU一直处于满负荷状态。比如例如一个计算圆周率至小数点一千位以下的程序,在执行的过程当中绝大部份时间用在三角函数和开根号的计算,复杂的加减乘除等。

1.4 GIL(Global Interpreter Lock)

GIL是为线程安全而生,但随着多核技术的普及其越来越被诟病,很多人经常将GIL和Python无法高效的实现多线程划上等号。
  网络也有很多介绍GIL的文章,在此不再复述,具体可以参考以下文章。

2 Python多线程与多进程编程实战

在Python中实现多线程和多进程主要涉及的库有thread、threading与multiprocessing。前两者主要用来实现多线程,后者用来实现多线程与多进程,在此以multiprocessing为例进行多线程与多进程编程实战爬取豆瓣网。

2.1 测试环境

(1)软硬件:CentOS6.5+Anaconda 4.3.0 For Linux(Python 3.6)
虚拟机配置如下图:

虚拟机配置

(2)测试网站:豆瓣电影

之所以选择Linux环境是因为在windows下开启多进程的时候出现如下情况(程序一直在运行没办法退出),尝试了将Python3.5环境改成2.7环境也是如此,似乎陷入进程死循环中。在此不解,如有简友明白的欢迎简信交流。

2.2 导入库并定义浏览器伪装函数

import urllib
from multiprocessing.dummy import Pool as mulThread #多线程池
from multiprocessing import Pool as mulProcess #多进程池
import time
import random  
import pylab as pl

def GetUserAgent():
   '''
   功能:随机获取HTTP_User_Agent
   '''
   user_agents=[
   "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; AcooBrowser; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
   "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Acoo Browser; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506)",
   "Mozilla/4.0 (compatible; MSIE 7.0; AOL 9.5; AOLBuild 4337.35; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
   "Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)",
   "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)",
   "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)",
   "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.04506.30)",
   "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)",
   "Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6",
   "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2pre) Gecko/20070215 K-Ninja/2.1.1",
   "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/20080705 Firefox/3.0 Kapiko/3.0",
   "Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5",
   "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.8) Gecko Fedora/1.9.0.8-1.fc10 Kazehakase/0.5.6",
   "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11",
   "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.20 (KHTML, like Gecko) Chrome/19.0.1036.7 Safari/535.20",
   "Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; fr) Presto/2.9.168 Version/11.52",
   "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.11 TaoBrowser/2.0 Safari/536.11",
   "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.71 Safari/537.1 LBBROWSER",
   "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; LBBROWSER)",
   "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E; LBBROWSER)",
   "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.84 Safari/535.11 LBBROWSER",
   "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)",
   "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; QQBrowser/7.0.3698.400)",
   "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E)",
   "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; SV1; QQDownload 732; .NET4.0C; .NET4.0E; 360SE)",
   "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E)",
   "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)",
   "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.89 Safari/537.1",
   "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.89 Safari/537.1",
   "Mozilla/5.0 (iPad; U; CPU OS 4_2_1 like Mac OS X; zh-cn) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8C148 Safari/6533.18.5",
   "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b13pre) Gecko/20110307 Firefox/4.0b13pre",
   "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:16.0) Gecko/20100101 Firefox/16.0",
   "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11",
   "Mozilla/5.0 (X11; U; Linux x86_64; zh-CN; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10"
   ]
   user_agent = random.choice(user_agents)
   return user_agent

2.3 定义HTTP访问函数

def getURLs():
    '''获取所需爬取的所有URL'''
    urls = []
    for i in range(0, 101,20):#每翻一页其start值增加20
        keyword = "科幻"
        keyword = urllib.request.quote(keyword)
        newpage = "https://movie.douban.com/tag/"+keyword+"?start="+str(i)+"&type=T"
        urls.append(newpage)
    return urls  

def getResponse(url):
    '''获取响应信息'''
    global res
    user_agent = GetUserAgent()
    headers=("User-Agent",user_agent)
    opener = urllib.request.build_opener() 
    opener.addheaders = [headers] 
    try:
        res = opener.open(url,timeout=5).read()
    except Exception as er:
        print("爬取的时候发生错误,具体如下:")
        print(er)
    return res

2.4 单线程计时函数

def singleTime(urls):
    '''单进程计时'''
    time1 = time.time()
    for i in urls:
        print(i)
        getResponse(i) 
    time2 = time.time()
    return str(time2 - time1) 

2.5 多线程计时函数

def mulThreadTime(urls,nums):
    '''多线程计时'''
    pool = mulThread(processes=nums) #开启四个线程
    time3 = time.time()
    pool.map(getResponse,urls)
    pool.close()
    pool.join() #等待线程池中的worker进程执行完毕
    time4 = time.time()
    return str(time4 - time3)  

2.6 多进程计时函数

def mulProcessTime(urls,nums):
    '''多进程计时'''
    pool = mulProcess(processes=nums) #开启四个进程
    time5 = time.time()
    pool.map(getResponse,urls)
    pool.close()
    pool.join() #等待进程池中的worker进程执行完毕
    time6 = time.time()
    return str(time6 - time5) 

2.7 执行效率可视化

def getPlot(singleTimes,mulThreadTimes,mulProcessTimes):
    '''执行效率可视化'''
    thread_num = [2,3,4,5,6,7,8]
    process_num = [2,3,4,5,6,7,8]
    pl.plot(thread_num,[singleTimes]*7,'b',label='singleTimes')
    pl.plot(thread_num,mulThreadTimes,'r',label='mulThreadTimes')
    pl.plot(process_num,mulProcessTimes,'g',label='mulProcessTimes')
    pl.xlabel('The num of thread or process')# make axis labels
    pl.ylabel('times(s)')
    pl.legend()
    show_plot = pl.show()# show the plot on the screen 
    return show_plot

2.8 函数调用

if __name__ == '__main__':
    urls = getURLs()
    ####单线程计时  
    singleTimes = singleTime(urls) 
    ####多线程计时
    mulThreadTimes = []
    for num in range(2,9):
        tmpTimes = mulThreadTime(urls,num)
        mulThreadTimes.append(tmpTimes) 
    ####多进程计时
    mulProcessTimes = []
    for num in range(2,9):
        tmpTimes = mulProcessTime(urls,num)
        mulProcessTimes.append(tmpTimes) 
    ####执行效率可视化
    getPlot(singleTimes,mulThreadTimes,mulProcessTimes)

2.9 结果分析

res.png

(1)多线程与多进程的效率均优于单进程
(2)多线程在3线程时效率最优,5线程效率最差
(3)多进程在7进程时效率最优,5进程效率最差
(4)在多线程在4个线程前效率均大于同等数量的多进程,但到了4个以及4个以线程效率均差于同等数量的多进程

Python的多线程在多核CPU上,只对于IO密集型计算产生正面效果;而当有至少有一个CPU密集型线程存在,那么多线程效率会由于GIL而大幅下降。

3 后记

大致记录了Python并发的相关知识,关于里面的效率呈现机理在后续的学习中有待进一步研究。

参考与拓展阅读:
[1]Python模块学习:threading 多线程控制和处理
[2]Python中的multiprocessing和threading
[3]Python 多线程 threading和multiprocessing模块
[4]Python并发编程之协程/异步IO


个人Github
个人博客DebugNLP
欢迎各路同学互相交流

推荐阅读更多精彩内容