Python高级知识点学习(九)

简介: 并发、并行,同步、异步,阻塞、非阻塞并发、并行并发是在一个时间段内,有几个程序在同一个cpu上运行,但是任意时刻只有一个程序在cpu上运行。并行是任意时刻点上,有多个程序同时运行在多个cpu上。

并发、并行,同步、异步,阻塞、非阻塞

并发、并行
  • 并发是在一个时间段内,有几个程序在同一个cpu上运行,但是任意时刻只有一个程序在cpu上运行。
  • 并行是任意时刻点上,有多个程序同时运行在多个cpu上。
同步、异步
  • 同步是指代码调用IO操作时,必须等待IO操作完成才返回的调用方式。
  • 异步是指代码调用IO操作时,不必等待IO操作完成就返回的调用方式。
阻塞、非阻塞
  • 阻塞是指调用函数时候当前线程被挂起。
  • 非阻塞是指调用函数时候当前线程不会被挂起,而是立即返回。

阻塞和非阻塞的概念和同步异步感觉很像,但是其实它们之间是有区别的。

区别:

同步和异步实际上是消息通信的一种机制,可以把IO操作看做一个消息,调用IO操作时,相当于发一个消息给另外一个线程(或者说另外一个协程),让它去执行某些操作,在提交数据之后立刻得到future,后边就可以通过future拿到结果,实际上是消息之间的通信机制。

阻塞和非阻塞是不同于同步异步的,它是函数调的一种机制。

IO 多路复用 (select、poll 和 epoll)

unix中五种I/O模型

  1. 阻塞式I/O
  2. 非阻塞式I/O
  3. I/O复用
  4. 信号驱动式I/O (用的比较少)
  5. 异步I/O (POSIX的aio_系列函数)

以上五种是递进式的发展。

I/O多路复用:

select方法也是阻塞的方法,select本身是阻塞式的,select可以监听多个文件句柄和socket,select在某一个文件句柄或者socket准备好的话就会返回,这时候立刻可以做业务逻辑处理。

I/O多路复用带来的好处是:

比如现在同时发起了100个非阻塞式的请求,这时候直接使用select去监听这100个socket,这样的话一旦有一个发生状态变化,我们就可以立马处理它。

I/O多路复用中,将数据从内核复制到用户空间这段时间消耗还是省不了。

异步IO:

这里的异步IO是真正意义上的异步IO(aio),我们现在接触到很多高并发框架实际上都没有使用异步IO,实际上在很大程度上使用的都是io多路复用技术,IO多路复用很成熟很稳定,异步IO对于IO多路复用性能提升并没有达到很明显的程度,但是编码难度有很大提升,所以当前情况下IO多路复用用的比较多。
异步IO节省掉了数据从内核拷贝到用户空间这一步骤。

select、poll、epoll:

select、poll、epoll都是I/O多路复用的机制。
I/O多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般就是读就绪或写就绪),能够通知程序进行相应的读写操作。
但select、poll、epoll本质上都是同步I/O,因为它们都需要在读写事件就绪后自己负责进行读写(数据从内核拷贝到用户区),也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。

select是什么?

select 函数监视的文件描述符分三类,分别是writefds、readfds、exceptfds。调用select后会阻塞,直到有描述符就绪(有数据可读、可写、或者有except),或者超时(timeout指定等待时间,如果立即返回设为null即可),函数返回。当select函数返回后,可以通过遍历fdset来找到就绪的描述符。
select目前几乎在所有的平台上支持,其良好的跨平台也是它的一个优点。select的一个缺点在于单个进程监视的文件描述符的数量有最大限制,在linux上一般为1024,可以通过修改宏定义甚至重新编译内核的方式提升这一性质,但是这样也会造成效率的降低。

poll是什么?

不同于select使用三个位图来表示三个fdset的方式,poll使用一个pollfd的指针实现。pollfd结构包含了要监视的event和发生的event,不再使用select "参数-值" 传递的方式。同时,pollfd并没有最大数量限制(但是数量过大性能也会下降)。和select函数一样,poll返回后,需要轮询pollfd来获取就绪的描述符。

从上面看,select和poll都需要在返回后,通过遍历文件描述符来获取已经就绪的socket。事实上,同时连接的大量客户端在一时刻可能只有很少的处于就绪的状态,因此随着监视的描述符数量的增长,其效率也会线性下降。

epoll是什么?

epoll是在2.6内核中提出的,epoll是之前的select和poll的增强版本。相对于select和poll来讲,epoll更加灵活,没有描述符限制。epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需要一次。
epoll它的查询使用了数据结构中性能很高的一个:红黑树。
nginx就是使用了epoll。

