今日内容
思考题:
并发部分的最后一题
270w条数据
多线程 给每100行创建一个任务
对这100行进行分析
思路:多线程 + 协程
1 协程:
概念:多个任务在线程里来回切换这就是协程,
为什么说协程安全:
协程是不能够被操作系统感知的,所以在协程中数据的代码运行都是一块执行完整的,就不会发生不安全问题
2 作用:
解决高io的并发的编程,当cpu给线程的时间片,线程遇到阻塞就会切出去,而协程就解决了这个问题,让多个任务在一条线程来回切换,
让操作系统看起来很繁忙,而不去切换线程.提高了效率
在软件与硬件中是操作系统聊感知的,而python 解释器不能够知道硬件比如键盘的输入
3 为什么要有协程
1:能够让线程看起来很忙碌,就会骗过操作系统,让你的系统不进入阻塞队列
2:线程中也有三个状态,就绪---运行---阻塞 线程之间遇到io 就会被操作系统切换,进入阻塞状态,就降低了效率
3:有了协程就不用去做线程之间的切换了,减轻了操作系统的负担
4:就在就绪队列与运作之间来回
5:针对线程来说,协程数据是安全的,因为它没有被操作系统直接切换
注意 Cpython解释器
高计算型 开多进程
高IO型 开多线程
进程 线程 协程
进程:
是操作系统分配资源的最小单位
就是一程序正在执行的状态,实例 支持多核 开销大 数据隔离(数据安全) 建议开cpU 的一到俩倍
线程:
是操作系统调度的最小单位
依赖于进程,轻型的进程,开销小,数据共享, 一条进程大约开20条线程
应用与 网络编程 爬虫
协程 + 进程 爬虫 网站开发
线程 : 针对于文件管理 处理文件操作(处理日志文件) 处理网络并发(请求爬虫的页面)
进程 : 针对于 高精度算法
在cpyhon解释器下 线程 协程 都不支持 多核
关于协程的开启: 忘记了
依赖于第三方库 clenvet ---(yiled)--> envent
gevent :使用的是 greenlet 的切换机制,实现了IO的自动化的切换 稍微比yiled慢一点
asyncio: 使用的yield 关键字 实现了规避IO的操作 稍微快一点
进程 线程 协程 代码的区别
进程: 主进程是等待其他进程结束才结束的,不过主进程代码可能在子进程前结束所以得加join在子进程结束后在运行
线程:是不加join 应为主线程 结束需要所有的子线程运行结束了 它才结束
协程: 需要加join 的 得在线程中阻塞一下 ,才去执行 协程的任务
协程的原理 为什么要有协程:
使用协程 是为了 规避io
遇到io切换
切换机制:greenlet yiled
针对数据不安全的讲解
线程中的多个任务,其中的每一个任务都可以成为一个协程(感觉太准确,得几个任务来回切换,并且针对于io阻塞)
我们在线程的内部 又出现了一个新的单位 协程
能够放多个任务在一个线程中
每一个协程都可以在一条线程中任意的切换
线程 : cpython解释器 只能用一核 只有有IO操作的多个任务才适合起多线程
协程 : 在一个线程内能够放多个任务,并且这多个任务的执行状态还可以切换
20个任务 都是访问网页的爬虫任务
起20个线程,每个线程执行一个任务 并发效果
操作系统来做的
20个任务 5个线程
每个线程 执行4个任务
也不是协程
什么是协程呢?
在一条线程的基础上多个任务能够来回切换 这就是最基础协程
是不是所有放在线程内的任务都放在协程里就好了?
不是因为协程解决的是io操作
是不是就不需要有多线程了,直接协程就行了?
不是,线程是操作系统能够感知,调度的最小单位,操作系统对于io操作的识别度比程序还要高,比如键盘的输入协程是感知不到的
首先 如果对于所有的IO操作都能够做到协程 那么在Cpython解释器下 线程就没有意义了
但是 操作系统对于io操作的识别度 还是比 程序要高
操作系统是离硬件最近的
io操作 : 读写文件,网络操作,input,sleep
sleep : 时间 python能获取时间
input : 键盘 python程序中能够明确键盘操作
文件 : 硬件 操作系统是否提供了你一个工具,能够让你感知到某一个文件中的数据变化
网络 : 网卡 python能不能让我感知到网络上有数据来了
IO多路复用 操作系统提供给你的
我们能够从程序级别感知到的io操作很有限
网络
时间
总结:
在其他编译型语言中 由于多线程可以利用多核 所以协程这个概念被弱化了
对于Cpython 多线程也不能利用多核 所以协程这个概念就变得至关重要了
协程的本质是一条线程
1.不能利用多核
2.用户级的概念,操作系统不可见
3.协程不存在数据安全问题
切换 一条线程
操作系统感知到线程一直在运行 就减少了线程进入阻塞状态的次数,从而提高了效率
协程实际的定义 : 在一条线程之间来回切换
1.yield和函数作比较 yield进行切换实际上会浪费时间
即便是程序级别的切换也会浪费时间
2.500个client并发的server的效率计算
协程的处理并发能力 很强
3.geturl爬虫的练习,协程和函数做对比
协程的速度快 gevent去处理问题实际上很简便 直接扔给spawn就行了
进程 cpu个数的1-2倍 对于其他的语言 来说 进程是不常开的
线程
协程 500并发
爬虫
get url
数据分析 利用多核
进程 + 协程
开5个进程,在进程中开协程 既能够利用多核 也可以提高处理IO的效率
线程
处理 文件 ,input
8Cpu
进程 8个进程
协程 1000个
4*500 = 8000并发
# 线程池
from concurrent.futures import ThreadPoolExecutor as Executor,ProcessPoolExecutor #as Executor 线程与进程之间来回灵活的切换
def func(i):
print('66',i)
# return '*'*i
if __name__ == '__main__':
t = Executor(4)
#map函数型
# ret = map(func,range(5))
# for i in ret:
# print(i)#----->获取返回值
# map 不需要result() 来获取结果
#for 循环型
lis = []
for i in range(5):
ret = t.submit(func,i)# --->func 直接运行吗
lis.append(ret)
t.shutdown() #用shutdown 主线程就会等线程执行完了在执行
for i in lis:
print(i.result()) #获取 func 函数返回值
print('主线程')
#回调函数
import time
import random
from concurrent.futures import ThreadPoolExecutor
def back(ret):
print(ret)#打印的是对象
print(ret.result()) #打印的是结果son_func的reurn i+i
def son_func():
time.sleep(random.random())
return i+i
p = ThreadPoolExecutor(4)
for i in range(6): #线程池提交6个任务,分别执行son_func函数
ret = p.submit(son_func,i)#将son_func的返回值 作为回调函数 赋值给ret
ret.add_done_callback(back)#回调back 把ret 对象传回去 ---.>back(ret)
##############线程池进程池的爬虫
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
from multiprocessing import Pool
import requests
import json
import os
def get_page(url):
print('<进程%s> get %s' %(os.getpid(),url))
respone=requests.get(url)
if respone.status_code == 200:
return {'url':url,'text':respone.text}
def parse_page(res):
res=res.result()
print('<进程%s> parse %s' %(os.getpid(),res['url']))
parse_res='url:<%s> size:[%s]
' %(res['url'],len(res['text']))
with open('db.txt','a') as f:
f.write(parse_res)
if __name__ == '__main__':
urls=[
'https://www.baidu.com',
'https://www.python.org',
'https://www.openstack.org',
'https://help.github.com/',
'http://www.sina.com.cn/'
]
# p=Pool(3)
# for url in urls:
# p.apply_async(get_page,args=(url,),callback=pasrse_page)
# p.close()
# p.join()
p=ProcessPoolExecutor(3)
for url in urls:
# parse_page拿到的是一个future对象obj,需要用obj.result()拿到结果
p.submit(get_page,url).add_done_callback(parse_page)
# /协程代码: 生成器本身就是一个协程
# 基于函数版的 yiled 协程
def consumer():
while 1:
goods = yield
print(goods)
def producer(): #生产者
c = consumer()
next(c)
for i in range(10):
c.send('苹果%s'%i)
producer()
#####基于协程的socket server
from gevent import monkey
monkey.patch_all()
import socket
import gevent
server = socket.socket()
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
server.bind(('127.0.0.1',8888))
server.listen(5)
def func(conn):
msg = conn.send(b'qwer')
conn.recv(1024).decode('utf8')
print(msg)
while 1:
conn,_ = server.accept()
g =gevent.spawn(func,conn)
#######一条线程的多协程运用
from threading import currentThread
from gevent import monkey
monkey.patch_all()
import time
import gevent #内部使用了 greenlet 的切换机制 实现了io自动切换
def eat():
print('start eating ',currentThread())
time.sleep(1)
print('eating finished')
def sleep():
print('start sleeping ',currentThread())
time.sleep(1)
print('sleeping finshed')
l =[]
for i in range(5):
g1 =gevent.spawn(eat)
g2 =gevent.spawn(sleep)
l.append(g1,)
l.append(g2,)
gevent.joinall(l)
from gevent import monkey
monkey.patch_all()
import time
import gevent
def eat():
print('start eating ')
time.sleep(1)
print('eating finished ')
def sleep():
print('starr sleeping')
time.sleep(1)
print('sleeping finished')
g1 = gevent.spawn(eat)
g2 = gevent.spawn(sleep)
g1.join()
g2.join()