当前位置: 首页 > news >正文

万字长文掌握Python高并发

文章目录

  • 0 前言
  • 1 并发、并行、同步、异步、阻塞、非阻塞
    • 1.1 并发
    • 1.2 并行
    • 1.3 同步
    • 1.4 异步
    • 1.5 阻塞
    • 1.6 非阻塞
  • 2 多线程
    • 2.1 Python线程的创建方式
      • 2.1.1 方式一
      • 2.1.2 方式二 继承Thread
      • 2.1.3 通过线程池创建多线程
    • 2.2 聊聊GIL
      • 2.2.1 Python线程与操作系统线程的关系
    • 2.3 线程同步
      • 2.3.1 加同步锁处理
      • 2.3.2 死锁问题
      • 2.3.3 可重入锁
    • 2.4 高级线程同步-Condtion
    • 2.5 高级线程同步-Semphore
    • 2.6 高级线程同步-Event
  • 3 IO 多路复用+回调
    • 3.1 用同步的方式访问服务
    • 3.2 使用多线程提速
    • 3.3 改写成IO多路复用+回调的方式
  • 4 协程
    • 4.1 yield关键字
      • 4.1.1 给yield传值
      • 4.1.2 给yield传异常
      • 4.1.3 gen.close() 关闭生成器
    • 4.2 yield from 关键字
  • 5 asyncio

0 前言

高并发一直在软件开发遇到的老大难问题,软件承载并发的能力也是一个核心性能点之一,这篇文章主要讲解Python语言的高并发工具,主要包括多进程、多线程、协程等。同时聊聊python的全局解释器锁对多线程的影响。

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

1.1 并发

在这里插入图片描述
多任务在一个CPU上交替运行, 通过CPU的时间片机制,轮询调度多任务,由于CPU执行速度非常快,给人的感觉好像是多任务并行执行。

家里来客人了,我们需要泡壶茶水招待客人
需要有以下几个工作要做:
洗茶壶:5分钟
洗茶杯:3分钟
泡茶:2分钟
烧水:20分钟

主人这样做:
先烧水,烧水的同时洗茶壶和茶杯,最后在泡茶总计耗时间为22分钟
主要忘记烧水了,但是洗完茶杯又想起来了,烧水的同时洗茶壶,这样就需要消耗25分钟

其实这个生活小场景和并发非常相似,烧水的过程中,我们没有等水烧开再去做别的事情,这就类似于操作系统的IO操作,而是在烧水的过程中做一些其他的操作,进而提高了工作效率。

1.2 并行

并行是多个任务同时执行,真正的同时进行,而不是通过CPU轮询执行。

在这里插入图片描述
举个生活中的例子,一家人要吃晚饭,爸爸妈妈孩子一起做

孩子负责洗菜
妈妈负责切菜
爸爸负责炒菜

三个人同时做不同的事情,这就是并行。

1.3 同步

同步指的是不同任务同步进行,一个任务完成之后等待结果返回后,再开始另外一个任务。

import timedef task1():print("task1 start")time.sleep(1)print("task1 end")return "task1 end"def task2():print("task2 start")time.sleep(2)print("task2 end")return "task2 end"if __name__ == '__main__':task1()task2()

执行结果:task1和task2按顺序进行

task1 start
task1 end
task2 start
task2 end

1.4 异步

异步指的是不同任务一起进行,一个任务完成后不需要等待其返回结果,直接进行另外一个任务。
异步编程在IO密集型场景中效率很高。

1.5 阻塞

阻塞的意思是函数等在某个位置,直到达到某个条件后才往下走。

socket客户端的connect和recv都是阻塞点。

1.6 非阻塞

非阻塞是不必等待,直接往下进行,非阻塞一般会绑定回调函数进行工作,经典的应用场景就是IO多路复用和回调函数。但是这种编程模型也有很多问题,编程难度较高。

2 多线程

2.1 Python线程的创建方式

2.1.1 方式一

import time
import threadingdef task1():for i in range(10):time.sleep(1)print("一边看电视")def task2():for i in range(10):time.sleep(1)print("一边嗑瓜子")t1 = threading.Thread(target=task1)
t2 = threading.Thread(target=task2)t1.start()
t2.start()

2.1.2 方式二 继承Thread

通过继承Thread类,重写run方法实现多线程

import time
import threadingclass WatchThread(threading.Thread):def __init__(self, name):super().__init__()self.name = namedef run(self):for i in range(10):time.sleep(1)print("一边看电视")class EatThread(threading.Thread):def __init__(self, name):super().__init__()self.name = namedef run(self):for i in range(10):time.sleep(1)print("一边嗑瓜子")def main():wt = WatchThread(name="看电视线程")et = EatThread(name="嗑瓜子线程")wt.start()et.start()if __name__ == '__main__':main()

