需要注意一下
不能无限的开进程,不能无限的开线程
最常用的就是开进程池,开线程池。其中回调函数非常重要
回调函数其实可以作为一种编程思想,谁好了谁就去掉 只要你用并发,就会有锁的问题,但是你不能一直去自己加锁吧
那么我们就用QUEUE,这样还解决了自动加锁的问题
由Queue延伸出的一个点也非常重要的概念。以后写程序也会用到
这个思想。就是生产者与消费者问题

一、Python标准模块--concurrent.futures(并发未来)

concurent.future模块需要了解的
1.concurent.future模块是用来创建并行的任务,提供了更高级别的接口,
为了异步执行调用
2.concurent.future这个模块用起来非常方便,它的接口也封装的非常简单
3.concurent.future模块既可以实现进程池,也可以实现线程池
4.模块导入进程池和线程池
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
还可以导入一个Executor,但是你别这样导,这个类是一个抽象类
抽象类的目的是规范他的子类必须有某种方法(并且抽象类的方法必须实现),但是抽象类不能被实例化
5.
p = ProcessPoolExecutor(max_works)对于进程池如果不写max_works:默认的是cpu的数目,默认是4个
p = ThreadPoolExecutor(max_works)对于线程池如果不写max_works:默认的是cpu的数目*5
6.如果是进程池,得到的结果如果是一个对象。我们得用一个.get()方法得到结果
但是现在用了concurent.future模块,我们可以用obj.result方法
p.submit(task,i) #相当于apply_async异步方法
p.shutdown() #默认有个参数wite=True (相当于close和join)

那么什么是线程池呢?我们来了解一下

二、线程池

进程池:就是在一个进程内控制一定个数的线程
基于concurent.future模块的进程池和线程池 (他们的同步执行和异步执行是一样的)
 # 1.同步执行--------------
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
import os,time,random
def task(n):
print('[%s] is running'%os.getpid())
time.sleep(random.randint(1,3)) #I/O密集型的,,一般用线程,用了进程耗时长
return n**2
if __name__ == '__main__':
start = time.time()
p = ProcessPoolExecutor()
for i in range(10): #现在是开了10个任务, 那么如果是上百个任务呢,就不能无线的开进程,那么就得考虑控制
# 线程数了,那么就得考虑到池了
obj = p.submit(task,i).result() #相当于apply同步方法
p.shutdown() #相当于close和join方法
print('='*30)
print(time.time() - start) #17.36499309539795 # 2.异步执行-----------
# from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
# import os,time,random
# def task(n):
# print('[%s] is running'%os.getpid())
# time.sleep(random.randint(1,3)) #I/O密集型的,,一般用线程,用了进程耗时长
# return n**2
# if __name__ == '__main__':
# start = time.time()
# p = ProcessPoolExecutor()
# l = []
# for i in range(10): #现在是开了10个任务, 那么如果是上百个任务呢,就不能无线的开进程,那么就得考虑控制
# # 线程数了,那么就得考虑到池了
# obj = p.submit(task,i) #相当于apply_async()异步方法
# l.append(obj)
# p.shutdown() #相当于close和join方法
# print('='*30)
# print([obj.result() for obj in l])
# print(time.time() - start) #5.362306594848633 基于concurrent.futures模块的进程池
 from  concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
from threading import currentThread
import os,time,random
def task(n):
print('%s:%s is running'%(currentThread().getName(),os.getpid())) #看到的pid都是一样的,因为线程是共享了一个进程
time.sleep(random.randint(1,3)) #I/O密集型的,,一般用线程,用了进程耗时长
return n**2
if __name__ == '__main__':
start = time.time()
p = ThreadPoolExecutor() #线程池 #如果不给定值,默认cup*5
l = []
for i in range(10): #10个任务 # 线程池效率高了
obj = p.submit(task,i) #相当于apply_async异步方法
l.append(obj)
p.shutdown() #默认有个参数wite=True (相当于close和join)
print('='*30)
print([obj.result() for obj in l])
print(time.time() - start) #3.001171827316284 基于concurrent.futures模块的线程池

应用线程池(下载网页并解析)

 from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