epoll并不代表一定比select好:

  • 在并发高的情况下,连接活跃度不是很高, epoll比select。
  • 并发性不高,同时连接很活跃, select比epoll好。

非阻塞I/O实现http请求

上示例代码:

import socket
from urllib.parse import urlparse
def get_url(url):
    # 通过socket请求html
    url = urlparse(url)
    host = url.netloc
    path = url.path
    if path == "":
        path = "/"

    # 建立socket连接
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 这里会导致后边抛异常,但是连接请求已经发出去了
    client.setblocking(False)
    # 捕获异常
    try:
        client.connect((host, 80)) # 阻塞不会消耗cpu
    except BlockingIOError as e:
        pass

    # 不停的询问连接是否建立好, 需要while循环不停的去检查状态
    # 做计算任务或者再次发起其他的连接请求
    while True:
        try:
            client.send("GET {} HTTP/1.1\r\nHost:{}\r\nConnection:close\r\n\r\n".format(path, host).encode("utf8"))
            break
        except OSError as e:
            pass

    data = b""
    while True:
        # 这里还会抛异常,读不到就继续读
        try:
            d = client.recv(1024)
        except BlockingIOError as e:
            continue
        if d:
            data += d
        else:
            break

    data = data.decode("utf8")
    html_data = data.split("\r\n\r\n")[1]
     #打印返回的数据
    print(html_data)
    client.close()


if __name__ == "__main__":
    get_url("http://www.baidu.com")

非阻塞I/O整个过程依赖前后的监测,整个过程不停的做while循环检测状态,但是返回时间没有变,所以并没有提高并发。

select+回调+事件循环实现http请求

目前开源的高性能框架,一般都是使用这种方式实现并发。
使用select + 回调 + 事件循环实现下载网页,并发性高且是单线程。

select方法本尊是在import select这个包里边,但是有另外一个包把select基础上进行了封装,用起来更简单:from selectors import DefaultSelector,DefaultSelector一般使用DefaultSelector这个比较多。

看代码示例:

import socket
import time
from urllib.parse import urlparse
from selectors import DefaultSelector, EVENT_READ, EVENT_WRITE


selector = DefaultSelector()
urls = []
stop = False