这两种实现多线程的方式中,第二种是比较常用的,第一种多用于测试或者一些初级应用场景

2.1.3 通过线程池创建多线程

通过python内置并发库里面的线程池开启多线程也是一种常用的方式,线程池免去创建线程和销毁线程的开销,对于那种频繁需要创建多线程和场景是比较合适的,并且多线程之间的来回切换也会造成很大的系统级性能开销,线程池维护指定数量的线程,可以降低线程切换带来的性能开销,同时可以控制线程的数量,线程的数量并不是越多越好的。

map接口的例子

import time
from concurrent.futures import ThreadPoolExecutor, Executor, Futuredef task1(sleep_time):print("hello......")time.sleep(sleep_time)return "world"executor = ThreadPoolExecutor(max_workers=3)fu = executor.map(task1, [1,2,3,2,1])  # 同一个函数,不同的参数print(fu) # <generator object Executor.map.<locals>.result_iterator at 0x00000285EADDCD40>for i in fu: # 获取结果print(i)

submit结果的例子

import concurrent.futures
import urllib.requestURLS = ['http://www.foxnews.com/','http://www.cnn.com/','http://europe.wsj.com/','http://www.bbc.co.uk/','http://some-made-up-domain.com/']# Retrieve a single page and report the URL and contents
def load_url(url, timeout):with urllib.request.urlopen(url, timeout=timeout) as conn:return conn.read()# We can use a with statement to ensure threads are cleaned up promptly
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:# Start the load operations and mark each future with its URLfuture_to_url = {executor.submit(load_url, url, 60): url for url in URLS}for future in concurrent.futures.as_completed(future_to_url):url = future_to_url[future]try:data = future.result()except Exception as exc:print('%r generated an exception: %s' % (url, exc))else:print('%r page is %d bytes' % (url, len(data)))

2.2 聊聊GIL

python的GIL一直被大家所诟病,Python的执行速度确实很慢,但是GIL不能完全背锅,GIL只是不能发挥CPU的多核优势,但是在非多线程环境下,或者是IO密集型任务下,跟GIL就没有关系了,只是在计算密集型任务时,会有一些比较慢的情况,但是可以使用多进程加速程序的运行。
Python慢的本质原因是其所有的对象都被创建在堆内存中,为了增加灵活性和动态性,牺牲了运行速度,这种设计理念也造成了Python的运行速度偏慢,同时也因为Python的动态性和灵活性才能让他这么受欢迎,有利有弊,如果真的有项目语言运行速度成为瓶颈,那可以更换其他编译型语言,比如Go。

2.2.1 Python线程与操作系统线程的关系

Python通过内置的threading模块进行线程的创建,threading模块是底层的_thread模块,_thread模块是用C语言编写的,编译之后,内嵌到解释器中的。
所以Python创建线程对应C语言的线程,C语言通过OS接口启动操作系统的线程,所以Python的线程与操作系统的线程是一一对应的关系。

Python与CPython之间的关系
cpython是python语言的解释器,cpython是c语言实现的,python代码在执行前,会被编译成字节码,然后cpython解释器逐行执行字节码。

GIL跟python语言没有关系,是cpython在实现的时候考虑到垃圾回收中的计数和C语言实现函数的原子性而设计的,如果是Jpython解释器没有GIL。

重点:GIL说的是Cpython解释器,而不是python语言本身

GIL保证同一时刻,只有一个Python线程能够执行字节码,所以GIL是字节码级别的锁,一条字节码对应一个或者多个C语言实现的函数,GIL保证了C函数的原子性。
GIL并不能保证Python源码是线程安全性。

看下面这个程序:

import disclass Ob:passobj = Ob()def add():global objdel objdis.dis(add)

通过dis模块的dis方法,获取add函数的字节码

其中第一列是源代码行号,第二列是字节码偏移量,第三列是操作指令(也叫操作码),第四列是指令参数(也叫操作数)。Python 的字节码指令都是成对出现的,每个指令都会带有一个指令参数。

11           0 RESUME                   013           2 DELETE_GLOBAL            0 (obj)4 LOAD_CONST               0 (None)6 RETURN_VALUE

每个操作码都对应一个C函数:

Python/bytecodes.c

