day39 同步异步,异步回调,线程队列,协程

1.GIL
  什么是GIL?
  是全局解释器锁 ,仅存在与Cpython中。
  为什么需要它?
  在同一时间只有一个线程在使用解释器
  如果程序中只有一个线程还需要吗?
  解释器会自己启动垃圾回收机制,也会造成解释器的竞争问题
例如 GC发现变量x引用计数为0 正准备清扫 CPU突然切换到了另一个线程a
a拿着x进行使用 在使用的过程中 又切换到了GC GC接着把X指向的空间进行释放
这样一来a中的x就无法使用了 GIL将分配内存回收内存相关的操作加了锁
GIL无法避免自定义的线程中的数据竞争问题
  GIL带来的问题是什么?
 多线程情况下:加锁虽然保证了数据的安全 但是降低了性能, 在多CPU的机器上 无法利用多核提升效率。
 其他线程要想执行 必须等到之前的线程释放了GIL 这就意味着 同一时间只有一个线程在运行
这是一个全球性的问题 你可以尝试解决一下
  GIL带来的效率问题致命吗?
  不是致命,因为目前很多程序 都是需要网络的,  网络速度远比CPU慢 ,假设你的网络需要10ms python 3ms。


区分IO密集 与 计算密集
为什么不用其他解释器?
因为cpython是c语言实现 可以无缝对接c现有的所有库 就是很多现成的功能

GIL 和 自定义互斥锁的异同点:
  相同点:都是互斥锁 ,争抢执行权是无序的 ,执行被锁定的代码时有序的。
  不同点:1.GIL锁的是解释器的数据 自定义互斥锁所得是用户自定义的数据;
         2.GIL的加锁与解锁,是自动执行的。
GIL自动解锁的时间点: 1.io操作时,切换到另一个进程;2.代码执行完毕时~~;3.同一线程执行时间过长3ms(py3中)~~。
执行的字节码指令数量达到一定值(py2中)


线程池与进程池
什么是池? 一种存储数据的容器 要存储的数据是线程或进程
为什么使用? 为了方便管多线程或进程 (在有很多子进程或子线程的情况下)
有什么特点:
1.管理进程的创建
2.管理进程的销毁
3.负责任务的分配
4.控制最大并发数量
注意:用池来实现TCP并发 是不完美的 ,因为进程中代码执行完毕才??算空闲。???




今日内容:
1.异步同步 和 阻塞非阻塞 概念******

 

线程的三种状态:???
1.就绪
2.运行
3.阻塞

 

阻塞: 遇到了IO操作时,表面:代码卡在当前行代码,无法执行下一行,内在:CPU会切换到其他任意线程下的线程。
eggon:阻塞调用是指调用结果返回之前,当前线程会被挂起(如遇到io操作)。函数只有在得到结果之后才会将阻塞的线程激活。

遇见io操作就会阻塞,那么io操作有哪些例子?文件读写操作;input;recv、accept;我们常常用time.sleep(2)模拟io操作。


非阻塞: 与阻塞相反,代码正在执行(运行状态) 或处于就绪状态,没有被卡。指运行态或就绪态。
阻塞和非阻塞描述的是运行的状态

同步:提交任务必须等待任务完成,才能执行下一行。
举例:
1.没学并发编程之前的函数嵌套调用。
#
1. concurrent.futures.ProcessPoolExecutor().submit(func,).result() #2. concurrent.futures.ThreadPoolExecutor().submit(func,).result()
def task():
    for i in range(1000000):
        i += 1000
    print("11111")

print("start")
task() #这是 同步提交任务,也叫异步调用函数
print("end")

 

异步:提交任务不需要等待任务完成,立即执行下一行。
指的是一种提交任务的2种方式
异步也叫异步调用,使用它的前提是有多进程或多线程。
#异步的概念和同步相对。当一个异步功能调用发出后,调用者不能立刻得到结果。
当该异步功能完成后,通过状态、通知或回调来通知调用者。
如果异步功能用状态来通知,那么调用者就需要每隔一定时间检查一次,效率就很低(有些初学多线程编程的人,总喜欢用一个循环去检查某个变量的值,这其实是一 种很严重的错误)。
如果是使用通知的方式,效率则很高,因为异步功能几乎不需要做额外的操作。至于回调函数,其实和通知没太多区别。
#举例: #1. multiprocessing.Pool().apply_async() #发起异步调用后,并不会等待任务结束才返回,相反,会立即获取一个临时结果(并不是最终的结果,可能是封装好的一个对象)。 #2. concurrent.futures.ProcessPoolExecutor(3).submit(func,) #3. concurrent.futures.ThreadPoolExecutor(3).submit(func,)

from threading import  Thread

print("start1")
Thread(target=task).start()  #这是 异步提交任务,也叫异步调用函数。
print("end1")
 总结:
#1. 同步与异步针对的是函数/任务的调用方式:同步就是当一个进程发起一个函数(任务)调用的时候,一直等到函数(任务)完成,而进程继续处于激活状态。
而异步情况下是当一个进程发起一个函数(任务)调用的时候,不会等函数返回运行结果,而是继续往下执行当,函数返回的时候通过状态、通知、事件等方式通知进程任务完成。
#2. 阻塞与非阻塞针对的是进程或线程的类型或运行状态:阻塞是当请求不能满足的时候就将进程挂起,而非阻塞则不会阻塞当前进程。

 

2.异步回调 ******

为何要用异步回调?

 

什么是异步回调?

 

 

题目:利用回调完成生产者消费者模型。
  01.多进程为什么需要用回调?怎么用?
    子进程帮助主进程完成任务,  处理任务的结果应该交还给主进程。
  其他方式也可以将数据交还给主进程
  1.shutdown 主进程会等到所有任务完成
  2.result函数 会等到直到任务完成
  都会等待 导致效率降低 所以使用回调
  注意:
  回调函数什么时候被执行? 子进程任务完成时
  谁在执行回调函数? 主进程
  



from
concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor from threading import current_thread import os pool = ProcessPoolExecutor() #创建进程池对象 # 爬虫 1.从网络某个地址获取一个HTML文件 import requests # 该模块用于网络(HTTP)请求 # 生产数据 def get_data_task(url): print(os.getpid(),"进程正在生产数据!") response = requests.get(url) text = response.content.decode("utf-8") print(text) return text # 处理数据 def parser_data(f): print(os.getpid(),"进程处理数据") 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) #提交函数/submit函数的作用:异步提交任务。 f.add_done_callback(parser_data) # 回调函数在多进程中是主进程在执行,回调函数
#回调函数即add_done_callback函数,作用是:将异步提交任务的执行结果,交给任务发起方。
 # 因为子进程是负责获取数据的 然而数据怎么处理 子进程并不知道 应该把数据还给主进程 print("over")

 

  02.多线程为什么需要用回调?怎么用?
    使用方式都相同 唯一的不同是:执行回调函数 是子线程在执行。

from  concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
from threading import current_thread
import  os
pool = ThreadPoolExecutor()#建立一个线程池对象

# 爬虫  1.爬虫就是从网络某个地址,发一个请求获取一个HTML文件,然后匹配我要的数据。

import requests # 该模块用于网络(HTTP)请求

# 生产数据
def get_data_task(url):
    
    print(current_thread(),"线程正在生产数据!")

    response = requests.get(url)
    text = response.content.decode("utf-8")
    print(text)
    return text


#   处理数据
def parser_data(f):
    
    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")

 

 

 

 



3.线程队列 ***
队列
堆栈
优先级队列

4.协程 ******
协程的目的是在单线程下实现并发。
为什么出现协程? 因为cpython 由于GIL 导致同一时间只有一个线程再跑。
意味着 如果你的程序时计算密集 多线程效率也不会提升
如果是io密集型 有没有必要再单线程下实现并发?
没有 我会开启多线程来处理io,子线遇到io cpu切走,但是请问 你能保证一定切到主线吗? 不能保证
有 如果可以 我在遇到io的时候转而去做计算, 这样一来可以保证cpu一直在处理你的程序 当然时间太长也要切走

总结一下:单线下实现并发的原理: 将io阻塞时间用于执行计算 可以提高效率。 原理:一直使用CPU直到超时。
有了思路那么怎么实现单线程并发?
并发 指的是 看起来像是同时运行 实际是在任务间来回切换 同时需要保存执行的状态
任务一堆代码 可以用函数装起来
1.如何让两个函数切换执行
yield可以保存函数的执行状态
通过生成器可以实现伪并发
并发不一定提升效率 反而会降低效率 当任务全是计算时
2.如何知道发生了io? 从而切换执行
目前咱们实现不了
第三方模块 greenlet 可以实现并发 但是不能检测io
第三方模块 gevent 封装greenlet 可以实现单线程并发 并且能够检测io操作 自动切换

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()#作用:主线程等待g1任务结束
# g2.join()  gevent.joinall([g1,g2]) print('主') 
注意函数作用:
# 1.spawn函数传入你的任务。 # 2.调用join 去让主进程等待g1任务结束。因为不调用join的话,spawn产生任务后还未来得及运行完,这个单线程就结束了。 # 3.检测io操作需要打monkey补丁 就是一个函数 在程序最开始的地方调用它。

 

协程的应用场景:
TCP 多客户端各个实现方式的缺点:
1.来一个客户端就来一个进程 资源消耗较大;
2.来一个客户端就来一个线程 也不能无限开;
3.用进程池 或 线程池 还是一个线程或进程只能维护一个连接;
4.协程 一个线程就可以处理多个客户端 遇到io就切到另一个任务。

 

posted @ 2018-11-13 17:27  timm_book  阅读(53)  评论(0)    收藏  举报