【Python】线程—GIL—asyncio

2024-03-18 18:44
文章标签 python 线程 asyncio gil

本文主要是介绍【Python】线程—GIL—asyncio,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 一、Python 线程
  • 二、threading 模块
  • 三、例程
    • 3.1 基本用法
    • 3.2 同步
      • 3.21 Lock(锁)
      • 3.22 RLock(递归锁)
      • 3.23 Condition(条件变量)
      • 3.24 Semaphore(信号量)
  • 四、GIL
    • 4.1 简述
    • 4.2 详细
    • 4.3 有GIL多线程仍要加锁
  • 五、协程 asyncio

一、Python 线程

线程是一种轻量级的并行执行方式,它可以让我们在一个程序中同时执行多个任务。线程编程是多任务处理的一种特殊形式,它允许我们创建多个线程来执行不同的任务。

多线程通常是为了利用计算机的多核CPU(并行处理),来提升程序的运行速度。然而在多核CPU出现的几十年之前就出现多线程的概念了,其目的是不显式的切换任务的情况下,让CPU可以并发的处理若干个任务,提高资源利用率。

因为GIL的存在,有人说“python的多线程没有意义”。的确,由于GIL的存在,python的多线程无法像其它语言一样通过利用多核CPU提高程序运行速度,但其在处理IO瓶颈任务和处理需要低延迟的小任务时,仍然具有优势。同时,多线程也可解决协程调度的问题。

我第一次使用python线程是GUI程序,当主窗口打开一个子窗口的时候,子窗口如果有正在处理的任务,主窗口就会卡住,所以使用了线程。
在这里插入图片描述

二、threading 模块

Python的threading模块提供了多个类来支持多线程编程

  1. Thread类:这是threading模块中最核心的类。它代表了一个线程,可以独立执行任务。创建Thread对象时,可以传递一个callable对象作为目标函数。Thread类还有一个重要的方法start()用于启动线程,以及join()方法用于等待线程完成。
  2. Lock类:用于实现线程间的互斥锁机制,确保同一时间只有一个线程能够访问特定的资源或代码段。它有两个主要的方法:acquire()release(),分别用于获取和释放锁。
  3. RLock类:与Lock类似,但RLock允许同一个线程多次获得锁,这在递归调用时非常有用。
  4. Semaphore类:是一个更高级的锁机制,允许一定数量的线程同时访问资源。Semaphore维护了一个计数器,通过acquire()release()方法来增加或减少计数值。
  5. Event类:用于实现线程间的同步,一个线程可以通过Event对象向另一个线程发送信号。Event对象有一个内部标志,可以通过set()clear()方法来设置和清除这个标志。
  6. Condition类:类似于Event,但提供了更复杂的同步机制。它允许线程等待某个条件成立,然后才继续执行。通常与Lock或RLock一起使用。
  7. Barrier类:用于实现线程间的同步屏障,当所有线程都达到某个点时,它们才会被允许继续执行。
  8. Timer类:用于在指定的延迟后调用一个函数。它不是直接用于线程同步,但可以用于安排未来的任务。
  9. ThreadLocal类:提供了线程局部数据的功能,允许每个线程拥有自己独立的对象实例。

部分方法:

方法描述
Threadstart()开始线程执行。
run()线程要执行的方法。默认情况下是调用target参数指定的函数,可以被子类重写。
join(timeout=None)等待线程终止。如果设置了timeout,则最多等待timeout秒。
is_alive()判断线程是否在运行。
getName()获取线程名称。
setName(name)设置线程名称。
Lockacquire(blocking=True, timeout=-1)获取锁。blocking=True时,阻塞直到获得锁或超时;blocking=False时,非阻塞获取锁。timeout指定非阻塞等待时间。
release()释放锁。
locked()检查锁的状态。
RLockacquire(blocking=True, timeout=-1)获取重入锁。行为类似于Lock,但支持同一线程多次获取锁。
release()释放重入锁。
locked()检查锁的状态。
Conditionacquire(blocking=True, timeout=-1)获取锁,用于线程间同步。行为类似于Lock,但用于线程间协调。
release()释放锁,用于线程间同步。
wait(timeout=None)等待直到被通知或超时。必须在已获得锁的情况下调用。
notify(n=1)通知等待的线程,至少通知n个线程。
notify_all()通知所有等待的线程。
Semaphoreacquire(blocking=True, timeout=None)获取信号量。类似于Lock,但允许多个线程同时访问临界区,但有一定限制。
release()释放信号量。
Eventset()设置事件标志为True,通知等待该事件的所有线程。
clear()设置事件标志为False。
is_set()检查事件标志是否为True。
Timerstart()开始计时器线程。在指定时间后调用指定函数。
cancel()取消计时器。如果计时器仍在等待运行,则取消。