inst(DELETE_GLOBAL, (--)) {PyObject *name = GETITEM(names, oparg);int err;err = PyDict_DelItem(GLOBALS(), name);// Can't use ERROR_IF here.if (err != 0) {if (_PyErr_ExceptionMatches(tstate, PyExc_KeyError)) {format_exc_check_arg(tstate, PyExc_NameError,NAME_ERROR_MSG, name);}goto error;}}

如果两个线程同时操作add函数
线程1,在执行完err = PyDict_DelItem(GLOBALS(), name);这句之后刚好发生了线程切换
线程2,又会重新删除这个地址,python中的对象对应C语言中的一个结构体,这就造成了重复删除,这个地址可能被其他程序使用了,也可能是空的,最终会造成什么问题,没人知道。
为了保护C语言操作的原子性和引用计数,所以才有了GIL这把大锁。

2.3 线程同步

刚说完GIL是字节码层面的锁,保证cpython在同一时刻只能有一个线程执行字节码。但是python语言的层面还是会存在线程不安全的情况,比如两个线程竞争同一个资源,会造成数据不安全,这就需要线程同步机制,在python代码层面通过加锁来实现。

一个线程不安全的例子

from threading import Threadnum = 0def add_num():global numfor i in range(1000000):num += 1if __name__ == '__main__':t1 = Thread(target=add_num)t2 = Thread(target=add_num)t3 = Thread(target=add_num)t1.start()t2.start()t3.start()print(num)

每次得到的数据都不一样

2316753

2.3.1 加同步锁处理

from threading import Thread, Locknum = 0
lock = Lock()def add_num():global numglobal lockfor i in range(1000000):lock.acquire()num += 1lock.release()if __name__ == '__main__':t1 = Thread(target=add_num)t2 = Thread(target=add_num)t3 = Thread(target=add_num)t1.start()t2.start()t3.start()t1.join()t2.join()t3.join()print(num)

加锁之后都是3000000,不管运行多少次

3000000

很明显加锁之后的速度变慢了,加锁和释放锁一定有性能消耗,所以能不加锁尽量不要加锁。

不加锁的时间是:消耗的时间:0.14963150024414062
加锁的时间是:消耗的时间是:3.216205358505249
相差的时间将近30倍了。

2.3.2 死锁问题

import threadinglock = threading.Lock()def task_a():lock.acquire()lock.acquire()print("死锁了。。。。")lock.release()lock.release()threading.Thread(target=task_a).start()

观察控制点会发现,代码没有执行也没有结束,这就是死锁,当线程没有释放锁,又去取另一把锁的时候会造成死锁。

2.3.3 可重入锁

对于同一个线程,允许同时获取多把锁,但是一定要保证获取锁和释放锁的次数是相同的。

import threadingrlock = threading.RLock()def task_a():global rlockrlock.acquire()rlock.acquire()print("相同线程可以同时获取多把锁,但是一定要与释放次数相同")rlock.release()rlock.release()print("end")threading.Thread(target=task_a).start()  # 同一个线程可以获取多次锁,不会死锁

输出结果:

相同线程可以同时获取多把锁,但是一定要与释放次数相同
end

2.4 高级线程同步-Condtion

主人与小爱同学对话:

主人:小爱同学
小爱:在,请问有什么需要帮您
主人:今天天气怎么样?
小爱:今天天气晴朗,阳光明媚
主人:小爱同学
小爱:在
主人:今天是星期几
小爱:今天是星期三
。。。

这种对话通过两个线程来交互实现,通过加锁很难实现,因为python多线程的调度机制用的是操作系统的线程调度机制,python没办法直接控制操作系统如何调度线程,所以,使用默认的调度机制通过加锁来实现交替运行是不能保证百分百实现的,还有人想通过延时操作实现,因为遇到延时操作线程都会发生切换,但是在处理多线程问题的时候,基本都不能通过时间处理,这是一个经验总结。

我们创建两个线程,通过condition来实现这种复杂的锁机制:
Condition类是在threading内置模块下的一个类,常用的 API有如下几个:

  • acquire 内部维护了一把可重入锁,获取可重入锁
  • release 释放可重入锁
  • wait 线程等待调度
  • notify 通知python开始调度线程
import threadingclass Owner(threading.Thread):def __init__(self, name, cond: threading.Condition):super().__init__()self.name = nameself._cond = conddef run(self) -> None:with self._cond:print("主人:小爱同学")self._cond.notify()self._cond.wait()print("主人:今天天气怎么样?")self._cond.notify()self._cond.wait()print("主人:小爱同学")self._cond.notify()self._cond.wait()print("主人:今天是星期几?")self._cond.notify()self._cond.wait()# 唤醒所有线程,结束程序self._cond.notify_all()class XiaoAi(threading.Thread):def __init__(self, name, cond: threading.Condition):super().__init__()self.name = nameself._cond = conddef run(self) -> None:with self._cond:self._cond.wait()print("小爱:在,请问有什么需要帮您")self._cond.notify()self._cond.wait()print("小爱:今天天气晴朗,阳光明媚")self._cond.notify()self._cond.wait()print("小爱:在")self._cond.notify()self._cond.wait()print("小爱:今天是星期三")self._cond.notify()def main():condition = threading.Condition()owner_thread = Owner(name="主人", cond=condition)xa_thread = XiaoAi(name="小爱同学", cond=condition)xa_thread.start()   # 注意这两个线程的启动顺序特别重要,不能反了,反了就错了owner_thread.start()owner_thread.join()xa_thread.join()if __name__ == '__main__':main()

2.5 高级线程同步-Semphore

控制线程并发的数量

import random
import threading
import timeclass Task(threading.Thread):def __init__(self, sem):super().__init__()self.sem = semdef run(self):with self.sem:print(f"{threading.current_thread().name}:task")time.sleep(random.randint(1, 3))def main():sem = threading.Semaphore(3)for i in range(10):t = Task(sem=sem)t.start()main()

2.6 高级线程同步-Event

Event与Condition类似,看源码可以发现Event也是通过Condition实现的,Event具有如下接口:

event.isSet():返回event的状态值;event.wait():如果 event.isSet()==False将阻塞线程;event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;event.clear():恢复event的状态值为False

通过Event可以控制两个线程的调度机制,进而实现高级别的线程同步。

from threading import Thread,Event
import timeevent=Event()def light():print('红灯正亮着')time.sleep(3)event.set() #绿灯亮def car(name):print('车%s正在等绿灯' %name)event.wait() #等灯绿 此时event为False,直到event.set()将其值设置为True,才会继续运行.print('车%s通行' %name)if __name__ == '__main__':# 红绿灯t1=Thread(target=light)t1.start()# 车for i in range(10):t=Thread(target=car,args=(i,))t.start()

3 IO 多路复用+回调

平常常提起的selelct,poll,epoll都是IO多路复用的底层实现,IO多路复用从OS层面了用户态和内核态的管理,通过通知和回调的方式避免程序进行等待。

为了本节的测试工作,首先使用flask启动一个web服务:

flask服务端代码

import timefrom flask import Flaskapp = Flask(__name__)@app.route('/sleep/<int:sleep_second>')
def index(sleep_second):time.sleep(sleep_second)return f"sleeping {sleep_second}s......"if __name__ == '__main__':app.run(host='0.0.0.0')

客户端代码

import socketdef get_url():host = "192.168.8.133"client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)client.connect((host, 5000))client.send(f'GET /sleep/2 HTTP/1.1\r\nHost:{host}\r\nConnection:close\r\n\r\n'.encode('utf-8'))response_byte = b''while True:response = client.recv(1024)response_byte += responseif not response:breakresponse = response_byte.decode('utf-8').split('\r\n\r\n')[1]print(response)client.close()if __name__ == '__main__':get_url()

