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
欢迎各路同学互相交流

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容