三、例程

3.1 基本用法

import threadingdef my_function():print("Thread {} is running...".format(threading.Thread.getName(my_thread)))# 创建线程
my_thread = threading.Thread(target=my_function, name="myThread")# 启动线程
my_thread.start()# 等待线程执行完成
my_thread.join()print("Main thread ends.")

3.2 同步

Python中线程同步的方法有以下几种:

  1. 锁(Lock):这是最基本的同步机制,用于确保同一时间只有一个线程能够访问特定的资源或代码段。通过acquire()方法加锁和release()方法解锁来实现线程间的互斥。
  2. 递归锁(RLock):与普通锁类似,但允许同一个线程多次获得锁,适用于递归调用的情况。
  3. 信号量(Semaphore):用于控制同时访问特定资源的线程数量。当一个线程完成对资源的访问后,会释放信号量,允许其他线程进入。
  4. 事件(Event):用于通知所有等待的线程某个事件已经发生。线程可以通过wait()方法等待事件发生,通过set()方法来通知事件已发生。
  5. 条件变量(Condition):允许线程等待某些条件成立,然后才继续执行。通常与锁一起使用,以防止多个线程同时改变条件。
  6. 队列(Queue):提供了一种适合多线程编程的数据结构,可以在不同线程之间安全地传递消息。
  7. 全局解释器锁(GIL):虽然不是直接由程序员控制的同步机制,但它是Python中的一个内置机制,用于确保在任何给定时刻,只有一个线程能够执行Python字节码。

它们的特点和适用场景:

工具特点适用场景
Lock最基本的互斥锁,一次只允许一个线程访问共享资源
不可重入,即同一线程再次获取会导致死锁
简单的线程同步需求
需要确保一段代码同一时间只能被一个线程执行
RLock可重入锁,同一线程可以多次获取锁并释放
允许同一线程多次调用 acquire()
复杂的递归线程同步需求
某些情况下需要允许同一线程多次获取和释放锁
Semaphore允许一定数量的线程同时访问共享资源
控制并发数量
有限资源的并发控制
控制同时运行的线程数量,比如限流
Event可以通过 set() 和 clear() 设置和清除事件状态
线程可以等待事件的发生
线程间通信和同步
一个线程等待某个事件的发生,另一个线程触发事件
Condition提供了更高级的线程同步机制,结合了锁和事件复杂的线程协调和通信需求
- 允许线程等待某个条件,其他线程在满足条件时通知等待的线程继续执行

3.21 Lock(锁)

  • threading.Lock 类提供了最基本的线程同步机制,它可以确保一次只有一个线程可以访问共享资源。
  • acquire() 方法用于获取锁,release() 方法用于释放锁。
  • 示例:
import threading
import time# 创建一个锁对象
lock = threading.Lock()def worker():# 获取锁lock.acquire()try:# 执行需要同步的操作print("Thread {} is working...".format(threading.Thread.getName(t)))# 模拟耗时操作time.sleep(1)finally:# 释放锁lock.release()# 创建多个线程并启动它们
threads = []
for i in range(5):t = threading.Thread(target=worker)threads.append(t)t.start()# 等待所有线程完成
for t in threads:t.join()print("All threads finished.")

3.22 RLock(递归锁)

  • threading.RLock 是一个可重入锁,允许同一线程多次获得锁。
  • acquire()release() 方法的使用方式与 Lock 类似,但允许同一线程多次调用 acquire()
  • 示例:
import threading
import timeclass Counter:def __init__(self):self.value = 0self.lock = threading.RLock()def increment(self):with self.lock:self.value += 1print(f"Incremented to {self.value} by {threading.currentThread().getName()}")time.sleep(0.1)  # 模拟一些计算或I/O操作def decrement(self):with self.lock:self.value -= 1print(f"Decremented to {self.value} by {threading.currentThread().getName()}")time.sleep(0.1)  # 模拟一些计算或I/O操作def worker(counter):for _ in range(3):counter.increment()counter.decrement()# 创建 Counter 实例
counter = Counter()# 创建多个线程
threads = []
for i in range(3):thread = threading.Thread(target=worker, args=(counter,))threads.append(thread)thread.start()# 等待线程执行完成
for thread in threads:thread.join()print("Final counter value:", counter.value)

3.23 Condition(条件变量)

  • threading.Condition 是一个高级的线程同步工具,同时提供了锁和条件等待/通知机制。
  • acquire()release() 方法用于加锁和解锁,wait() 方法用于等待条件的通知,notify()notify_all() 方法用于发送通知。
  • 示例:
import threadingshared_resource = []
condition = threading.Condition()def consumer():with condition:print("Consumer waiting...")condition.wait()print("Consumer consumed the resource:", shared_resource.pop(0))def producer():with condition:print("Producer producing resource...")shared_resource.append("New Resource")condition.notify()print("Producer notified the consumer.")# 创建线程
consumer_thread = threading.Thread(target=consumer)
producer_thread = threading.Thread(target=producer)# 启动线程
consumer_thread.start()
producer_thread.start()# 等待线程执行完成
consumer_thread.join()
producer_thread.join()print("Main thread ends.")

3.24 Semaphore(信号量)

  • threading.Semaphore 是一种控制并发访问的计数器,它允许多个线程同时访问共享资源,但可以限制同时访问的线程数量。
  • acquire()release() 方法用于获取和释放信号量。
  • 示例:
import threadingsemaphore = threading.Semaphore(value=2)  # 允许同时两个线程访问def access_resource():with semaphore:print(threading.currentThread().getName(), "is accessing the resource.")# 假设这里是对共享资源的访问# 创建多个线程
threads = []
for i in range(5):thread = threading.Thread(target=access_resource)threads.append(thread)thread.start()# 等待线程执行完成
for thread in threads:thread.join()print("Main thread ends.")

这些是 Python 中常用的线程同步方法。选择合适的方法取决于你的应用场景,例如是否需要多次获取锁、是否需要等待条件、是否需要限制并发数量等。这些同步工具能够有效地管理多线程程序中的竞态条件,确保线程安全地访问共享资源。

四、GIL

4.1 简述

Python的全局解释器锁Global Interpreter Lock,简称GIL)是CPython解释器中的一种线程同步机制。具体如下:

  1. 原理与作用:GIL是一种互斥锁,它确保在任何时刻只有一个线程执行Python字节码。这意味着即使在多核CPU上,使用多线程的Python程序也无法实现真正的并行执行
  2. 优缺点:GIL的存在简化了内存管理和解释器的实现,因为不需要担心多个线程同时修改内存中的数据结构。然而,这也限制了多线程在计算密集型任务中的应用,因为GIL会阻止多个线程同时利用多核处理器的优势。
  3. 性能瓶颈:对于I/O密集型任务,GIL的影响相对较小,因为线程大部分时间都在等待I/O操作,而不是执行计算。但是,对于计算密集型任务,GIL可能导致性能瓶颈,因为它限制了多线程的并行能力。
  4. 解决方案:为了克服GIL的限制,可以使用多进程代替多线程(多进程未必使程序更快),因为每个进程都有自己的Python解释器和GIL,从而可以在多核CPU上并行运行。此外,还可以使用Jython或IronPython这样的替代Python解释器,它们没有GIL的限制。
  5. 未来展望:Python社区正在努力解决GIL的问题。例如,Python 3.12引入了GIL可选项,允许在编译时关闭GIL,以提高CPU密集型场景的性能。

cpython的PR中可以看到前几天的一条PR,即添加GIL的开关。
在这里插入图片描述
当然不是release版,能用到可能还需要很久。

有时候,限制程序性能的可能不是GIL,而是程序的生产者。🤣

4.2 详细

看的高天视频。

在讲解GIL之前,首先需要澄清一个概念:什么是线程?

  • 线程是操作系统进行计算和调度的最小单位。我们可以简单地理解为,程序运行在线程中。每个线程有自己的上下文。
  • 而进程是比线程更大的单位,每个进程有自己的内存等。一个进程可以包含多个线程,这些线程共享进程的内存,也就是说这些线程可以读写相同的变量。