运行程序之后可以获取到返回值:

D:\Envs\py_venvs\venv_py3.11\Scripts\python.exe D:\code\高并发测试代码\coders\get_url.py 
sleeping 2s......

3.1 用同步的方式访问服务

import socket
import timedef get_url(sleep_time):host = "192.168.8.133"client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)client.connect((host, 5000))client.send(f'GET /sleep/{sleep_time} HTTP/1.1\r\nHost:{host}\r\nConnection:close\r\n\r\n'.encode('utf-8'))response_byte = b''while True:response = client.recv(1024)response_byte += responseif not response:breakresponse = response_byte.decode('utf-8').split('\r\n\r\n')[1]print(response)client.close()if __name__ == '__main__':start_time = time.time()for i in range(10):get_url(i)print(f"消耗的时间:{time.time()-start_time}")

运行结果:

sleeping 0s......
sleeping 1s......
sleeping 2s......
sleeping 3s......
sleeping 4s......
sleeping 5s......
sleeping 6s......
sleeping 7s......
sleeping 8s......
sleeping 9s......
消耗的时间:45.03647589683533

3.2 使用多线程提速

import socket
import threading
import timedef get_url(sleep_time):host = "192.168.8.133"client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)client.connect((host, 5000))client.send(f'GET /sleep/{sleep_time} HTTP/1.1\r\nHost:{host}\r\nConnection:close\r\n\r\n'.encode('utf-8'))response_byte = b''while True:response = client.recv(1024)response_byte += responseif not response:breakresponse = response_byte.decode('utf-8').split('\r\n\r\n')[1]print(response)client.close()if __name__ == '__main__':start_time = time.time()for i in range(10):threading.Thread(target=get_url, args=(i,)).start()while len(threading.enumerate())-1:passprint(f"消耗的时间:{time.time()-start_time}")

运行结果

sleeping 0s......
sleeping 1s......
sleeping 2s......
sleeping 3s......
sleeping 4s......
sleeping 5s......
sleeping 6s......
sleeping 7s......
sleeping 8s......
sleeping 9s......
消耗的时间:9.092759370803833

3.3 改写成IO多路复用+回调的方式