import requests
import time,os
def get_page(url):
print('<%s> is getting [%s]'%(os.getpid(),url))
response = requests.get(url)
if response.status_code==200: #200代表状态:下载成功了
return {'url':url,'text':response.text}
def parse_page(res):
res = res.result()
print('<%s> is getting [%s]'%(os.getpid(),res['url']))
with open('db.txt','a') as f:
parse_res = 'url:%s size:%s\n'%(res['url'],len(res['text']))
f.write(parse_res)
if __name__ == '__main__':
# p = ThreadPoolExecutor()
p = ProcessPoolExecutor()
l = [
'http://www.baidu.com',
'http://www.baidu.com',
'http://www.baidu.com',
'http://www.baidu.com',
]
for url in l:
res = p.submit(get_page,url).add_done_callback(parse_page) #这里的回调函数拿到的是一个对象。得
# 先把返回的res得到一个结果。即在前面加上一个res.result() #谁好了谁去掉回调函数
# 回调函数也是一种编程思想。不仅开线程池用,开线程池也用
p.shutdown() #相当于进程池里的close和join
print('主',os.getpid())

map函数的应用

# map函数举例
obj= map(lambda x:x**2 ,range(10))
print(list(obj)) #运行结果[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
可以和上面的开进程池/线程池的对比着看,就能发现map函数的强大了
 # 我们的那个p.submit(task,i)和map函数的原理类似。我们就
# 可以用map函数去代替。更减缩了代码
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
import os,time,random
def task(n):
print('[%s] is running'%os.getpid())
time.sleep(random.randint(1,3)) #I/O密集型的,,一般用线程,用了进程耗时长
return n**2
if __name__ == '__main__':
p = ProcessPoolExecutor()
obj = p.map(task,range(10))
p.shutdown() #相当于close和join方法
print('='*30)
print(obj) #返回的是一个迭代器
print(list(obj)) map函数应用

三、协程介绍

协程:单线程下实现并发(提高效率)

说到协成,我们先说一下协程联想到的知识点

1
2
3
4
5
6
7
8
9
10
11
12
13
切换关键的一点是:保存状态(从原来停留的地方继续切)
return:只能执行一次,结束函数的标志
yield:函数中但凡有yield,这个函数的执行结果就变成了一个生成器,
生成器本质就是一个迭代器,那么迭代器怎么用呢?用一个next()方法
 
 
1.yield语句的形式:yield 1
yield功能1:可以用来返回值,可以返回多次值
yield功能2:可以吧函数暂停住,保存原来的状态
 
2.yield表达式的形式:x = yieldsend可以吧一个函数的结果传给另一个函数,以此实现单线程内程序之间的切换
send()要想用就得先next()一下
但是要用send至少要用两个yield  

yield复习

 3.yield功能2(可以吧函数暂停住,保存原来的状态)--------------
def f1():
print('first')
yield 1
print('second')
yield 2
print('third')
yield 3
# print(f1()) #加了yield返回的是一个生成器
g = f1()
print(next(g)) #当遇见了yield的时候就返回一个值,而且保存原来的状态
print(next(g)) #当遇见了yield的时候就返回一个值
print(next(g)) #当遇见了yield的时候就返回一个值 yield功能示例2
 # 3.yield表达式(对于表达式的yield)--------------------
import time
def wrapper(func):
def inner(*args,**kwargs):
ret =func(*args,**kwargs)
next(ret)
return ret
return inner
@wrapper
def consumer():
while True:
x= yield
print(x) def producter(target):
'''生产者造值'''
# next(g) #相当于g.send(None)
for i in range(10):
time.sleep(0.5)
target.send(i)#要用send就得用两个yield
producter(consumer())

引子

本节主题是实现单线程下的并发,即只在一个主线程,并且很明显的是,可利用的cpu只有一个情况下实现并发,

为此我们需要先回顾下并发的本质:切换+保存状态

cpu正在运行一个任务,会在两种情况下切走去执行其他的任务(切换由操作系统强制控制),

一种情况是该任务发生了阻塞,另外一种情况是该任务计算的时间过长

其中第二种情况并不能提升效率,只是为了让cpu能够雨露均沾,实现看起来大家都被执行的效果,如果多个程序都是纯计算任务,这种切换反而会降低效率。为此我们可以基于yield来验证。yield本身就是一种在单线程下可以保存任务运行状态的方法,我们来简单复习一下:

1
2
1 yiled可以保存状态,yield的状态保存与操作系统的保存线程状态很像,但是yield是代码级别控制的,更轻量级
2 send可以把一个函数的结果传给另外一个函数,以此实现单线程内程序之间的切换

单纯的切反而会影响效率

 #串行执行
import time
def consumer(res):
'''任务1:接收数据,处理数据'''
pass def producer():
'''任务2:生产数据'''
res=[]
for i in range(10000000):
res.append(i)
return res start=time.time()
#串行执行
res=producer()
consumer(res)
stop=time.time()
print(stop-start) #1.5536692142486572 串行执行
 import time