当一个进程有不止一个线程时,就会出现一种情况,称为"racing"或"竞争冒险"。因为一个进程中的多个线程,既可能同时运行,也可能交替运行。无论是同时运行还是交替运行,你都无法控制它们之间的相对顺序。
举例:

a = 1if a > 0:a -= 1
  • 假设两个线程都在运行这个函数。
  • 我们有"线程一"和"线程二"。假设它们两个都成功地将 A 初始化为 1。注意,他们两个共享变量 A。
  • 假设 “多线程一” 先来判断 if A 大于零,他发现是 true,然后进入了 if 语句。
  • 这时候 “多线程二” 开始判断,同时也发现 A 大于零,然后也进入了 if 语句。
  • 由于他们两个线程都进了 if 语句,所以 A 被减了两次。然而,左边这个程序的目的显然是将 A 减到零。
  • 在更多情况下,可能是线程一运行,然后 if A 大于零,A 减一,然后 A 变成零了。这时候线程二再来判断 if A 大于零,他就发现 A 不大于零了,然后他就跳过了 if 语句。
  • 这种情况,由于线程之间的相对运行顺序不同,导致了结果不同,我们称之为"竞争冒险"。

如果你学过 C 和 C++ 的话,你会知道在这些语言里,你需要显式地分配(如malloc)和释放(free)内存。如果你只分配不释放,随着程序的运行,你占用的内存会越来越多,最终导致内存泄漏。但是在 Python 中,你不需要显式地去分配和释放内存。所有的 Python 对象,包括列表和字典等,你拿来就可以直接使用,不用担心这些繁琐的事情,因为 Python 的解释器会帮你管理内存。

那么 Python 是如何实现自动分配和释放内存的呢?内存分配相对容易,我需要内存时我就拿就行了,关键是什么时候可以释放它。Python 使用的机制叫做"引用计数"。引用计数的原理并不复杂,每一个 Python 对象都数着有多少个地方在使用它。当没有新的地方在使用它时,对象的引用计数加一;当这个对象不再被使用时,引用计数减一。这样一来,只要你数数是对的,Python 就可以知道什么时候这个对象的引用计数变为零,这时候就没有人需要它了,于是自动帮你释放掉这块内存。

这个过程本身并不难理解,就是数数嘛。

然而,结合刚才我们提到的"竞争冒险",我们可以想象,如果一个进程中有多个线程在运行的话,这里就会存在一个"竞争冒险"的问题。因为这个减少引用计数的操作并不是"原子性(atomic)"的。"原子性"的意思是在运行的时候不会被其他线程打断。这个减少引用计数虽然在 C 语言中看起来像一个操作符,但它实际上也要先读取这个引用计数的信息,然后减一存回去。在这三个步骤中间,就有可能有其他的线程过来,在你存回去之前也进行这个操作。这种情况的发生就可能导致你数数数错了,多数了一个,或者少数了一个。一旦你数数数不清楚了,你就无法保证每一个 Python 对象都能被正确释放,那就会出现严重的内存泄漏问题。

在多线程中一般来说,我们会使用"加锁"来解决这个问题。加锁的意思是,我要保证这一段程序只有一个线程在运行,其他线程不能进入这段程序。

伪代码:

# 在这个if之前,先锁住
lock.acquire()
if a > 0:a -= 1
# 在这个if之后,释放锁
lock.release()

通过这种方式,在运行 if a > 0: 语句块时,其他线程无法进入这段代码。他们需要等待当前线程释放锁之后,才能再次运行这段程序。

回到 Python。你可能会认为在这个例子中,只需在 if 外面加一个锁就可以解决问题。但是在 Python 中,并不仅仅是引用计数存在这个问题,所有与 Python 对象相关的代码都有可能存在竞争冒险的问题,都有可能有多个线程同时尝试读取或写入 Python 对象的数据。因此,当 Python 设计者决定给 Python 设计一个全局锁,也就是我们所说的 GIL ,这是一个比较简单的解决方案。