import time
import socket
from selectors import DefaultSelector, EVENT_READ, EVENT_WRITEselect = DefaultSelector()
urls = []
stop = Falseclass GetSource:def __init__(self, _url):self.host = "192.168.8.133"self.port = 5000self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)self.client.setblocking(False)self.url = _urlself.response_byte = b''def get_connect(self):try:self.client.connect((self.host, self.port))except OSError:passselect.register(self.client.fileno(), EVENT_WRITE, self.send_msg)def send_msg(self, key):select.unregister(key.fd)self.client.send(f'GET {self.url} HTTP/1.1\r\nHost:{self.host}\r\nConnection:close\r\n\r\n'.encode('utf-8'))select.register(self.client.fileno(), EVENT_READ, self.recv_msg)def recv_msg(self, key):response = self.client.recv(1024)if response:self.response_byte += responseelse:select.unregister(key.fd)response = self.response_byte.decode('utf-8').split('\r\n\r\n')[1]print(response)self.client.close()urls.remove(self.url)if not urls:global stopstop = Truedef loop():global stopwhile not stop:ready = select.select()for key, mask in ready:call_back = key.datacall_back(key)if __name__ == '__main__':start = time.time()for i in range(0, 10):url = f"/sleep/{i}"urls.append(url)gs = GetSource(url)gs.get_connect()loop()print(f"消耗的时间:{time.time() - start}")

运行结果

sleeping 0s......
sleeping 1s......
sleeping 2s......
sleeping 3s......
sleeping 4s......
sleeping 5s......
sleeping 6s......
sleeping 7s......
sleeping 8s......
sleeping 9s......
消耗的时间:9.008429527282715

IO多路复用比多线程快10倍。

4 协程

协程的概念很早就被提出了,大家都知道进程的切换,会消耗很多系统资源,所以为了提高系统的并发性,又出现了多线程,操作系统在创建多线程的时候会在用户态和内核态分别创建内存空间,供线程使用,线程在执行过程中存在内核态和用户态的切换,这个过程同样是需要消耗时间的。为了降低多线程切换的性能开销和用户态,内核态的相互拷贝,前辈们提出了协程的概念,协程其实是用户态的多线程,协程的调度完全由程序员负责,操作系统是完全感觉不到的。协程的关键是函数能够在指定的位置停止,并且能够恢复到停止之前的状态继续执行。

4.1 yield关键字

在函数中使用yield关键字,调用函数之后返回一个生成器对象。

def gen_func():yield 1yield 2gen = gen_func()print(gen)  # <generator object gen_func at 0x000001EFE4B9B1C0>

生成器函数的生命周期

  • GEN_CREATED 生成器被创建
  • GEN_RUNNING 生成器运行中
  • GEN_SUSPENDED 生成器运行中
  • GEN_CLOSED 生成器已停止
def gen_func():yield 1yield 2gen = gen_func()print(gen)  # <generator object gen_func at 0x000001EFE4B9B1C0>import inspectprint(inspect.getgeneratorstate(gen))  # GEN_CREATEDres = gen.send(None)
print(res)
print(inspect.getgeneratorstate((gen)))  # GEN_SUSPENDEDres = gen.send(None)
print(res)try:res = gen.send(None)print(res)
except StopIteration:passprint(inspect.getgeneratorstate(gen))  # GEN_CLOSED

当函数定义中包括yield关键字后,这就是一个生成器函数,生成器函数调用后会返回一个生成器对象,生成器对象必须驱动才能够运行,驱动生成器有两种方式:

  • gen_object.send(None)
  • next(gen_object)

上面这两种方式是相同的作用,生成器驱动之后,第一次调用会停留在第一个yield之后,并把yield后面的值返回给调用方,第二次调用会停在第二个yield后面,生成器函数执行结束后会抛出一个StopIteration异常。

同样,生成器也是一个特殊的迭代器,可以通过for语句进行遍历

def gen_func():yield 1yield 2for i in gen:print(i)

输出结果:

1
2

其实for语言的本质是:

while Truetry:val = next(gen)print(val)except StopIteration:break

4.1.1 给yield传值

调用方不仅能够收到yield出来的值,还能够将值传递给生成器函数。

def gen_func():a = yield 1print(f"接收到的第一个值 a=:{a}")b = yield 2print(f"接收到的第二个值 b=: {b}")return "gen return"gen = gen_func()# 激活生成器对象
res = gen.send(None)
# 获取第一个yield后面的值
print(res)
# 给生成器对象传递值,同时获取第二个yield后面的值
res = gen.send("hello")
# 生成器函数向下执行
print(res) 
try:res = gen.send("world")
except StopIteration as e:print(e.value)  # gen return

执行结果:

1
接收到的第一个值 a=:hello
2
接收到的第二个值 b=: world
gen return

4.1.2 给yield传异常

python中一切皆对象,既然能够传值,当然也能够传递异常。

