进程池,线程池
以时间换空间,控制进程,线程开启的数量
进程池与cpu一一对应是并行
线程池是并发:一个容器,这个容器限制住你开启线程(进程)的数量,比如4个,第一次肯定只能变更发的处理4个任务,只要有任务完成,线程马上就会接下一个人任务
以时间换空间,控制进程,线程开启的数量
进程池与cpu一一对应是(并行)或并行加并发
线程池是并发:一个容器,这个容器限制住你开启线程(进程)的数量,比如4个,第一次肯定只能变更发的处理4个任务,只要有任务完成,线程马上就会接下一个人任务
以时间换空间
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor def fun(): print(1) t=ThreadPoolExecutor()#实例化一个进程池对象 t.submit(fun)#用submit开启一个进程池
基于线程池的服务端
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor import socket import os import time import random def communicate(conn, addr): while 1: # # try: from_client_data = conn.recv(1024) print(f'来自客户端{addr[1]}的消息: {from_client_data.decode("utf-8")}') to_client_data = input('>>>').strip() conn.send(to_client_data.encode('utf-8')) if __name__ == '__main__': server = socket.socket() server.bind(('127.0.0.1', 8848)) server.listen(5) print(1) while 1: con, addr = server.accept() t=ThreadPoolExecutor() t.submit(communicate,con,addr)
基于进程的服务端
import socket from threading import Thread import time import random from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor from multiprocessing import Process def comniate(conn,addr): while 1: time.sleep(random.randint(1,3)) for_client_data=conn.recv(1024) print(f'客户端{addr[1]}消息{for_client_data.decode("utf-8")}') time.sleep(random.randint(3,4)) # to_client_data=input('>>>').strip()#不能使用input conn.send('121212'.encode('utf-8')) conn.close() def func(): server=socket.socket() server.bind(('127.0.0.1',8848)) server.listen(5) print('开启') while 1: conn,addr=server.accept() t.submit(comniate,conn,addr) if __name__ == '__main__': t = ProcessPoolExecutor(3) func()
阻塞非阻塞
阻塞,非阻塞程序两种运行状态
三种运行状态 运行,就绪,阻塞
遇到io操作就会发生阻塞,程序一旦遇到阻塞操作就会停在原地,并且会立刻释放cpu资源(cpu切换到其他进程中),结束进入就绪态
非阻塞:没有遇到io操作,但是我通过某种手段,让cpu强行运行我的程序
(或者用某种手段让程序即使遇到io操作也不会停在原地,执行其他操作,力求尽可能多的占有cpu)
同步与异步
obj=pool.submit()#obj是动态对象
打印obj出现状态
obj.result()等待这个任务结束,返回结果,在执行下一个任务(加上这个result会变成同步)#需要等到拿到返回结果
提交任务的两种方式
同步调用:
提交完任务后(任务执行不完会被cpu会切走),(不管有计算还是遇到io)就在原地等待,直到任务运行完毕后,拿到任务的返回值(不一定有),才继续执行下一行代码(同步调用和阻塞没有关系)
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor import time import random import os def task(i): print(f'{os.getpid()}开始任务') time.sleep(random.randint(1,3)) print(f'{os.getpid()}任务结束') return i if __name__ == '__main__': pool=ProcessPoolExecutor() for i in range(20): obj=pool.submit(task,i)#开启子进程 print(obj.result())#等待打印返回值#return 的返回值返回给了 result pool.shutdown(wait=True) print('===主')
多个任务,同步调用效率低
异步调用:
一次提交多个任务,然后直接执行下一行代码,
如何接收返回值
1.统一回收结果
假如异步发出10个任务,并发的执行,但是统一的接收所有的任务的返回值(效率低,不能实时的获取结果)
缺点:我不能马上收到任何一个已经完成的任务的返回值
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor import time import random import os def task(i): print(f'{os.getpid()}开始任务') time.sleep(random.randint(1,3)) print(f'{os.getpid()}任务结束') return i if __name__ == '__main__': pool=ProcessPoolExecutor() li=[] for i in range(20): obj=pool.submit(task,i) li.append(obj) # print(obj.result()) # 打印返回值 pool.shutdown(wait=True) for i in li: print(i.result()) print('===主')
2.完成一个任务接收一个返回结果(有问题)
import requests from concurrent.futures import ProcessPoolExecutor from concurrent.futures import ThreadPoolExecutor def task(url): ret=requests.get(url) if ret.status_code==200: return parse(ret.text) #parse(ret.text)#一个线程除了取数据还需要清洗数据 时间长 def parse(obj): return(len(obj)) #def task(url): #ret=requests.get(url) #if ret.status_code==200: #return parse(ret.text) #def parse(obj): #return(len(obj)) if __name__ == '__main__': pool=ThreadPoolExecutor(4) url_list=[ 'https://www.luffycity.com/', 'https://www.baidu.com/', 'https://www.luffycity.com/', 'https://www.luffycity.com/', 'https://www.luffycity.com/', 'https://www.luffycity.com/', 'https://www.luffycity.com/', ] li=[] for url in url_list: obj=pool.submit(task,url) li.append(obj) pool.shutdown(wait=True) for i in li: print(i.result())#统一接收结果 #两个函数耦合性太高
shutdown:
只有线程 进程池里面有
1.让我的主进程池中所有的子进程都结束任务之后,类似join,一个任务是通过一个函数实现的,任务完成了的返回值是函数的返回值
2.执行时不允许别人添加执行任务,接受返回值就证明任务结束了
浏览器的工作原理
向服务端发送一个请求,服务端验证你的请求,如果正确,给你的浏览器返回一个文件,将文件里面的代码渲染成你看到的漂亮美丽的模样.
爬虫
1.利用代码模拟一个浏览器,浏览器的工作流程
2.数据清洗(处理源代码)
对源代码进程数据 清晰得到我想要的数据
import requests response=requests.get('http://www.baidu.com')#拿到一个网址url if response.status_code==200: #200可以访问 print(response.text)#进行网页数据获取
异步+回调
运行完不等待返回值,返回值需统一回收,想要不统一,需要用到回调机制
进行
异步是io密集型,回调是计算密集型
异步统一回收,实时接收,回调接收处理结果
什么是回调函数?
按顺序接收每个任务的结果,进行下一步 处理.
import requests from concurrent.futures import ProcessPoolExecutor from concurrent.futures import ThreadPoolExecutor def task(url): '''模拟的就是爬取多个源代码 一定有IO操作''' ret=requests.get(url) if ret.status_code==200: return ret.text def parse(obj): '''模拟对数据进行分析 一般没有IO''' print(len(obj.result())) if __name__ == '__main__': pool=ThreadPoolExecutor(4) url_list=[ 'https://www.luffycity.com/', 'https://www.baidu.com/', 'https://www.luffycity.com/', 'https://www.luffycity.com/', 'https://www.luffycity.com/', 'https://www.luffycity.com/', 'https://www.luffycity.com/', ] for url in url_list: obj=pool.submit(task,url) obj.add_done_callback(parse)#回调函数 #线程池设置4个线程, 异步发起10个任务,每个任务是通过网页获取源码, 并发执行, # 当一个任务完成之后,将parse这个分析代码的任务交由剩余的空闲的线程去执行,你这个线程继续去处理其他任务. #进程池+回调是全部交由主进程进行运行 #线程池+回调是全部交由空闲线程运行
线程queue
十四 线程队列
线程之间的通信我们列表行不行呢,当然行,那么队列和列表有什么区别呢
queue队列 :使用import queue,用法与进程Queue一样
- class queue.Queue(maxsize=0) #先进先出
import queue #不需要通过threading模块里面导入,直接import queue就可以了,这是python自带的 #用法基本和我们进程multiprocess中的queue是一样的 q=queue.Queue() q.put('first') q.put('second') q.put('third') # q.put_nowait() #没有数据就报错,可以通过try来搞 print(q.get()) print(q.get()) print(q.get()) # q.get_nowait() #没有数据就报错,可以通过try来搞 ''' 结果(先进先出): first second third ''' 先进先出示例代码
class queue.LifoQueue(maxsize=0) #last in fisrt out
import queue q=queue.LifoQueue() #队列,类似于栈,栈我们提过吗,是不是先进后出的顺序啊 q.put('first') q.put('second') q.put('third') # q.put_nowait() print(q.get()) print(q.get()) print(q.get()) # q.get_nowait() ''' 结果(后进先出): third second first ''' 先进后出示例代码
class queue.PriorityQueue(maxsize=0) #存储数据时可设置优先级的队列
import queue q=queue.PriorityQueue() #put进入一个元组,元组的第一个元素是优先级(通常是数字,也可以是非数字之间的比较),数字越小优先级越高 q.put((-10,'a')) q.put((-5,'a')) #负数也可以 # q.put((20,'ws')) #如果两个值的优先级一样,那么按照后面的值的acsii码顺序来排序,如果字符串第一个数元素相同,比较第二个元素的acsii码顺序 # q.put((20,'wd')) # q.put((20,{'a':11})) #TypeError: unorderable types: dict() < dict() 不能是字典 # q.put((20,('w',1))) #优先级相同的两个数据,他们后面的值必须是相同的数据类型才能比较,可以是元祖,也是通过元素的ascii码顺序来排序 q.put((20,'b')) q.put((20,'a')) q.put((0,'b')) q.put((30,'c')) print(q.get()) print(q.get()) print(q.get()) print(q.get()) print(q.get()) print(q.get()) ''' 结果(数字越小优先级越高,优先级高的优先出队): ''' 优先级队列示例代码
这三种队列都是线程安全的,不会出现多个线程抢占同一个资源或数据的情况。
多线程抢占资源,串行
第一种 先进先出
q=queue.Queue(3) q.put(1) q.put(2) q.put(3) print(q.get()) print(q.get()) print(q.get(block=false)) q.get(timeout=2)
第二种后进先出 堆栈 lifo
第三种 优先级队列 数字越低 优先级越高
事件event
进程使用标志位FALG=FALSE
开启两个线程,一个线程运行到中间的某个阶段,触发另 个线程执行.两个线程增加了耦合性.
event.wait()#等待 event.set()#ture is_set==isSet#判断是否为ture 方法
多进程
from threading import Thread,current_thread,Event from multiprocessing import Process,queues,Event from concurrent.futures import ThreadPoolExecutor import time event=Event() def check(q): print('%s 正常检测服务是否正常。。。'% current_thread().name) time.sleep(1) q.set() def connect(q): num = 1 while not q.is_set(): if num==4: print('%s 连接失败' % current_thread().name) break else: print("%s 开始尝试连接%s次"%(current_thread().name,num)) q.wait(1) num+=1 else: print('%s 连接成功' % current_thread().name) if __name__ == '__main__': # q=queues.Queue() event=Event() t1=Process(target=connect,args=(event,)) t2=Process(target=connect,args=(event,)) t3=Process(target=connect,args=(event,)) T4=Process(target=check,args=(event,)) T4.start() t1.start() t2.start() t3.start()
多线程
from threading import Thread,current_thread,Event from multiprocessing import Process from concurrent.futures import ThreadPoolExecutor import time event=Event() def check(): print('%s 正常检测服务是否正常。。。'% current_thread().name) time.sleep(3) event.set() def connect(): num = 1 while not event.is_set(): if num==4: print('%s 连接失败' % current_thread().name) break else: print("%s 开始尝试连接%s次"%(current_thread().name,num)) event.wait(2) num+=1 else: print('%s 连接成功' % current_thread().name) if __name__ == '__main__': t1=Thread(target=connect) t2=Thread(target=connect) t3=Thread(target=connect) T4=Thread(target=check) T4.start() t1.start() t2.start() t3.start()
开启两个线程,一个线程允许到中间某个阶段,触发另一个线程执行
两个线程增加耦合性
协程(单线程下)
协程的本质就是在单线程下,由用户自己控制一个任务遇到io阻塞了就切换另外一个任务去执行,以此来提升效率。
python的线程属于内核级别的,即由操作系统控制调度(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其他线程运行)
什么是协程?
单个线程并发的处理多个任务. 程序控制协程的切换+保持状态.
单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率(!!!非io操作的切换与效率无关)
并发
如何实现并发 多线程 多进程 并发去执行,携程也是并发
并行
多个cpu执行多个任务,4个cpu执行4个任务 速度快开销大需要开启4个进程
串行
一个线程执行一个任务,执行完毕之后,执行下一个任务
并发的核心:
切换并且保持状态
多线程并发
3个线程处理10个任务
多线程遇到阻塞执行下一个线程 操作系统强行把cpu切到下一个线程(例如三个线程,一个线程阻塞,下一个线程调用cpu,)
多线程切换是cpu在不同线程下的切换,cpu只能运行线程(不能主动切换),线程是最小的执行单位
协程(单线程下并发的操作流程)
单个线程并发的处理多个任务,程序控制协程的切换+保持状态
协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度
一个线程运行多个任务(单线程的并发)
协程和cpu的关系
cpu一直运行线程,当遇到阻塞,协程调用cpu执行下一个任务遇到,阻塞切换到下一个任务(遇到阻塞切换是线程在三个任务之间来回切换,(多线程是指cpu在不同线程之间切换),cpu一直运行线程,线程没有停
利用多线程io的阻塞时间 进行下一个线程的操作
10个任务,让你给我并发10个任务
协程最好
前提一个cpu
1.开启多进程(有主线程)并发执行。--操作系统--时刻监视cpu(cpu停了)遇到阻塞或者运行时间过长就把cpu切走)切换+保持状态 2.开启多线程并发执行 --操作系统--时刻监视cpu)遇到阻塞(同时阻塞)或者运行时间过长(操作系统)切换+保持状态 3.(一个线程并发执行任务)开启协程并发执行,自己的程序(代码)绑定cpu 让cpu一直工作,把控着cpu在3个任务之间来回切换+保持状态 对3详细解释:协程他切换速度非常快,蒙蔽操作系统的眼睛,让操作系统认为cpu一直在运行你这一个线程(协程)
协程的优点:
1.运行速度快,单线程内就可以实现并发效果
2.协程会长期霸占cpu只执行我的程序效率高
3.携程开销小,
4.单线程并发最大限度利用cpu
缺点
协程单线程本质上是单线程下的无法利用多核
(一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程)
协程指的是单个线程,一旦出现阻塞,将会阻塞整个线程 cpu被操作系统切走,整个线程结束
总结协程特点:
1.必须在只有一个单线程里实现并发
2.修改共享数据不需加锁(因为是一个线程,多线程需要加锁,多个任务修改数据共享 )
3.用户程序里自己保存多个控制流的上下文栈(保持状态)
4一个协程遇到io操作自动切换到其他协程
并发的本质:切换+保持状态
协程处理io密集型,计算密集型,还剩串行好
携程一直阻塞会来回切 整个线程就会结束 ,操作系统会切走cpu,结束了
greenlet
模块里 有一个机制强行不让cpu切换
任务之间来回切 切换加保存状态 遇到io不会切换需要等
g2.switch()#切换保存状态
from greenlet import greenlet def eat(name): print('%s eat 1' %name) #2 g2.switch('taibai') #3 print('%s eat 2' %name) #6 g2.switch() #7 def play(name): print('%s play 1' %name) #4 g1.switch() #5 print('%s play 2' %name) #8 g1=greenlet(eat) g2=greenlet(play) g1.switch('taibai')
Gevent
模块里 有一个机制强行不让cpu切换
spawn(cls, *args, **kwargs) #传参依次是(函数,位置参数,默认参数) g1=gevent.spawn(eat,'eg'(位置传参))#前面一个是函数,第二个参数是位置传参 g2=gevent.spawn(play,name='on'(默认传参))
import gevent#引用一个第三方模块 # from multiprocessing import Process def eat(name): print('%s eat 1' %name) gevent.sleep(2)#这个阻塞是gevent的阻塞 print('%s eat 2' %name) def play(name): print('%s play 1' %name) gevent.sleep(1) print('%s play 2' %name) g1=gevent.spawn(eat,'eg')#前面一个是函数,第二个参数是位置传参 g2=gevent.spawn(play,name='on') ''' 为什么加join就运行了 一个线程相当于虚拟了两个线程 因为主线程结束了 join限制主线程不结束 ''' g1.join() g2.join() #或者gevent.joinall([g1,g2]) print('主')
最终版本把下面所有任务的阻塞都打上标记
import gevent from gevent import monkey monkey.patch_all() # 一定要写到最上面打补丁: 将下面的所有的任务的阻塞都打上标记 def eat(name): print('%s eat 1' %name) time.sleep(2)#阻塞切换 print('%s eat 2' %name) def play(name): print('%s play 1' %name) time.sleep(1)#阻塞一秒 print('%s play 2' %name) g1 = gevent.spawn(eat,'egon') g2 = gevent.spawn(play,name='egon') # g1.join() # g2.join() gevent.joinall([g1,g2]) 协程下面的两个任务阻塞,切换一两次就被操作系统讲cpu拿走了,当一个任务一个任务最先阻塞完毕之后,协程就会将cpu,抢回来执行