day36

GIL

'''
全局解释器锁
一个互斥锁,是用于防止解释器里线程导致的数据错乱,保证了数据安全性
在CPython中,GIL会把线程的并行变成串行,导致效率降低
因为线程并发的安全问题,所以需要GIL(gc和其他线程)

CIL的加锁和解锁时机:
当前的线程遇到; I/O时
当前线程执行时间超过设定值(注意,这是python解释器自己的调度时间)

GIL的性能问题:

1. GIL仅仅在CPython解释器中存在,在其他的解释器中没有,并不是Python这门语言的缺点

2. 在单核处理器下,多线程之间本来就无法真正的并行执行

3. 在多核处理下,运算效率的确是比单核处理器高,但是要知道现代应用程序多数都是基于网络的(qq,微信,爬虫,浏览   器等等),CPU的运行效率是无法决定网络速度的,而网络的速度是远远比不上处理器的运算速度,则意味着每次处理   器在执行运算前都需要等待网络IO,这样一来多核优势也就没有那么明显了



I/O密集型:这类问题使用多线程,并发执行也没问题
计算密集型:这类问题使用多进程


之所以广泛采用CPython解释器,就是因为大量的应用程序都是IO密集型的,还有另一个很重要的原因是CPython可以无缝对接各种C语言实现的库,这对于一些数学计算相关的应用程序而言非常的happy,直接就能使用各种现成的算法


自定义的线程/进程锁与GIL的区别:
GIL保护的是解释器级别的数据安全,比如对象的引用计数,垃圾分代数据等等,具体参考垃圾回收机制详解。

对于程序中自己定义的数据则没有任何的保护效果,这一点在没有介绍GIL前我们就已经知道了,所以当程序中出现了共享自定义的数据时就要自己加锁
'''






线程池/进程池

'''
线程池就是用来存储线程对象的容器
线程池.不仅帮我们管理了线程的开启和销毁,还帮我们管理任务的分配

在很多情况下需要控制进程或线程的数量在一个合理的范围,例如TCP程序中,一个客户端对应一个线程,虽然线程的开销小,但肯定不能无限的开,否则系统资源迟早被耗尽,解决的办法就是控制线程的数量。
'''
from concurrent.futures import ThreadPoolExecutor #线程池
from concurrent.futures import ProcessPoolExecutor #进程池
from threading import Thread,current_thread
# 创建线程池,如果不写默认为cup个数*5
pool = ThreadPoolExecutor(5)


def task():
   print(current_thread())
   name = input('please input:')
pool.submit(task)



同步 异步

'''
在并发中,经常提及的几个概念
阻塞: 程序遇到I/O操作,处于I/O状态
非阻塞: 程序处于就绪或者运行状态
并发: 多个任务看起来像是同时运行,本质是切换+保存状态
并行: 真正的多个任务同时运行,必须具备多核处理器

同步异步指的是任务的提交方式

同步: 发起任务后,必须在原地等待,直到任务完成拿到结果
默认情况下就是同步的

异步: 发起任务,不需要等待结果,可以继续执行其他代码
异步任务必须依赖并发或并行,在python中,通过线程和进程实现并发
同步会有等待的效果但是这和阻塞是完全不同的,阻塞时程序会被剥夺CPU执行权,而同步调用则不会!
'''

import time
from concurrent.futures import ThreadPoolExecutor

pool = ThreadPoolExector()

def task():
time.time()

def finished(arg):
   print(arg.result())
   print()
   
res = pool.subit(task)    
res = add_done_callback(finished) #回调函数


# 该函数默认是阻塞的 会等待池子中所有任务执行结束后执行
pool.shutdown(wait=True)
'''
总结:异步回调使用方法就是在提交任务后得到一个Futures对象,调用对象的add_done_callback来指定一个回调       函数

注意:
1. 使用进程池时,回调函数都是主进程中执行
2. 使用线程池时,回调函数的执行线程是不确定的,哪个线程空闲就交给哪个线程
3. 回调函数默认接收一个参数就是这个任务对象自己,再通过对象的result函数来获取任务的处理结果
'''

回调函数案例

import requests,re,os,random,time
from concurrent.futures import ProcessPoolExecutor

def get_data(url):
   print("%s 正在请求%s" % (os.getpid(),url))
   time.sleep(random.randint(1,2))
   response = requests.get(url)
   print(os.getpid(),"请求成功 数据长度",len(response.content))
   #parser(response) # 3.直接调用解析方法 哪个进程请求完成就那个进程解析数据 强行使两个操作耦合到一起了
   return response

def parser(obj):
   data = obj.result()
   htm = data.content.decode("utf-8")
   ls = re.findall("href=.*?com",htm)
   print(os.getpid(),"解析成功",len(ls),"个链接")

if __name__ == '__main__':
   pool = ProcessPoolExecutor(3)
   urls = ["https://www.baidu.com",
           "https://www.sina.com",
           "https://www.python.org",
           "https://www.tmall.com",
           "https://www.mysql.com",
           "https://www.apple.com.cn"]
   # objs = []
   for url in urls:
       # res = pool.submit(get_data,url).result() # 1.同步的方式获取结果 将导致所有请求任务不能并发
       # parser(res)

       obj = pool.submit(get_data,url) #
       obj.add_done_callback(parser) # 4.使用异步回调,保证了数据可以被及时处理,并且请求和解析解开了耦合
       # objs.append(obj)
       
   # pool.shutdown() # 2.等待所有任务执行结束在统一的解析
   # for obj in objs:
   #     res = obj.result()
   #     parser(res)
   # 1.请求任务可以并发 但是结果不能被及时解析 必须等所有请求完成才能解析
   # 2.解析任务变成了串行,
原文地址:https://www.cnblogs.com/zhuqihui/p/10982317.html