def gen_func():a = Nonetry:a = yield 1except ValueError:  # 如果不处理直接报错passprint(f"接收到的第一个值 a=:{a}")b = yield 2print(f"接收到的第二个值 b=: {b}")return "gen return"gen = gen_func()# 激活生成器对象
res = gen.send(None)
# 获取第一个yield后面的值
print(res)
# 给生成器对象传递值,同时获取第二个yield后面的值
res = gen.throw(ValueError("值传递错误"))

4.1.3 gen.close() 关闭生成器

def gen_func():yield 1yield 2gen = gen_func()import inspectprint(inspect.getgeneratorstate(gen))   # GEN_CREATEDgen.send(None)
gen.close()
print(inspect.getgeneratorstate(gen))  # GEN_CLOSED

4.2 yield from 关键字

在一个生成器中yield另外一个生成器

def gen1():yield 1yield 2def gen2():yield gen1()gen2 = gen2() 
print(gen2)  # <generator object gen2 at 0x00000185E6115180>res = gen2.send(None)
for i in res:print(i)

输出结果:

<generator object gen2 at 0x00000185E6115180>
1
2

使用yield from 关键字更简单的实现这个功能

def gen1():yield 1yield 2def gen2():yield from gen1()gen2 = gen2()
print(gen2)  # <generator object gen2 at 0x00000185E6115180>for i in gen2:print(i)

直接遍历父生成器就可以获取到子生成器中的值

<generator object gen2 at 0x000001606D2D5180>
1
2

yield from 可以作为委派生成器,在调用方和子生成器之间搭建一个桥梁,让调用方可以获取到子生成器的数据,同时可以将数据或者异常直接通过委派生成器传送到子生成器中,yield from也是python实现原生协程的根本所在。

5 asyncio

相关文章:

万字长文掌握Python高并发

文章目录0 前言1 并发、并行、同步、异步、阻塞、非阻塞1.1 并发1.2 并行1.3 同步1.4 异步1.5 阻塞1.6 非阻塞2 多线程2.1 Python线程的创建方式2.1.1 方式一2.1.2 方式二 继承Thread2.1.3 通过线程池创建多线程2.2 聊聊GIL2.2.1 Python线程与操作系统线程的关系2.3 线程同步2.…...

高性能办公娱乐迷你主机——Maxtang大唐AMD5600U

今天给大家介绍一款AMD5600U迷你主机&#xff0c;说起这款处理器大家应该并不陌生&#xff0c;像联想小新、YOGA以及ThinkBook等很多款用的都是这个型号&#xff0c;不过笔记本的价格基本都在3999-4999这个价位区间&#xff0c;同样的处理器&#xff0c;笔记本卖那么贵&#xf…...

牛客教你用雇主品牌力抢人才!附6类校招玩法

最新校招数据显示&#xff0c;79%的应届生在Offer抉择时首要考量薪资福利。但谈钱多伤感情啊~牛客从100案例中挑出6种最潮的校招雇主品牌玩法&#xff0c;助力你抢人才。01、英特尔中国&#xff1a;“芯”动小镇雇主是否能让自己产生激情和热情&#xff0c;已经成为应届生选择O…...

leaflet: 鼠标mouseover显示城市信息,mouseout隐藏信息(067)

第067个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+leaflet中显示城市信息,这里给marker添加鼠标事件,用到了mouseover和mouseout,用于控制信息的显示和隐藏。 直接复制下面的 vue+leaflet源代码,操作2分钟即可运行实现效果 文章目录 示例效果配置方式示例源代码…...

docker部署springboot项目

1、创建放置项目jar包和Dockerfile的文件夹 cd usr/ mkdir reggie cd reggie/ 2、上传Dockerfile和项目jar包 Dockerfile内容如下&#xff1a; # 基础镜像使用java FROM java:8 # 作者 MAINTAINER chenxiansheng # VOLUME 指定了临时文件目录为/tmp。 # 其效果是在主机 /v…...

简单实用的CSS属性(滑轮滚动保持头部不动、暂无数据显示、元素隔开距离、带背景的文字效果、网页上禁止选中文字、校验值有效为绿色无效为红色、)

简单实用的CSS属性&#xff08;滑轮滚动保持头部不动、暂无数据显示、元素隔开距离、带背景的文字效果、网页上禁止选中文字、校验值有效为绿色无效为红色、&#xff09; 目录 一、滑轮滚动保持头部不动 二、暂无数据显示 三、元素隔开距离 四、带背景的文字效果 backgro…...

Unity 工具 之 SoftMask软遮罩 实现 UI 边缘渐变过渡的简单使用介绍

Unity 工具 之 SoftMask软遮罩 实现 UI 边缘渐变过渡的简单使用介绍 目录 Unity 工具 之 SoftMask软遮罩 实现 UI 边缘渐变过渡的简单使用介绍 一、简单介绍 二、Mask 实现的遮罩效果 三、Soft Mask 实现遮罩效果 四、 Soft Mask 的一些设置 五、插件下载 一、简单介绍 U…...