GIL,全局解释器锁,位于我们之前提到的 CPython 的主循环中。它的作用是确保在它运行完之前,当前线程持有 GIL 锁。通过这种机制,Python 可以确保每一个字节码在运行时都拿到线程锁。换言之,没有线程能够在运行期间打断执行任何字节码。因此,在每一个字节码中运行的 C 程序都是线程安全的。你可以在里面放心地增加引用计数、减少引用计数,而不必担心锁的问题,因为你知道锁已经被拿住了。

全局锁带来的好处是非常多的:

  • 首先,这是一个非常简单的设计。在编写较大项目时,你会发现简单真的很重要。程序越简单,你维护所需要的努力就越少。相比于为每个对象实现自己的锁,全局锁要简单得多。
  • 其次,由于只有一个线程锁,它避免了死锁问题。死锁是指一个线程拥有两个以上的锁时可能发生的情况。
  • 第三,对于单线程程序或者不能并行的多线程程序来说,全局锁的性能是非常优秀的。因为全局锁保证了每次运行一个字节码时最多只需要一次锁。但是如果每个对象都有自己的锁,你可能需要多次锁。
  • 最后,它让编写 C 扩展变得更容易。因为你可以确定每个字节码运行时都没有竞争冒险的问题。这样在你的 C 代码中修改 Python 对象时,你就不必担心锁的问题了,这让第三方开发者编写扩展变得更容易。

正因为这些优点,GIL 至今仍然存在于 Python 中。当然,也有人尝试过从 Python 中移除 GIL,但没有一次尝试能够保证 Python 在单线程下的运行速度不受影响。另外,还有一个非常严重的问题,即所谓的向后兼容性。也就是说,之前写的 C 扩展都默认现在有线程锁。如果你现在把这个东西拿掉了,那之前写的扩展很可能就无法使用了。

上面谈到了 GIL 的一些优势,但是它也受到很多人的批评,因为它限制了 Python 在多核 CPU 下的表现。然而,在 Python 中,有其他的方法来避免这个问题。

  • 最简单也是最 Python 的方法就是**使用多进程。**虽然一个进程不能利用多个 CPU 核心,但我可以有很多个进程,通过多进程可以避开 GIL 的问题,并利用多核 CPU 来加速程序。
  • 第二种方法是编写 C 扩展,然后在 C 中实现多线程,让多线程运行的是 C 代码而不是 Python 代码。当然,这样一来,你需要自己解决竞争冒险的问题。
  • 最后,你还可以尝试使用一些没有 GIL 的 Python 解释器,像 Jython 和 IronPython。不过,它们也有自己的问题。

4.3 有GIL多线程仍要加锁

尽管Python的全局解释器锁(GIL)确保了同一时刻只有一个线程执行Python字节码,这防止了多个线程同时修改Python对象,从而避免了一些竞态条件问题。然而,这并不意味着你可以完全不需要锁。

简单例程:

import threadingcounter = 0def f():global counterfor _ in range(1000000):counter += 1t1 = threading.Thread(target=f)
t2 = threading.Thread(target=f)t1.start()
t2.start()t1.join()
t2.join()print(counter)

分别用 python3.12 和 python3.8 来运行:

在这里插入图片描述

结果是不一样的。Why ???

python虚拟机的核心在_PyEval_EvalFrameDefault函数里面,先看一下3.8版本的,主要是main loop:,它负责一个一个运行字节码,循环开始时,对eval_breaker的值进行了检查,它保存的信息是是否需要交出GIL。
在这里插入图片描述

每一次循环,即一个字节码的运行时,都会检查是否需要交出GIL(给其他线程)。但这样开销较大。可以看到不同字节码可能对应 FAST_DISPATCHDISPATCH,前者不会触发GIL检查,后者会。python3.8 里面使用
DISPATCH的占大多数。即大部分字节码运行后有可能交出GIL,给其它线程使用。
在这里插入图片描述
查看前面程序的字节码:python -m dis .\gil.py

  9          12 LOAD_GLOBAL              1 (counter)14 LOAD_CONST               2 (1)16 INPLACE_ADD

INPLACE_ADD对应的是DISPATCH,即第一个线程运行+1后还没将结果保存到counter就可能会交出GIL,然后线程2在那里自加,之后又将运行权交给线程1,线程1将很久之前的值拿来加,导致结果错误。


在python3.8中

#define FAST_DISPATCH() goto fast_next_opcode
#define DISPATCH() continue

而3.10中:

#define DISPATCH() goto predispatch;
#endif
#define CHECK_EVAL_BREAKER() \if (_Py_atomic_load_relaxed(eval_breaker)) { \continue; \}

FAST_DISPATCH消失,新加CHECK_EVAL_BREAKER(检查是否释放GIL),DISPATCH宏变成了goto predispatch

从3.10开始只有少数字节码会触发GIL转移,即函数调用、jump。

验证:之前在3.12中运行不加锁会出现正确结果,但现在加一个中间变量,线程的racing又出现了:

在这里插入图片描述
因为这里的jump(JUMP_BACKWARD)操作可能会触发GIL的转移。

 11          46 LOAD_GLOBAL              0 (counter)56 LOAD_CONST               2 (1)58 BINARY_OP                0 (+)62 STORE_FAST               0 (temp)64 JUMP_BACKWARD           15 (to 36)

尽管前面3.12能运行出正确的结果,但这只是一种behavior,而不是feature。
在这里插入图片描述

五、协程 asyncio

协程(Coroutine)是一种运行在单线程中的并发操作结构,它允许程序在特定的位置挂起(暂停)和恢复执行。与线程相比,协程更加轻量级,因为它们不需要像线程那样维护操作系统的上下文切换开销。协程的实现通常由程序员自己控制,可以更加灵活地控制执行流程,有助于编写高效、简洁的异步代码。

在 Python 中,协程通常使用 asyncio 模块来实现。Python 3.5 引入了 async 和 await 关键字,使得定义和使用协程变得更加方便。

协程的特点包括:

  • 轻量级: 协程是由程序员自己控制的,不需要操作系统的上下文切换开销,因此更加轻量级。
  • 非阻塞式的异步编程: 协程允许在等待 I/O 操作(如网络请求、文件读写等)时让出执行权,让其他协程继续执行,从而实现非阻塞式的异步编程。这使得 Python 应用程序可以高效地处理大量并发的 I/O 操作。
  • 易于理解和编写: 使用 async 和 await 关键字,协程的编写更加直观和易于理解。相比于回调函数或者使用生成器实现的协程,async/await 的语法更加清晰。
  • 可以并发执行: 在一个单线程中,可以同时运行多个协程,由事件循环来调度它们的执行。

await关键字用于在异步函数中等待一个协程的执行结果。它只能在async def定义的异步函数中使用。当程序执行到await语句时,它会暂停当前协程的执行,将控制权交还给事件循环,直到等待的协程完成执行并返回结果。然后,程序会继续执行后续代码。

asyncio 方法:

  1. 创建和管理任务(Tasks)
    • asyncio.create_task(coroutine):创建一个 Task 对象来运行指定的协程。
    • asyncio.gather(*coroutines, return_exceptions=False):并发运行多个协程,并等待它们全部完成。如果 return_exceptions 设置为 True,则不会在协程抛出异常时立即取消其他协程,而是等待所有协程完成,异常信息会放在结果列表中。
  2. 事件循环(Event Loop)
    • asyncio.get_event_loop():获取当前事件循环。
    • asyncio.set_event_loop(loop):设置当前事件循环。
    • loop.run_until_complete(future):运行直到 future 完成。future 可以是一个任务(Task)或协程对象。
    • loop.run_forever():运行事件循环,直到调用 loop.stop()
    • loop.stop():停止事件循环。
  3. 等待和挂起
    • await asyncio.sleep(delay, result=None):挂起当前协程,等待指定的时间(秒),然后继续执行。
    • await asyncio.wait(tasks, timeout=None, return_when=ALL_COMPLETED):等待指定的任务(Tasks),直到满足指定的返回条件。
    • asyncio.wait_for(future, timeout):等待 future 完成或者超时。
    • asyncio.shield(aw):保护一个协程,使其不受取消的影响。
  4. 事件和回调
    • loop.call_soon(callback, *args):调度一个回调函数让事件循环尽快执行。
    • loop.call_later(delay, callback, *args):调度一个回调函数让事件循环在指定延迟后执行。
    • loop.call_at(when, callback, *args):在指定的时间调度一个回调函数。
    • loop.time():返回事件循环的时间。
  5. 信号和事件
    • asyncio.Event():创建一个事件对象,用于协程间的通信。
    • asyncio.Queue(maxsize=0):创建一个队列,用于协程间的数据交换。
    • loop.add_signal_handler(signal, callback, *args):为信号添加一个回调函数。