def wrapper(func):
def inner(*args,**kwargs):
ret =func(*args,**kwargs)
next(ret)
return ret
return inner
@wrapper
def consumer():
while True:
x= yield
print(x) def producter(target):
'''生产者造值'''
# next(g) #相当于g.send(None)
for i in range(10):
time.sleep(0.5)
target.send(i)#要用send就得用两个yield
producter(consumer()) 基于yield并发执行

对于单线程下,我们不可避免程序中出现io操作,但如果我们能在自己的程序中(即用户程序级别,而非操作系统级别)控制单线程下多个任务能遇到io就切换,这样就保证了该线程能够最大限度地处于就绪态,即随时都可以被cpu执行的状态,相当于我们在用户程序级别将自己的io操作最大限度地隐藏起来,对于操作系统来说:这哥们(该线程)好像是一直处于计算过程的,io比较少。

协程的本质就是在单线程下,由用户自己控制一个任务遇到io阻塞了就切换另外一个任务去执行,以此来提升效率。

因此我们需要找寻一种可以同时满足以下条件的解决方案:

1. 可以控制多个任务之间的切换,切换之前将任务的状态保存下来(重新运行时,可以基于暂停的位置继续)

2. 作为1的补充:可以检测io操作,在遇到io操作的情况下才发生切换

四、Greenlet

Greenlet模块和yield没有什么区别,就只是单纯的切,跟效率无关。

只不过比yield更好一点,切的时候方便一点。但是仍然没有解决效率

Greenlet可以让你在多个任务之间来回的切

1
2
#安装
pip3 install greenlet

举例:

 from greenlet import greenlet
import time
def eat(name):
print('%s eat 1' %name)
time.sleep(10) #当遇到IO的时候它也没有切,这就得用gevent了
g2.switch('egon')
print('%s eat 2' %name)
g2.switch()
def play(name):
print('%s play 1' %name)
g1.switch()
print('%s play 2' %name) g1=greenlet(eat)
g2=greenlet(play) g1.switch('egon')#可以在第一次switch时传入参数,以后都不需要 greenlet

所以上面的方法都不可行,那么这就用到了Gevert ,也就是协程。就解决了单线程实现并发的问题,还提升了效率

五、Gevent介绍

1
2
#安装
pip3 install gevent

Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet,

它是以C扩展模块形式接入Python的轻量级协程。 Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。

 #用法
g1=gevent.spawn(func,1,,2,3,x=4,y=5)创建一个协程对象g1,spawn括号内第一个参数是函数名,如eat,后面可以有多个参数,可以是位置实参或关键字实参,都是传给函数eat的 g2=gevent.spawn(func2) g1.join() #等待g1结束 g2.join() #等待g2结束 #或者上述两步合作一步:gevent.joinall([g1,g2]) g1.value#拿到func1的返回值

举例;

 from gevent import monkey;monkey.patch_all()
import gevent
import time
def eat(name):
print('%s eat 1' %name)
time.sleep(2) #我们用等待的时间模拟IO阻塞
''' 在gevent模块里面要用gevent.sleep(2)表示等待的时间
然而我们经常用time.sleep()用习惯了,那么有些人就想着
可以用time.sleep(),那么也不是不可以。要想用,就得在
最上面导入from gevent import monkey;monkey.patch_all()这句话
如果不导入直接用time.sleep(),就实现不了单线程并发的效果了
'''
# gevent.sleep(2)
print('%s eat 2' %name)
return 'eat'
def play(name):
print('%s play 1' %name)
time.sleep(3)
# gevent.sleep(3)
print('%s play 2' %name)
return 'paly' #当有返回值的时候,gevent模块也提供了返回结果的操作 start = time.time()
g1 = gevent.spawn(eat,'egon') #执行任务
g2 = gevent.spawn(play,'egon') #g1和g2的参数可以不一样
# g1.join() #等待g1
# g2.join() #等待g2
#上面等待的两句也可以这样写
gevent.joinall([g1,g2])
print('主',time.time()-start) #3.001171588897705 print(g1.value)
print(g2.value) gevent的一些方法(重要)

需要说明的是:

gevent.sleep(2)模拟的是gevent可以识别的io阻塞,

而time.sleep(2)或其他的阻塞,gevent是不能直接识别的需要用下面一行代码,打补丁,就可以识别了

from gevent import monkey;monkey.patch_all()必须放到被打补丁者的前面,如time,socket模块之前

或者我们干脆记忆成:要用gevent,需要将from gevent import monkey;monkey.patch_all()放到文件的开头

六、Gevent之同步于异步

 from gevent import spawn,joinall,monkey;monkey.patch_all()

 import time
