一、编程思想

并行编程的思想:分而治之,有两种模型

1.MapReduce:将任务划分为可并行的多个子任务,每个子任务完成后合并得到结果

例子:统计不同形状的个数。

先通过map进行映射到多个子任务,分别统计个数,然后在用reduce进行归纳一下。

2.流水:将任务分为串行的多个子任务,每个子任务并行。ProductConsume

例子:

多个生产者进行并行,多个消费者进行并行。生产者生产出来东西放到队列里;队列里有东西时,消费者就可以进行消费,这样双方没有太大的依赖关系。

为什么要并行编程呢?

多核,云计算,使得实现并行编程的条件更容易满足。

大数据(导致数据多),机器学习(复杂),高并发,使得并行编程很必要。

为什么很少用呢?

任务分割,共享数据的访问,死锁,互斥,信号量,利用管道,队列通信。线程,进程的管理。

这些概念使得并行编程的实现看上去很难

怎么学并行编程?

库:  Threading,实现多线程

    Multiprocess,实现多进程

    Parallepython,实现分布式计算,同时解决CPU和网络资源受限问题。

    Celery+RabbitMQ/Redis,可实现分布式任务队列 Django和它搭配可实现异步任务队列

    Gevent,可实现高效异步IO,协成

2.进程和线程

CPU同一时刻只能调度一个进程,进程之间memory独立,进程内线程共享memory。

我们主要解决的问题是:

进程间通信问题;

线程间同步问题

例子:计算10000000000自减到0,然后用多进程和多线程计算,看看他们用时多久