Python-第六天 Python数据容器

Python-第六天 Python数据容器一、数据容器入门1.数据容器二、数据容器&#xff1a;list(列表)1.列表的定义2.列表的下标&#xff08;索引&#xff09;3.列表的常用操作&#xff08;方法&#xff09;4.练习案例:常用功能练习5.list&#xff08;列表&#xff09;的遍历5.1 列表的…...

【C/C++基础练习题】复习题三,易错点知识点笔记

C复习题知识点记录&#xff1a; 在定义结构体类型时&#xff0c;不可以为成员设置默认值。 在公用一个共用体变量时。系统为其分配存储空间的原则是按成员中占内存空间最大者分配 a ,La, "a", L"a" 字符 长字符 字符串 长字符串 布尔类型只有两个值 fal…...

Mysql sql优化

插入优化 1️⃣ 用批量插入代替单条插入 insert into 表明 values(1, xxx) insert into 表明 values(2, xxx) ... 改为使用&#x1f447; insert into 表名 values(1, xxx), (2, xxx)...2️⃣ 手动提交事务 start tranaction; insert into 表名 values(1, xxx), (2, xxx)... in…...

vnode 在 Vue 中的作用

vnode就是 Vue 中的 虚拟 dom 。 vnode 是怎么来的&#xff1f; 就是把 template 中的结构内容&#xff0c;通过 vue template complier 中的 render 函数&#xff08;使用了 JS 中的 with 语法&#xff09;&#xff0c;来生成 template 中对应的 js 数据结构&#xff0c;举个例…...

SQL语句实现找到一行中数据最大值(greatest)/最小值(least);mysql行转列

今日我在刷题时遇到这样一个题&#xff0c;它提到了以下需求&#xff1a; 有一场节目表演&#xff0c;五名裁判会对节目提供1-10分的打分&#xff0c;节目最终得分为去掉一个最高分和一个最低分后的平均分。 存在以下一张表performence_detail&#xff0c;包含字段有performa…...

记一次以小勃大,紧张刺激的渗透测试(2017年老文)

一、起因 emmm&#xff0c;炎炎夏日到来&#xff0c;这么个桑拿天干什么好呢&#xff1f; 没错&#xff0c;一定要坐在家里&#xff0c;吹着空调&#xff0c;吃着西瓜&#xff0c;然后静静地挖洞。挖洞完叫个外卖&#xff0c;喝着啤酒&#xff0c;撸着烧烤&#xff0c;岂不美…...

LeetCode 61. 旋转链表

原题链接 难度&#xff1a;middle\color{orange}{middle}middle 题目描述 给你一个链表的头节点 headheadhead &#xff0c;旋转链表&#xff0c;将链表每个节点向右移动 kkk 个位置。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], k 2 输出&#xff1a;[4,5,1…...

数据库(4)--视图的定义和使用

一、学习目的 加深对视图的理解&#xff0c;熟练视图的定义、查看、修改等操作 二、实验环境 Windows 11 Sql server2019 三、实验内容 学生&#xff08;学号&#xff0c;年龄&#xff0c;性别&#xff0c;系名&#xff09; 课程&#xff08;课号&#xff0c;课名&#xff0c;…...

pandas表格并表(累加合并)

今天需求是用pandas的两张表格合并起来&#xff0c;其中重复的部分将数据进行相加。 用到的是combine&#xff08;&#xff09;这个函数。 函数详细的使用可以看这个大佬的文章&#xff1a; https://www.cnblogs.com/traditional/p/12727997.html &#xff08;这个文章使用的测…...

汽车直营模式下OTD全流程

概述 随着新能源汽车的蓬勃发展&#xff0c;造车新势力的涌入&#xff0c;许多新能源车企想通过直营的营销模式来解决新能源汽车市场推广速度缓慢问题&#xff0c;而直营模式下OTD&#xff08;Order-To-Delivery&#xff0c;订单-交付&#xff09;全流程的改革创新在这过程中无…...

如何在 Canvas 上实现图形拾取?

图形拾取&#xff0c;指的是用户通过鼠标或手指在图形界面上能选中图形的能力。图形拾取技术是之后的高亮图形、拖拽图形、点击触发事件的基础。 canvas 作为一个过于朴实无华的绘制工具&#xff0c;我们想知道如何让 canvas 能像 HTML 一样&#xff0c;知道鼠标点中了哪个 “…...

适用于媒体行业的管理数据解决方案—— StorageGRID Webscale