def task(pid):
"""
Some non-deterministic task
"""
time.sleep(0.5)
print('Task %s done' % pid) def synchronous():
for i in range(10):
task(i) def asynchronous():
g_l=[spawn(task,i) for i in range(10)]
joinall(g_l) if __name__ == '__main__':
print('Synchronous:')
synchronous() print('Asynchronous:')
asynchronous()
#上面程序的重要部分是将task函数封装到Greenlet内部线程的gevent.spawn。 初始化的greenlet列表存放在数组threads中,此数组被传给gevent.joinall 函数,后者阻塞当前流程,并执行所有给定的greenlet。执行流程只会在 所有greenlet执行完后才会继续向下走。

七、Gevent之应用举例一

 from gevent import monkey;monkey.patch_all()  #打补丁
import gevent
import requests
import time
def get_page(url):
print('get :%s'%url)
response = requests.get(url)
if response.status_code==200: #下载成功的状态
print('%d bytes received from:%s'%(len(response.text),url))
start=time.time()
gevent.joinall([
gevent.spawn(get_page,'http://www.baidu.com'),
gevent.spawn(get_page, 'https://www.yahoo.com/'),
gevent.spawn(get_page, 'https://github.com/'),
])
stop = time.time()
print('run time is %s' %(stop-start)) 协程应用爬虫
 from gevent import joinall,spawn,monkey;monkey.patch_all()
import requests
from threading import current_thread def parse_page(res):
print('%s PARSE %s' %(current_thread().getName(),len(res))) def get_page(url,callback=parse_page):
print('%s GET %s' %(current_thread().getName(),url))
response=requests.get(url)
if response.status_code == 200:
callback(response.text) if __name__ == '__main__':
urls=[
'https://www.baidu.com',
'https://www.taobao.com',
'https://www.openstack.org',
] tasks=[]
for url in urls:
tasks.append(spawn(get_page,url)) joinall(tasks) 协程应用爬虫加了回调函数的

八、Gevent之应用举例二

也可以利用协程实现并发

 #!usr/bin/env python
# -*- coding:utf-8 -*-
from gevent import monkey;monkey.patch_all()
import gevent
from socket import *
print('start running...')
def talk(conn,addr):
while True:
data = conn.recv(1024)
print('%s:%s %s'%(addr[0],addr[1],data))
conn.send(data.upper())
conn.close()
def server(ip,duankou):
server = socket(AF_INET, SOCK_STREAM)
server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
server.bind((ip,duankou))
server.listen(5)
while True:
conn,addr = server.accept() #等待链接
gevent.spawn(talk,conn,addr) #异步执行 (p =Process(target=talk,args=(coon,addr))
# p.start())相当于开进程里的这两句
server.close()
if __name__ == '__main__':
server('127.0.0.1',8081) 服务端利用协程
 #!usr/bin/env python
# -*- coding:utf-8 -*-
from multiprocessing import Process
from gevent import monkey;monkey.patch_all()
from socket import *
def client(ip,duankou):
client = socket(AF_INET, SOCK_STREAM)
client.connect((ip,duankou))
while True:
client.send('hello'.encode('utf-8'))
data = client.recv(1024)
print(data.decode('utf-8'))
if __name__ == '__main__':
for i in range(100):
p = Process(target=client,args=(('127.0.0.1',8081)))
p.start() 客户端开了100个进程

