Python--day37(GIL、进程池与线程池、异步同步、异步回调)

1. GIL(全局解释器锁)

1.1 什么是GIL

在CPython中,这个全局解释器锁,也称为GIL,是一个互斥锁,防止多个线程在同一时间执行Python字节码。换句话说,这个锁就是用来锁住python解释器的。

1.2 GIL带来的问题

GIL加到了解释器上,并且是一把互斥锁,那么这把锁对应用程序就会产生一定的影响

py文件中的内容本质都是字符串,只有在被解释器解释时,才具备语法意义,解释器会将py代码翻译为当前系统支持的指令交给系统执行。

当进程中仅存在一条线程时,GIL锁的存在没有不会有任何影响,但是如果进程中有多个线程时,GIL锁就开始发挥作用了

 

开启子线程时,给子线程指定了一个target表示该子线程要处理的任务即要执行的代码。代码要执行则必须交由解释器,即多个线程之间就需要共享解释器,为了避免共享带来的数据竞争问题,于是就给解释器加上了互斥锁!

由于互斥锁的特性,程序串行,保证数据安全,降低执行效率,GIL将使得程序整体效率降低!

1.4 GIL与GC

GC:python自带的内存管理机制。Python中内存管理使用的是引用计数,每个数会被加上一个整型的计数器,表示这个数据被引用的次数,当这个整数变为0时则表示该数据已经没有人使用,成了垃圾数据。当内存占用达到某个阈值时,GC会将其他线程挂起,然后执行垃圾清理操作,垃圾清理也是一串代码,也就需要一条线程来执行。

GC与其他线程都在竞争解释器的执行权,而CPU何时切换,以及切换到哪个线程都是无法预支的,这样一来就造成了竞争问题。

当然其他一些涉及到内存的操作同样可能产生问题问题,为了避免GC与其他线程竞争解释器带来的问题,CPython简单粗暴的给解释器加了互斥锁。有了GIL后,多个线程将不可能在同一时间使用解释器,从而保证了解释器的数据安全。

1.5 为什么Cpython要这么设计???

Cpython诞生于1991年 而多核处理器诞生2004年

当时不需要考虑多核效率问题

现在为什么不拿掉这个锁,因为这期间,很多已经完成的代码都依赖于这个锁,如果直接拿到,这些代码全得改,成本太大了

1.6 GIL的加锁与解锁时机

加锁的时机:在调用解释器时立即加锁

解锁时机:

  该线程任务结束

  当前线程遇到了IO时释放

  当前线程执行时间超过设定值时释放   默认100纳秒

GIL的优点:

  • 保证了CPython中的内存管理是线程安全的

GIL的缺点:

  • 互斥锁的特性使得多线程无法并行

1.7 解决方案:

区分任务类型

如果是IO密集使用多线程 

  有一个下载任务 要从网络中下载一个文件 大小1G和转换 任务 使用input 转为大写输出

  上述任务并行执行,耗时也不会有太大的提升,反而开启多进程会浪费更多资源这种任务称之为

  IO密集型,大量的时间都花在IO等待

  这类问题使用多线程,

  如果是计算密集使用多进程

 

计算密集型任务使用多进程

图像处理,语音处理,大数据分析,

 

1.8 GIL锁与自定义锁的关系

都是互斥锁

为什么有了GIL还需要自己加锁

GIL是加在解释器上的,只能锁住,解释器内部的资源,但是无法锁住我们自己开启资源

 

自定义锁:自己开启共享资源还得自己所锁

 

2. 进程池与线程池

池表示一个容器,本质上就是一个存储进程或线程的列表

2.1 为什么需要进程/线程池?

在很多情况下需要控制进程或线程的数量在一个合理的范围,例如TCP程序中,一个客户端对应一个线程,虽然线程的开销小,但肯定不能无限的开,否则系统资源迟早被耗尽,解决的办法就是控制线程的数量。线程/进程池不仅帮我们控制线程/进程的数量,还帮我们完成了线程/进程的创建,销毁,以及任务的分配

 

mport time
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
from threading import enumerate,current_thread

# 创建池子,可以指定池子里面有多少线程,如果不指定默认为CPU的个数 * 5
# 不会立即开启线程,会等到有任务提交后再开启线程

pool = ThreadPoolExecutor(2) # 线程池最大值,应该机器所能承受的最大值
#
print(enumerate())   # 检测当前存活的线程 [<_MainThread(MainThread, started 11148)>]