Python 3.7 开始,Python 提供了 asyncio.run() 函数来简化运行 async 函数的过程。这个函数会自动创建一个新的事件循环,并运行给定的 async 函数,然后在完成后关闭事件循环。

import asyncioasync def foo():print('Start foo')await asyncio.sleep(1)print('End foo')async def main():print('Start main')await foo()print('End main')asyncio.run(main())

这篇关于【Python】线程—GIL—asyncio的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/823279

相关文章

Python如何获取域名的SSL证书信息和到期时间

《Python如何获取域名的SSL证书信息和到期时间》在当今互联网时代,SSL证书的重要性不言而喻,它不仅为用户提供了安全的连接,还能提高网站的搜索引擎排名,那我们怎么才能通过Python获取域名的S... 目录了解SSL证书的基本概念使用python库来抓取SSL证书信息安装必要的库编写获取SSL证书信息

详解如何使用Python提取视频文件中的音频

《详解如何使用Python提取视频文件中的音频》在多媒体处理中,有时我们需要从视频文件中提取音频,本文为大家整理了几种使用Python编程语言提取视频文件中的音频的方法,大家可以根据需要进行选择... 目录引言代码部分方法扩展引言在多媒体处理中,有时我们需要从视频文件中提取音频,以便进一步处理或分析。本文

python多种数据类型输出为Excel文件

《python多种数据类型输出为Excel文件》本文主要介绍了将Python中的列表、元组、字典和集合等数据类型输出到Excel文件中,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参... 目录一.列表List二.字典dict三.集合set四.元组tuplepython中的列表、元组、字典

VSCode配置Anaconda Python环境的实现

《VSCode配置AnacondaPython环境的实现》VisualStudioCode中可以使用Anaconda环境进行Python开发,本文主要介绍了VSCode配置AnacondaPytho... 目录前言一、安装 Visual Studio Code 和 Anaconda二、创建或激活 conda

pytorch+torchvision+python版本对应及环境安装

《pytorch+torchvision+python版本对应及环境安装》本文主要介绍了pytorch+torchvision+python版本对应及环境安装,安装过程中需要注意Numpy版本的降级,... 目录一、版本对应二、安装命令(pip)1. 版本2. 安装全过程3. 命令相关解释参考文章一、版本对

讯飞webapi语音识别接口调用示例代码(python)

《讯飞webapi语音识别接口调用示例代码(python)》:本文主要介绍如何使用Python3调用讯飞WebAPI语音识别接口,重点解决了在处理语音识别结果时判断是否为最后一帧的问题,通过运行代... 目录前言一、环境二、引入库三、代码实例四、运行结果五、总结前言基于python3 讯飞webAPI语音

基于Python开发PDF转PNG的可视化工具

《基于Python开发PDF转PNG的可视化工具》在数字文档处理领域,PDF到图像格式的转换是常见需求,本文介绍如何利用Python的PyMuPDF库和Tkinter框架开发一个带图形界面的PDF转P... 目录一、引言二、功能特性三、技术架构1. 技术栈组成2. 系统架构javascript设计3.效果图

Python如何在Word中生成多种不同类型的图表

《Python如何在Word中生成多种不同类型的图表》Word文档中插入图表不仅能直观呈现数据,还能提升文档的可读性和专业性,本文将介绍如何使用Python在Word文档中创建和自定义各种图表,需要的... 目录在Word中创建柱形图在Word中创建条形图在Word中创建折线图在Word中创建饼图在Word

Java捕获ThreadPoolExecutor内部线程异常的四种方法

《Java捕获ThreadPoolExecutor内部线程异常的四种方法》这篇文章主要为大家详细介绍了Java捕获ThreadPoolExecutor内部线程异常的四种方法,文中的示例代码讲解详细,感... 目录方案 1方案 2方案 3方案 4结论方案 1使用 execute + try-catch 记录

Python Excel实现自动添加编号

《PythonExcel实现自动添加编号》这篇文章主要为大家详细介绍了如何使用Python在Excel中实现自动添加编号效果,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1、背景介绍2、库的安装3、核心代码4、完整代码1、背景介绍简单的说,就是在Excel中有一列h=会有重复