python并发编程之进程池、线程池、协程的更多相关文章

  1. python并发编程之进程、线程、协程的调度原理(六)

    进程.线程和协程的调度和运行原理总结. 系列文章 python并发编程之threading线程(一) python并发编程之multiprocessing进程(二) python并发编程之asynci ...

  2. python中socket、进程、线程、协程、池的创建方式和应用场景

    进程 场景 利用多核.高计算型的程序.启动数量有限 进程是计算机中最小的资源分配单位 进程和线程是包含关系 每个进程中都至少有一条线程 可以利用多核,数据隔离 创建 销毁 切换 时间开销都比较大 随着 ...

  3. Python学习之路--进程,线程,协程

    进程.与线程区别 cpu运行原理 python GIL全局解释器锁 线程 语法 join 线程锁之Lock\Rlock\信号量 将线程变为守护进程 Event事件 queue队列 生产者消费者模型 Q ...

  4. Python并发编程之从生成器使用入门协程(七)

    大家好,并发编程 进入第七篇. 从今天开始,我们将开始进入Python的难点,那就是协程. 为了写明白协程的知识点,我查阅了网上的很多相关资料.发现很难有一个讲得系统,讲得全面的文章,导致我们在学习的 ...

  5. Python笔记_第四篇_高阶编程_进程、线程、协程_1.进程

    1. 多任务原理: 现代操作系统,像win,max os x,linux,unix等都支持多任务. * 什么叫做多任务? 操作系统可以同时运行多个任务. * 单核CPU实现多任务原理? 操作系统轮流让 ...

  6. Python笔记_第四篇_高阶编程_进程、线程、协程_5.GPU加速

    Numba:高性能计算的高生产率 在这篇文章中,笔者将向你介绍一个来自Anaconda的Python编译器Numba,它可以在CUDA-capable GPU或多核cpu上编译Python代码.Pyt ...

  7. Python笔记_第四篇_高阶编程_进程、线程、协程_2.线程

    1. 线程概述: 在一个进程的内部,要同时干多件事情,就需要同时运行“多个子任务”,我们把进程内的这些“子任务”叫做线程.也就说线程是进程成的子任务. 线程通常叫做情景的进程.线程是通过向内侧控件的并 ...

  8. Python笔记_第四篇_高阶编程_进程、线程、协程_4.协程

    1.协程的概念: 子程序或者子函数,在所有语言中都是层级调用,比如A调用B,再B执行的过程中又可以调用C,C执行完毕返回,B执行返回,最后是A执行完毕返回.是通过栈来实现的,一个线程就是执行一个自称, ...

  9. Python(八)进程、线程、协程篇

    本章内容: 线程(线程锁.threading.Event.queue 队列.生产者消费者模型.自定义线程池) 进程(数据共享.进程池) 协程 线程 Threading用于提供线程相关的操作.线程是应用 ...

  10. Python之路,Day9, 进程、线程、协程篇

    本节内容 操作系统发展史介绍 进程.与线程区别 python GIL全局解释器锁 线程 语法 join 线程锁之Lock\Rlock\信号量 将线程变为守护进程 Event事件 queue队列 生产者 ...

随机推荐

  1. LAMP 系统性能调优之网络文件系统调优

    LAMP 系统性能调优之网络文件系统调优 2011-03-21 09:35 Sean A. Walberg 网络转载 字号:T | T 使用LAMP系统的用户,都想把自己LAMP性能提高运行的速度提高 ...

  2. 【BZOJ 3681】Arietta

    传送门 题目描述 Arietta 的命运与她的妹妹不同,在她的妹妹已经走进学院的时候,她仍然留在山村中. 但是她从未停止过和恋人 Velding 的书信往来.一天,她准备去探访他. 对着窗外的阳光,临 ...

  3. JAVA笔记23-IO流(1)

    一.流的概念和分类 JAVA程序中,对于数据的输入.输出操作以“流”(stream)方式进行:J2SDK提供了各种各样的“流”类,用以获取不同种类的数据:程序中通过标准的方法输入或输出数据. java ...

  4. 在线PDF的实现 纯js

    /// <summary> /// 使用微软的TransmitFile下载文件 /// </summary> /// <param name="filePath ...

  5. linux的字符集转换

    命令查看编码类型 查看命令参数 查看支持的字符集 将文件转换成utf-8 的文件(经测试比较鸡肋,不好用) [root@ag-1 hh]# iconv oldboy -f us-ascii -t ut ...

  6. UVa 213 信息解码 (模拟 && 二进制)

    题意 :对于下面这个字符串 0,00,01,10,000,001,010,011……. 首先是长度为1的串,然后是长度为2的串,以此类推.不存在全为1的串. 你的任务是编写一个程序.首先输入一个代码头 ...

  7. POJ 3260 The Fewest Coins(完全背包+多重背包=混合背包)

    题目代号:POJ 3260 题目链接:http://poj.org/problem?id=3260 The Fewest Coins Time Limit: 2000MS Memory Limit: ...

  8. css使用2

    一.盒子模型 盒子模型 margin:用来调节盒子与盒子之间的距离(标签与标签之间距离) border:盒子的包装厚度(边框) padding:内部物体与盒子之间距离(文本与边框之间的距离) cont ...

  9. nginx报错 nginx: [alert] kill(25903, 1) failed (3: No such process)

    当nginx 中报错 时 nginx报错 nginx: [alert] kill(25903, 1) failed (3: No such process) 通过在nginx/sbin,目录下 运行命 ...

  10. Linux基本命令使用(一)

    1.head -n 文件    可以查看文件前n行 tail -n 文件        可以查看文件的后n行 tail -f  文件      可以实时查看文件,比如日志在更新,就可以实时显示最后几行 ...