1同步异步,阻塞与非阻塞
线程的三种状态:
1.就绪
2.运行
3.阻塞
#1.1、阻塞与非阻塞指的是程序的两种运行状态
# 阻塞:遇到IO就发生阻塞,程序一旦遇到阻塞操作就会停在原地,并且立刻释放CPU资源
# 非阻塞(就绪态或运行态):没有遇到IO操作,或者通过某种手段让程序即便是遇到IO操作也不会停在原地,执行其他操作,力求尽可能多的占有CPU
#1.2、同步与异步指的是提交任务的两种方式:
# 同步调用:提交完任务后,就在原地等待,直到任务运行完毕后,拿到任务的返回值,才继续执行下一行代码
比如掉用一个普通的函数,然后拿到这个函数的return的返回值再进行下一步操作.这属于同步调用
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
import time,os,random
def task(x):
print('%s 接客' %x)
time.sleep(random.randint(1,3))
return x**2
if __name__ == '__main__':
# 异步调用
# p=ThreadPoolExecutor(4) # 默认开启的线程数是cpu的核数*5
#
# # alex,武佩奇,杨里,吴晨芋,张三
#
# obj_l=[]
# for i in range(10):
# obj=p.submit(task,i)
# obj_l.append(obj)
#
# # p.close()
# # p.join()
# p.shutdown(wait=True)
#
# print(obj_l[3].result())
# print('主')
# 同步调用
p=ThreadPoolExecutor(4) # 默认开启的线程数是cpu的核数*5
# alex,武佩奇,杨里,吴晨芋,张三
for i in range(10):
res=p.submit(task,i).result()#等结果进行下一步
print('主')
2.异步回调 ******
为什么需要回调?
子进程帮助主进程完成任务 处理任务的结果应该交还给准进程
add_done_callback
其他方式也可以将数据交还给主进程
1.shutdown 主进程会等到所有任务完成
2.result函数 会阻塞直到任务完成
都会阻塞 导致效率降低 所以使用回调
注意:
回调函数什么时候被执行? 子进程任务完成时
谁在执行回调函数? 主进程
线程的异步回调
使用方式都相同 唯一的不同是执行回调函数 是子线程在执行
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
import os, time
pool = ProcessPoolExecutor()
def task():
print("%s is 正在打水" % os.getpid())
# time.sleep(0.2)
w = "%s 打的水" % os.getpid()
return w
def task_finish(res):
print("打水完成! %s" % res)
if __name__ == '__main__':
for i in range(20):
# 提交任务会返回一个对象 用于回去执行状态和结果
f = pool.submit(task)
f.add_done_callback(task_finish) # 添加完成后的回调
# print(f.result())
print("11111")
pool.shutdown() # 首先不允许提交新任务 然后等目前所有任务完成后
print("over")
2.1.利用回调完成生产者消费者
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
from threading import current_thread
import os
pool = ThreadPoolExecutor()
import requests # 该模块用于网络(HTTP)请求
# 生产数据
def get_data_task(url):
print(os.getpid(),"正在生产数据!")
# print(current_thread(),"正在生产数据!")
response = requests.get(url)
text = response.content.decode("utf-8")
print(text)
return text
# 处理数据
def parser_data(f):
print(os.getpid(),"处理数据")
# print(current_thread(), "处理数据")
print("正在解析: 长度%s" % len(f.result()))
urls = [
"http://www.baidu.com",
"http://www.baidu.com",
"http://www.baidu.com",
"http://www.baidu.com"
]
if __name__ == '__main__':
for url in urls:
f = pool.submit(get_data_task,url)
f.add_done_callback(parser_data) # 回调函数是主进程在执行
# 因为子进程是负责获取数据的 然而数据怎么处理 子进程并不知道 应该把数据还给主进程
print("over")
33.线程队列 *** 队列 堆栈优先级队列
import queue
# 普通队列 先进先出
q = queue.Queue()
q.put("a")
q.put("b")
print(q.get())
print(q.get())
# 堆栈队列 先进后出 后进先出 函数调用就是进栈 函数结束就出栈 递归造成栈溢出
q2 = queue.LifoQueue()
q2.put("a")
q2.put("b")
print(q2.get())
# 优先级队列
q3 = queue.PriorityQueue() # 数值越小优先级越高 优先级相同时 比较大小 小的先取
q3.put((-100,"c"))
q3.put((1,"a"))
q3.put((100,"b"))
print(q3.get())
3协程 ** 2个模块greenlet gevent
协程的目的是在单线程下实现并发
为什么出现协程? 因为cpython 由于GIL 导致同一时间只有一个线程再跑
意味着 如果你的程序时计算密集 多线程效率也不会提升
如果是io密集型 有没有必要再单线程下实现并发
没有 我会开启多线程来处理io 子线遇到io cpu切走 但是请问 你能保证一定切到主线吗? 不能保证
有 如果可以 我在遇到io的时候转而去做计算 这样一来可以保证cpu一直在处理你的程序 当然时间太长也要切走
总结一下:单线下实现并发 将io阻塞时间用于执行计算 可以提高效率 原理:一直使用CPU直到超时
怎么实现单线程并发?
并发 指的是 看起来像是同时运行 实际是在任务间来回切换 同时需要保存执行的状态
任务一堆代码 可以用函数装起来
1.如何让两个函数切换执行
yield可以保存函数的执行状态
通过生成器可以实现伪并发
并发不一定提升效率 反而会降低效率 当任务全是计算时
2.如何知道发生了io? 从而切换执行
目前咱们实现不了
第三方模块 greenlet 可以实现并发 但是不能检测io
第三方模块 gevent 封装greenlet 可以实现单线程并发 并且能够检测io操作 自动切换
协程的应用场景:
TCP 多客户端实现方式
1.来一个客户端就来一个进程 资源消耗较大
2.来一个客户端就来一个线程 也不能无限开
3.用进程池 或 线程池 还是一个线程或进程只能维护一个连接
4.协程 一个线程就可以处理多个客户端 遇到io就切到另一个
3,1greenlet使用
import greenlet
import time
def task1():
print("task1 1")
time.sleep(2)
g2.switch()
print("task1 2")
g2.switch()
def task2():
print("task2 1")
g1.switch()
print("task2 2")
g1 = greenlet.greenlet(task1)
g2 = greenlet.greenlet(task2)
g1.switch()
# 1.实例化greenlet得到一个对象 传入要执行的任务
# 至少需要两个任务
# 2.先让某个任务执行起来 使用对象调用switch
# 3.在任务的执行过程中 手动调用switch来切换
#
3.2gevent使用
from gevent import monkey
monkey.patch_all()
import gevent
import time
def eat():
print('eat food 1')
time.sleep(2)
# gevent.sleep(1)
print('eat food 2')
def play():
print('play 1')
time.sleep(1)
# gevent.sleep(1)
print('play 2')
g1=gevent.spawn(eat)
g2=gevent.spawn(play)
# g1.join()
# g2.join()
gevent.joinall([g1,g2])
print('主')
# 1.spawn函数传入你的任务
# 2.调用join 去开启任务
# 3.检测io操作需要打monkey补丁 就是一个函数 在程序最开始的地方调用它