class Fetcher:
 
    def connected(self, key):
        selector.unregister(key.fd)
        self.client.send("GET {} HTTP/1.1\r\nHost:{}\r\nConnection:close\r\n\r\n".format(self.path, self.host).encode("utf8")
        selector.register(self.client.fileno(), EVENT_READ, self.readable)

    # 当socket可读时,读数据,全部都是cpu操作
    def readable(self, key):
        d = self.client.recv(1024)
        if d:
            self.data += d
        else:
            # 数据读完为空
            selector.unregister(key.fd)
            data = self.data.decode("utf8")
            html_data = data.split("\r\n\r\n")[1]
            print(html_data[:30])
            self.client.close()
            urls.remove(self.spider_url)
            if not urls:
                global stop
                stop = True

    def get_url(self, url):
        self.spider_url = url
        url = urlparse(url)
        self.host = url.netloc
        self.path = url.path
        self.data = b""
        if self.path == "":
            self.path = "/"

        # 建立 socket 连接
        self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.client.setblocking(False)

        try:
            self.client.connect((self.host, 80))  # 阻塞不会消耗cpu
        except BlockingIOError as e:
            pass

        selector.register(self.client.fileno(), EVENT_WRITE, self.connected)

# 驱动整个事件循环
def loop():
    while not stop:
        ready = selector.select()
        for key, mask in ready:
            call_back = key.data
            call_back(key)

if __name__ == "__main__":
    # 计时开始
    start_time = time.time()
    for url in range(60):
        url = "http://www.baidu.com"
        urls.append(url)
        fetcher = Fetcher()
        fetcher.get_url(url)
    loop()
    print(time.time()-start_time)

上边代码中,Fetcher类包含三个方法,get_url简历socket连接,connected和readable是两个回调函数。
loop函数负责驱动整个事件循环。

回调的缺点

  1. 可读性差
  2. 共享状态异常处理
  3. 异常处理困难
目录
相关文章
|
13天前
|
安全 Linux 数据安全/隐私保护
python知识点100篇系列(15)-加密python源代码为pyd文件
【10月更文挑战第5天】为了保护Python源码不被查看,可将其编译成二进制文件(Windows下为.pyd,Linux下为.so)。以Python3.8为例,通过Cython工具,先写好Python代码并加入`# cython: language_level=3`指令,安装easycython库后,使用`easycython *.py`命令编译源文件,最终生成.pyd文件供直接导入使用。
python知识点100篇系列(15)-加密python源代码为pyd文件
|
14天前
|
网络协议 数据库连接 Python
python知识点100篇系列(17)-替换requests的python库httpx
【10月更文挑战第4天】Requests 是基于 Python 开发的 HTTP 库,使用简单,功能强大。然而,随着 Python 3.6 的发布,出现了 Requests 的替代品 —— httpx。httpx 继承了 Requests 的所有特性,并增加了对异步请求的支持,支持 HTTP/1.1 和 HTTP/2,能够发送同步和异步请求,适用于 WSGI 和 ASGI 应用。安装使用 httpx 需要 Python 3.6 及以上版本,异步请求则需要 Python 3.8 及以上。httpx 提供了 Client 和 AsyncClient,分别用于优化同步和异步请求的性能。
python知识点100篇系列(17)-替换requests的python库httpx
|
10天前
|
调度 Python
python知识点100篇系列(20)-python协程与异步编程asyncio
【10月更文挑战第8天】协程(Coroutine)是一种用户态内的上下文切换技术,通过单线程实现代码块间的切换执行。Python中实现协程的方法包括yield、asyncio模块及async/await关键字。其中,async/await结合asyncio模块可更便捷地编写和管理协程,支持异步IO操作,提高程序并发性能。协程函数、协程对象、Task对象等是其核心概念。
|
7天前
|
Python Windows
python知识点100篇系列(24)- 简单强大的日志记录器loguru
【10月更文挑战第11天】Loguru 是一个功能强大的日志记录库,支持日志滚动、压缩、定时删除、高亮和告警等功能。安装简单,使用方便,可通过 `pip install loguru` 快速安装。支持将日志输出到终端或文件,并提供丰富的配置选项,如按时间或大小滚动日志、压缩日志文件等。还支持与邮件通知模块结合,实现邮件告警功能。
python知识点100篇系列(24)- 简单强大的日志记录器loguru
|
8天前
|
自然语言处理 Python Windows
python知识点100篇系列(23)- 使用stylecloud生成词云
【10月更文挑战第10天】`stylecloud` 是 `wordcloud` 的优化版,支持使用 Font Awesome 图标自定义词云形状,操作更简便。本文介绍如何安装 `jieba` 和 `stylecloud` 库,并使用它们生成中文词云。通过 `jieba` 进行分词,再利用 `stylecloud` 的 `gen_stylecloud` 方法生成具有特定形状和颜色的词云图像。
python知识点100篇系列(23)- 使用stylecloud生成词云
|
11天前
|
Java Python
> python知识点100篇系列(19)-使用python下载文件的几种方式
【10月更文挑战第7天】本文介绍了使用Python下载文件的五种方法,包括使用requests、wget、线程池、urllib3和asyncio模块。每种方法适用于不同的场景,如单文件下载、多文件并发下载等,提供了丰富的选择。
|
12天前
|
数据安全/隐私保护 流计算 开发者
python知识点100篇系列(18)-解析m3u8文件的下载视频
【10月更文挑战第6天】m3u8是苹果公司推出的一种视频播放标准,采用UTF-8编码,主要用于记录视频的网络地址。HLS(Http Live Streaming)是苹果公司提出的一种基于HTTP的流媒体传输协议,通过m3u8索引文件按序访问ts文件,实现音视频播放。本文介绍了如何通过浏览器找到m3u8文件,解析m3u8文件获取ts文件地址,下载ts文件并解密(如有必要),最后使用ffmpeg合并ts文件为mp4文件。
|
14天前
|
机器学习/深度学习 人工智能 架构师
Python学习圣经:从0到1,精通Python使用
尼恩架构团队的大模型《LLM大模型学习圣经》是一个系统化的学习系列,初步规划包括以下内容: 1. **《Python学习圣经:从0到1精通Python,打好AI基础》** 2. **《LLM大模型学习圣经:从0到1吃透Transformer技术底座》**
Python学习圣经:从0到1,精通Python使用
|
15天前
|
Java Python
python知识点100篇系列(16)-python中如何获取线程的返回值
【10月更文挑战第3天】本文介绍了两种在Python中实现多线程并获取返回值的方法。第一种是通过自定义线程类继承`Thread`类,重写`run`和`join`方法来实现;第二种则是利用`concurrent.futures`库,通过`ThreadPoolExecutor`管理线程池,简化了线程管理和结果获取的过程,推荐使用。示例代码展示了这两种方法的具体实现方式。
python知识点100篇系列(16)-python中如何获取线程的返回值
|
14天前
|
机器学习/深度学习 人工智能 架构师
Python学习圣经:从入门到精通Python,打好 LLM大模型的基础
Python学习圣经:从0到1精通Python,打好AI基础