主要优势 1、降低媒体存储库的复杂性 • 借助真正的全局命名空间在全球范围内存储数据并在本地进行访问。 • 实施纠删编码和远程复制策略。 • 通过单一管理平台管理策略和监控存储。 2、优化媒体工作流 • 确认内容在合适的时间处于合适的位置。 • 支持应用程序直接通过 A…...

Springboot+ElasticSearch构建博客检索系统-学习笔记01

课程简介&#xff1a;从实际需求分析开始&#xff0c;打造个人博客检索系统。内容涵盖&#xff1a;ES安装、ES基本概念和数据类型、Mysql到ES数据同步、SpringBoot操作ES。通过本课&#xff0c;让学员对ES有一个初步认识&#xff0c;理解ES的一些适用场景&#xff0c;以及如何使…...

vue3+element-plus el-descriptions 详情组件二次封装(vue3项目)

最终效果 一、需求 一般后台管理系统&#xff0c;通常页面都有增删改查&#xff1b;而查不外乎就是渲染新增/修改的数据&#xff08;由输入框变成输入框禁用&#xff09;&#xff0c;因为输入框禁用后颜色透明度会降低&#xff0c;显的颜色偏暗&#xff1b;为解决这个需求于是封…...

No.14新一代信息技术

新一代信息技术产业包括&#xff1a;加快建设宽带、泛在、融合、安全的信息忘了基础设施&#xff0c;推动新一代移动通信、下一代互联网核心设备和智能终端的研发及产业化&#xff0c;加快推进三网融合&#xff0c;促进物联网、云计算的研发和示范应用。 大数据、云计算、互联…...

微信小程序开发(五)小程序代码组成2

微信小程序开发&#xff08;五&#xff09;小程序代码组成2 为了进一步加深我们对小程序基础知识的了解和掌握&#xff0c;需要更进一步的了解小程序的代码组成以及一些简单的代码的编写。 参考小程序官方的的代码组成文档&#xff1a;https://developers.weixin.qq.com/ebook?…...

关于tensorboard --logdir=logs的报错解决办法记录

我在运行tensorboard --logdirlogs时&#xff0c;产生了如下的报错&#xff0c;找遍全网后&#xff0c;解决办法如下 先卸载 pip uninstall tensorboard再安装 pip install tensorboard最后出现如下报错 Traceback (most recent call last): File “d:\newanaconda\envs\imo…...

em,rem,px,rpx,vw,vh的区别与使用

在css中单位长度用的最多的是px、em、rem&#xff0c;这三个的区别是&#xff1a;一、px是固定的像素&#xff0c;一旦设置了就无法因为适应页面大小而改变。二、em和rem相对于px更具有灵活性&#xff0c;他们是相对长度单位&#xff0c;意思是长度不是定死了的&#xff0c;更适…...

Vue+node.js医院预约挂号信息管理系统vscode

网上预约挂号系统将会是今后医院发展的主要趋势。 前端技术&#xff1a;nodejsvueelementui,视图层其实质就是vue页面&#xff0c;通过编写vue页面从而展示在浏览器中&#xff0c;编写完成的vue页面要能够和控制器类进行交互&#xff0c;从而使得用户在点击网页进行操作时能够正…...

Java真的不难(五十四)RabbitMQ的入门及使用

RabbitMQ的入门及使用 一、什么是RabbitMQ&#xff1f; MQ全称为Message Queue&#xff0c;即消息队列。消息队列是在消息的传输过程中保存消息的容器。它是典型的&#xff1a;生产者、消费者模型。生产者不断向消息队列中生产消息&#xff0c;消费者不断的从队列中获取消息。…...

Unity | Script Hot Reload

官网地址&#xff1a;https://hotreload.net/ 一、作用 Unity在运行时&#xff0c;可以直接修改代码&#xff0c;避免等待过长的编译时间。 二、说明 1、支持的平台&#xff1f; Windows、MacOS、Linux 2、支持的Unity版本&#xff1f; 2018.4 (LTS)2019.4 (LTS)2020.3 (L…...

3|射频识别技术|第五讲:数据通信和编码技术|第九章:编码与调制|重点理解掌握传输介质中的有线传输介质

计算机网络部分&#xff1a;https://blog.csdn.net/m0_57656758/article/details/128943949传输介质分为有线传输介质和无线传输介质两大类&#xff1b;有线传输介质通常包含双绞线、同轴电缆和光导纤维&#xff1b;无线传输介质包含微波、红外线等。传输介质的选择和连接是网络…...

【遇见青山】基于Redis的Feed流实现案例

【遇见青山】基于Redis的Feed流实现案例1.关注推送2.具体代码实现1.关注推送 关注推送也叫做Feed流&#xff0c;直译为投喂。为用户持续的提供"沉浸式”的体验&#xff0c;通过无限下拉刷新获取新的信息。 Feed流产品有两种常见模式&#xff1a; 这里我们实现基本的TimeL…...