# -*- coding: utf-8 -*-
# CopyRight by heibanke import time
from threading import Thread
from multiprocessing import Process def countdown(n):
while n > 0:
n -= 1 COUNT = 100000000 # 1亿 def thread_process_job(n, Thread_Process, job):
"""
n: 多线程或多进程数
Thread_Process: Thread/Process类
job: countdown任务
"""
local_time=time.time() #实例化多线程或多进程
threads_or_processes = [Thread_Process(target=job,args=(COUNT//n,)) for i in xrange(n)]#学习这种写法,很高大上,把不同的类放到列表里边
#threads_or_processes中保存了三个Thread_process个对象 for t in threads_or_processes:
t.start() #开始线程或进程,必须调用
for t in threads_or_processes:
t.join() #等待直到该线程或进程结束
#join的作用是阻塞进程,直到所有的线程执行完毕之后,才可以执行后边的语句 print n,Thread_Process.__name__," run job need ",time.time()-local_time if __name__=="__main__":
print "Multi Threads"
for i in [1,2,4]:
thread_process_job(i,Thread, countdown) print "Multi Process"
for i in [1,2,4]:
thread_process_job(i,Process, countdown)

输出结果:

从结果中看出来,多线程时,随着线程的增多,时间反而更多;多进程随着进程的增多,时间变少。原因是python的GIL机制

GIL

当有多个线程的时候,并不是真的是并行运行的,实际上有一个锁,谁申请到了谁运行

在python的原始解释器CPython中存在着GIL(Global Interpreter Lock,全局解释器锁),因此在解释执行python代码时,会产生互斥锁来限制线程对共享资源的访问,直到解释器遇到I/O操作或者操作次数达到一定数目时才会释放GIL。

所以,虽然CPython的线程库直接封装了系统的原生线程,但CPython整体作为一个进程,同一时间只会有一个获得GIL的线程在跑,其他线程则处于等待状态。这就造成了即使在多核CPU中,多线程也只是做着分时切换而已。

所以它更适合处理I/O密集型的任务,不适合处理CPU密集型的任务。

不过muiltprocessing的出现,已经可以让多进程的python代码编写简化到了类似多线程的程度了。(链接:https://www.zhihu.com/question/23474039/answer/35418893)

这是两个线程在运行,并不是并行,而是串行,红色的线表示在申请cpu

四个线程在运行

进程可以快,而线程反而慢的原因是,我的电脑有多个核,进程可以进行并行的,而线程在python里边还是串行的,申请cpu也需要花费时间的

三、I/O密集型任务

I/O密集型任务是诸如频繁的磁盘读取,或者通过网络进行获取数据,如爬虫

比如,第一个线程运行,然后遇到I/O请求,这个I/O请求不会满上满足你,所以就切换到线程2上进行,过了会儿,线程2也有I/O请求,所以切换到线程3,然后线程3也有I/O请求,此时线程1的I/O请求完成,然后切换到线程1运行……

举例:对韩寒博客进行爬取

步骤:

1.获取urls;

2.将urls分给不同的进程或线程(相当于分配子任务);

3.多进程/线程抓取

分析:韩寒网站是:http://blog.sina.com.cn/s/articlelist_1191258123_0_1.html

如图示,第几页,对应的相应的网址的箭头部位就变成几,所以我们用下面语句获取每一页的内容:

    for i in xrange(7):
#这里的extend要注意,是在list后边接上
#list.extend(seq):在列表末尾一次性追加另一个序列中的多个值(用新列表扩展原来的列表)
#str(i+1),因为我们从网站中可以看到,每一页的变化,第一页网站的此处就是1,第二页就是2,以此类推,一共七页
urls.extend(parseTarget('http://blog.sina.com.cn/s/articlelist_1191258123_0_'+str(i+1)+'.html'))

总的程序如下所示:

#!/usr/bin/env python
# coding: utf-8
#copyRight by heibanke import urllib
import os
import re
from threading import Thread
from multiprocessing import Process
import time def downloadURL(urls,dirpath):
"""
urls: 需要下载的url列表
dirpath: 下载的本地路径
"""
for url in urls:
if len(url)>0:
#print "current process id is ",os.getpid()
content = urllib.urlopen(url).read()
if not os.path.exists(dirpath):
os.makedirs(dirpath)
#dirpath实际上是文件夹,例如是1Process,+后边的文件夹中的文件名字
#把从网址中读到的所有信息存到该文件中
#url[-26:]是取url的倒数26个,'w'是写模式
open(dirpath+r'/'+url[-26:],'w').write(content) def parseTarget(url):
"""
根据目标url获取文章列表的urls
"""
urls=[]
content=urllib.urlopen(url).read()
#{.*} -- 尽可能多的吸取匹配字符串 (贪婪模式)
#{.*?} -- 只要一匹配到,就不再往后吸取字符 (懒惰模式)
pattern = r'<a title=(.*?) href="(.*?)">'
hrefs = re.findall(pattern,content) for href in hrefs:
urls.append(href[1])#只把href读取出来 return urls def thread_process_job(n, Thread_or_Process, url_list, job):
"""
n: 多线程或多进程数
Thread_Process: Thread/Process类
job: countdown任务
"""
local_time=time.time()
threads_or_processes = [Thread_or_Process(target=job,args=(url_list[i],str(n)+Thread_or_Process.__name__)) for i in xrange(n)]
for t in threads_or_processes:
t.start()
for t in threads_or_processes:
t.join() print n,Thread_or_Process.__name__," run job need ",time.time()-local_time if __name__=="__main__": t=time.time() urls=[]#urls是列表,和numpy中的array要区分开
for i in xrange(7):
#这里的extend要注意,是在list后边接上
#list.extend(seq):在列表末尾一次性追加另一个序列中的多个值(用新列表扩展原来的列表)
#str(i+1),因为我们从网站中可以看到,每一页的变化,第一页网站的此处就是1,第二页就是2,以此类推,一共七页
urls.extend(parseTarget('http://blog.sina.com.cn/s/articlelist_1191258123_0_'+str(i+1)+'.html')) url_len = len(urls) print "total urls number is ",url_len for n in [8,4,2,1]:
#将urls分割到url_list
url_list=[] #从Python2.2开始,增加了一个操作符 // ,以执行地板除://除法不管操作数为何种数值类型,
#总是会舍去小数部分,返回数字序列中比真正的商小的最接近的数字。
url_split_len = url_len//n
for i in xrange(n):
if i==n-1:
#和上边的extend区别开来
#list.append(obj):在列表末尾添加新的对象,所以它被作为单独整体加入的
#extend是飞散成一个一个的被加入的,这里注意区别
url_list.append(urls[i*url_split_len:url_len])
else:
url_list.append(urls[i*url_split_len:(i+1)*url_split_len])
#分割任务后创建线程
#url_list_len=len(url_list)
#print "total urls_list number is ",url_list_len
thread_process_job(n,Thread, url_list, downloadURL)
thread_process_job(n,Process, url_list, downloadURL) print "All done in ",time.time()-t

输出结果显示:

>>>
total urls number is 315
8 Thread run job need 33.6749999523
8 Process run job need 33.5950000286
4 Thread run job need 40.2200000286
4 Process run job need 90.7750000954
2 Thread run job need 86.0289998055
2 Process run job need 87.0989999771
1 Thread run job need 131.422999859
1 Process run job need 123.995000124
All done in 629.394000053
>>>

由于网速等原因,时间上会有起伏。

上述代码有一个地方有问题,就是在生成目录的时候,有可能会发生一个进程/线程在创建着目录,另一个进程/线程发现没有目录,然后也创建目录的情况。

四、LOCK锁

我们可以用lock锁来保护公共资源。

还是上边用的生产者和消费者模型

没有lock的时候:

#!/usr/bin/env python
# coding: utf-8
#copyRight by heibanke import time
import random
import threading #当还剩下0个产品时,则不进行消费,待生产者生产
#当生产了100个产品时,则不进行生产,待消费者消费 #生产者
class Producer(threading.Thread):
def __init__(self, product,filename):
self.product = product
self.file = filename
threading.Thread.__init__(self) def run(self):
while len(self.product)<100:
tmp = random.randint(0,10)
self.product.append(tmp)
print "add %d, product = %s" %(tmp,str(self.product))
fp=open(self.file,'a')
fp.write("add %d, product = %s\n" %(tmp,str(self.product)))
fp.close()
time.sleep(0.1)
#time.sleep(random.randrange(5)) #消费者
class Consumer(threading.Thread):
def __init__(self, product, filename):
self.product = product
self.file = filename
threading.Thread.__init__(self) def run(self):
while True:
if len(self.product)>0:
tmp = self.product[0]
del self.product[0]
print 'consum %d, product = %s'%(tmp,str(self.product))
fp=open(self.file,'a')
fp.write('consum %d, product = %s\n'%(tmp,str(self.product)))
fp.close()
time.sleep(0.1)
#time.sleep(random.randrange(4)) if __name__ == '__main__':
product = [] #产品初始化时为0 for i in range(5):#五个生产者
p = Producer(product,'log.txt')
p.start() for i in range(3):#三个消费者
s = Consumer(product,'log.txt')
s.start()

会出错。

有锁的时候:

 #!/usr/bin/env python
# coding: utf-8
#copyRight by heibanke import time
import random
import threading #当还剩下0个产品时,则不进行消费,待生产者生产
#当生产了100个产品时,则不进行生产,待消费者消费 lock = threading.Condition() #生产者
class Producer(threading.Thread):
def __init__(self, lock, product,filename):
self._lock = lock
self.product = product
self.file = filename
threading.Thread.__init__(self) def run(self):
while True:
if self._lock.acquire():
if len(self.product) >= 100:
self._lock.wait()
else:
tmp = random.randint(0,10)
self.product.append(tmp)
print "add %d, product = %s" %(tmp,str(self.product))
fp=open(self.file,'a')
fp.write("add %d, product = %s\n" %(tmp,str(self.product)))
fp.close()
self._lock.notify()
self._lock.release()
time.sleep(0.1)
#time.sleep(random.randrange(5)) #消费者
class Consumer(threading.Thread):
def __init__(self, lock, product, filename):
self._lock = lock
self.product = product
self.file=filename
threading.Thread.__init__(self) def run(self):
while True:
if self._lock.acquire():
if len(self.product)== 0:
self._lock.wait()
else:
tmp = self.product[0]
del self.product[0]
print 'consum %d, product =%s'%(tmp,str(self.product))
fp=open(self.file,'a')
fp.write('consum %d, product = %s\n'%(tmp,str(self.product)))
fp.close()
self._lock.notify()
self._lock.release()
time.sleep(0.1)
#time.sleep(random.randrange(4)) if __name__ == '__main__':
product = [] #产品初始化时为0
for i in range(5):
p = Producer(lock,product,'log_lock.txt')
p.start() for i in range(3):
s = Consumer(lock,product,'log_lock.txt')
s.start()

python并行编程的更多相关文章

  1. Python并行编程的几个要点

    一.基于线程的并行编程 如何使用Python的线程模块 如何定义一个线程 如何探测一个线程 如何在一个子类中使用线程 Lock和RLock实现线程同步 信号实现线程同步 条件(condition)实现 ...

  2. Python并行编程(十四):异步编程

    1.基本概念 除了顺序执行和并行执行的模型以外,还有异步模型,这是事件驱动模型的基础.异步活动的执行模型可以只有一个单一的主控制流,能在单核心系统和多核心系统中运行. 在并发执行的异步模型中,许多任务 ...

  3. Python并行编程(二):基于线程的并行

    1.介绍 软件应用中使用最广泛的并行编程范例是多线程.通常一个应用有一个进程,分成多个独立的线程,并行运行.互相配合,执行不同类型的任务. 线程是独立的处理流程,可以和系统的其他线程并行或并发地执行. ...

  4. python并行编程学习之绪论

    计算机科学的研究,不仅应该涵盖计算处理所基于的原理,还因该反映这些领域目前的知识状态.当今,计算机技术要求来自计算机科学所有分支的专业人员理解计算机处理的基础的关键,在于知道软件和硬件在所有层面上的交 ...

  5. Python并行编程(十一):基于进程的并行

    1.基本概念 多进程主要用multiprocessing和mpi4py这两个模块. multiprocessing是Python标准库中的模块,实现了共享内存机制,可以让运行在不同处理器核心的进程能读 ...

  6. Python并行编程(十三):进程池和mpi4py模块

    1.基本概念 多进程库提供了Pool类来实现简单的多进程任务.Pool类有以下方法: - apply():直到得到结果之前一直阻塞. - apply_async():这是apply()方法的一个变体, ...

  7. Python并行编程(十二):进程同步

    1.基本概念 多个进程可以协同工作来完成一项任务,通常需要共享数据.所以在多进程之间保持数据的一致性就很重要,需要共享数据协同的进程必须以适当的策略来读写数据.同步原语和线程的库类似. - Lock: ...

  8. Python并行编程(十):多线程性能评估

    1.基本概念 GIL是CPython解释器引入的锁,GIL在解释器层面阻止了真正的并行运行.解释器在执行任何线程之前,必须等待当前正在运行的线程释放GIL,事实上,解释器会强迫想要运行的线程必须拿到G ...

  9. Python并行编程(九):线程通讯queue

    1.基本概念 当线程之间要共享资源或数据的时候,可能变的非常复杂.Python的threading模块提供了很多同步原语,包括信号量,条件变量,事件和锁.如果可以使用这些原语的话,应该优先考虑使用这些 ...

随机推荐

  1. 日期获取 net

    项目中用到了,所以就写全了,供参考使用. DateTime dt=DateTime.Now;int weeknow = Convert.ToInt32(DateTime.Now.DayOfWeek); ...

  2. 设计模式初学者笔记:Abstract Factory模式

    首先啰嗦下创建迷宫所用的Room类.这个类并不直接保存Room四周的构造,而是通过MapSite* _sides[4]这个私有数组成员指向Room四周的构造.那么什么时候将四周构造直接放在Room中, ...

  3. HDFS高级开发培训课程之HDFS开发实例课件

    前言: 刚刚完成的HDFS高级开发培训课程课件中的一个章节,不知道PPT,如何导出HTML格式,只好批量导出图片,贴图了. 连接管理:建立连接.断开连接.设置连接参数 文件操作:浏览文件.上传文件.下 ...

  4. linux 下 rpc python 实例之使用XML-RPC进行远程文件共享

    这是个不错的练习,使用python开发P2P程序,或许通过这个我们可以自己搞出来一个P2P下载工具,类似于迅雷.XML-RPC是一个远程过程调用(remote procedure call,RPC)的 ...

  5. Java Internet

    网络通信: 网络通信三要素: IP 协议 端口 TCP: 建立连接,发送速度慢 三次握手协议 UDP: 不需要建立连接,发送速度快 安全性低 a) 使用UDP实现数据的发送 1 创建Socket端点实 ...

  6. unicode 转码 ansi

    #include "stdafx.h"#include <Windows.h>#include <stdio.h> HRESULT SomeCOMFunct ...

  7. no_unnest,push_subq,push_pred的用法 (转)

    常常有人把这三个hint搞混,主要是因为对三种重写原理不清楚.特总结如下.(实验环境为10204)1. no_unnest, unnestunnest我们称为对子查询展开,顾名思义,就是别让子查询孤单 ...

  8. Spring Batch批处理以及编程模型

    1.批处理: 类似于SQL里面的批处理提交 2.场景: 业务定时进行批处理操作,但是批处理的编程模型是怎么的呢? 3.开源框架 Spring Batch 4.编程模型: reader-processo ...

  9. SPM——Using Maven+Junit to test Hello Wudi

    Last week, ours teacher taught us 'Software Delivery and Build Management'. And in this class, our t ...

  10. Jenkins邮件扩展(Email Extension插件 Windows环境)

    1.Jenkins ver. 2.107.3版本自带Email Extension插件启动后即可看到系统设置里的 Extended E-mail Notification ,如果没有请安装 2.安装过 ...