【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 threading


def 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 time


class Counter:
    def __init__(self):
        self.value = 0
        self.lock = threading.RLock()

    def increment(self):
        with self.lock:
            self.value += 1
            print(f"Incremented to {self.value} by {threading.currentThread().getName()}")
            time.sleep(0.1)  # 模拟一些计算或I/O操作

    def decrement(self):
        with self.lock:
            self.value -= 1
            print(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 threading

shared_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 threading

semaphore = 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 = 1

if 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 threading

counter = 0

def f():
    global counter
    for _ in range(1000000):
        counter += 1


t1 = 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 asyncio


async 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())

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/466336.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MySQL教程-SQL

SQL(Structured Query Language)结构化查询语言,操作关系型数据库的编程语言,定义了一套操作关系型数据库统一标准。 语法 SQL语句可以单行或多行书写,以;为结束标记SQL可以使用空格或缩进来增强语句的可读性SQL分单行注释(-- 注释内容 或 …

跨境电商应该用什么样的服务器?多大带宽?

跨境电商在选择服务器 和带宽时,需要考虑多个因素,包括业务规模、用户数量、网站流量、地理位置等。下面是一些关键考虑因素: 1、服务器类型 跨境电商通常会选择使用云服务器,因为云服务器具有灵活性、可扩展性和高可用性。云服务…

做户用光伏代理赚钱吗

随着全球能源危机的加剧和环境问题的日益严重,清洁能源的开发和利用成为了一个重要的议题。光伏发电作为一种绿色、可再生的能源,在全球范围内得到了广泛的关注和应用。 一、代理农村光伏项目挣钱吗 随着国家对光伏发电的政策支持和补贴,以及…

关 于 重 燃 学 习 的 热 情

3月1日是我回学校的第一天。经历了长达8个月在家的昏暗时刻,我这10天的感觉和在家的感觉发生了翻天覆地的变化,最明显的莫过于学习状态的改变。 倒不是说在家学的不好,而是说在学校,我对学习的整体感觉,以及专注程度&…

鸿蒙开发学习:【驱动子系统】

OpenHarmony驱动子系统采用C面向对象编程模型构建,通过平台解耦、内核解耦,兼容不同内核,提供了归一化的驱动平台底座,旨在为开发者提供更精准、更高效的开发环境,力求做到一次开发,多系统部署。 为了缩减…

避雷!又新增一本SCI被标记On Hold,共16本!

毕业推荐 IEEE(CCF-C类) • 计算机医学类,7.5-8.0,JCR1区,中科院2/1区(TOP) • 3-4个月左右录用 SCIE: • 计算机类,6.5-7.0,JCR1区,中科院2…

关于udp能跨局域网传输的问题

UDP(用户数据报协议)以其独特的传输特性在多种应用场景中都有着极其重要的作用。然而,关于UDP是否能跨局域网(LAN)进行传输,以及这一传输过程中的优缺点,一直是网络技术领域讨论的热点。本文将详…

气液分离器的概念和原理

气液分离器也叫低压储液器,在热泵或制冷系统中使用,主要是将出蒸发器、进压缩机气流中的液滴分离出来,防止压缩机发生液击,用于工质充注量较大、压缩机进气可能带液且压缩机对湿压缩较敏感的情况 。 液击主要出现在活塞式压缩机中…

JSON 的了解和使用

目录 1. JSON 2. JSONcpp 的安装 3. JSONcpp 相关API的使用 3.1. 将 Json::Value 对象转化为 std::string 3.1.1. Json::Value 类 3.1.2. Json::Value::toStyledString 接口 3.1.3. Json::StyledWriter 类 3.1.4. Json::StyledWriter::write 接口 3.1.5. Json::Fas…

git remote Support for password authentication was removed on August 13, 2021

一 问题描述 git push 上报时提示 remote: Support for password authentication was removed on August 13, 2021. Please use a personal access token instead. 原因是:github 从2021年8月13日开始就不能用了,必须使用个人访问令牌(pers…

Midjourney绘图欣赏系列(十三)

Midjourney介绍 Midjourney 是生成式人工智能的一个很好的例子,它根据文本提示创建图像。它与 Dall-E 和 Stable Diffusion 一起成为最流行的 AI 艺术创作工具之一。与竞争对手不同,Midjourney 是自筹资金且闭源的,因此确切了解其幕后内容尚不…

Clickhouse 单机部署安装

前言 在大数据的时代背景下,数据的处理和分析能力成为企业竞争力的关键。ClickHouse,作为一款由俄国Yandex公司开发的分布式数据分析型数据库,凭借其卓越的性能和稳定性,赢得了业界的广泛关注。本文将介绍ClickHouse的基本概念、…

java IO 04 对象处理流,序列化

01.序列化和反序列化的作用 重点: 图: 02.对象流ObjectOutputStream和ObjectInputStream ObjectInputStream: ObjectOutputStream: 例子: 例子: 修改要序列化类的话,会出现不同的uid…

为什么要为 App 应用加固 ?如何为 App 应用加固 ?

一:为什么要为 App 应用加固 来看下 腾讯开放平台 官方的解释说明 若应用不做任何安全防护,极易被病毒植入、广告替换、支付渠道篡改、钓鱼、信息劫持等,严重侵害开发者的利益。 App 加固后,可以对应用进行安全防护,防…

酷开科技以酷开系统的力量让电视机“活”起来

让用户回归电视的绝不会是因为电视机本身,而是电视系统的内容和交互的形式。酷开科技以系统的力量让电视机“活”起来。对于许多人来说,观看电影是一种享受、一种放松、一种逃避现实的方式。而现在,酷开科技作为行业内领军企业,为…

Echarts横向柱形图

项目原型图如下: 实际上就是设置两个y轴,第一个显示底色柱子,另一个只显示真实数据的柱子,在这里只显示y轴,x轴不显示.. this.middleLeftOption {tooltip: {trigger: axis,axisPointer: {}},legend: {data: [回退次数],y: bottom},grid: {left: 3%,righ…

你清楚微信加好友的规则吗?

微信加人的规则是: 1、通过附近人功能加人上限15人/天,频率3次/天,间隔时间1-2小时; 2、通过摇一摇功能加人上限15人/天,频率3次/天,间隔时间1-2小时; 3、通过通讯录搜索功能加人上限6人/单次…

【大模型】直接在VS Code(Visual Studio Code)上安装CodeGeeX插件的过程

文章目录 一、什么是CodeGeeX(一)我理解的CodeGeeX(二)优缺点 二、CodeGeex下载、安装、注册(一)安装VS Code(Visual Studio Code)(二)下载安装CodeGeeX(三)注…

泰迪智能科技携手华北电力大学理学院共建“校外实践基地”

3月15日,华北电力大学数理学院教学副主任史会峰、科研副主任王涛、概率教研室副主任解西阳莅临泰迪智能科技产教融合实训基地开展“华北电力大学校外实践教学基地”签约揭牌仪式。泰迪智能科技董事长张良均、支持中心负责人王宏刚、外联部吴桂锋进行接待。 活动伊始…

动手学深度学习9 多层感知机+代码实现

多层感知机代码实现 1. 感知机2. 多层感知机单隐藏层三个激活函数多隐藏层 3. 代码实现从零开始实现简洁实现注意 4. QA 1. 感知机 视频:https://www.bilibili.com/video/BV1hh411U7gn/?spm_id_fromautoNext&vd_sourceeb04c9a33e87ceba9c9a2e5f09752ef8 书&am…