def task(i):
    print("%s is running" % i)
    time.sleep(3)

for i in range(5):
    pool.submit(task,i)

def task1(name,age):
    print(name)
    print(current_thread().name, "run")   # ThreadPoolExecutor-0_0 run
    print(age)

pool.submit(task1,"jerry",30)

time.sleep(5)

print(enumerate())

"""
    线程池,不仅帮我们管理了线程的开启和销毁,还帮我们管理任务的分配
    特点: 线程池中的线程只要开启之后 即使任务结束也不会立即结束  因为后续可能会有新任务
          避免了频繁开启和销毁线程造成的资源浪费
    1.创建一个线程池 
    2.使用submit提交任务到池子中   ,线程池会自己为任务分配线程  
"""

# 进程的使用 同样可以设置最大进程数,默认为CPU数

pool = ProcessPoolExecutor()
def task2(name):
    print(os.getpid())
    print(name)

if __name__ == '__main__':
    pool.submit(task2,"wangyong")
    pool.shutdown()   # 等待所有任务全部完毕 销毁所有线程 后关闭线程池
    # pool.submit(task2,"jerry") # 线程已经被销毁,所以会报错
View Code

3. 同步异步----阻塞非阻塞

3.1 阻塞非阻塞指的是程序的运行状态

阻塞:当程序执行过程中遇到了IO操作,在执行IO操作时,程序无法继续执行其他代码,称为阻塞!

非阻塞:程序在正常运行没有遇到IO操作,或者通过某种方式使程序即时遇到了也不会停在原地,还可以执行其他操作,以提高CPU的占用率

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

同步指调用:发起任务后必须在原地等待任务执行完成,才能继续执行

异步指调用:发起任务后必须不用等待任务执行,可以立即开启执行其他操作

同步会有等待的效果但是这和阻塞是完全不同的,阻塞时程序会被剥夺CPU执行权,

而同步调用则不会!

4. 异步回调

4.1 什么是异步回调

异步回调指的是:在发起一个异步任务的同时指定一个函数,在异步任务完成时会自动的调用这个函数

4.2 为什么需要异步回调

之前在使用线程池或进程池提交任务时,如果想要处理任务的执行结果则必须调用result函数或是shutdown函数,而它们都是是阻塞的,会等到任务执行完毕后才能继续执行,这样一来在这个等待过程中就无法执行其他任务,降低了效率,所以需要一种方案,即保证解析结果的线程不用等待,又能保证数据能够及时被解析,该方案就是异步回调

4.3 异步回调的案例分析

线程池异步回调

"""
    爬虫是干啥的
    从网络中下载某个页面数据
    在从页面中提取有价值的数据

"""
"""
为嘛使用异步回调
1.生产者和消费者解开了耦合  
2.消费者可以及时处理生产完成的数据 
"""
import requests
from concurrent.futures import ThreadPoolExecutor
from threading import current_thread

pool = ThreadPoolExecutor(2)

# 获取数据
def get_data(url):
    response = requests.get(url)
    print("%s 下载完成" % current_thread().name)
    return url,response.text

# 解析数据
def parser(obj):
    url,data = obj.result()
    print("%s 长度%s" % (url,len(data)))
    print("%s解析完成!" % current_thread().name)

urls = [
    "http://www.baidu.com",
    "http://www.qq.com",
    "http://www.python.org",
    "http://www.sina.com",
    "http://www.oldboyedu.com",
]

for u in urls:
    obj = pool.submit(get_data, u)
    obj.add_done_callback(parser)

print("提交完成")
View Code

进程池异步回调

import os
import requests
from concurrent.futures import ProcessPoolExecutor

pool = ProcessPoolExecutor()

def get_data(url):
    response = requests.get(url)
    print("%s下载完成" % os.getpid())
    return url,response.text

def parser(obj):
    url, data = obj.result()
    print("%s 的长度%s" %(url, len(data)))
    print("%s解析完成" % os.getpid())

urls = [
    "http://www.baidu.com",
    "http://www.qq.com",
    "http://www.python.org",
    "http://www.sina.com",
    "http://www.oldboyedu.com",
]

if __name__ == '__main__':
    for u in urls:
        obj = pool.submit(get_data, u)
        obj.add_done_callback(parser)

    print("提交完成")
    print("父进程pid:", os.getpid())
View Code

猜你喜欢

转载自www.cnblogs.com/wangyong123/p/10981734.html
今日推荐