zhcn 编程语言 Java Java 流程控制语句 非公開: Python 线程:概述

Python 线程:概述

在本教程中,您将学习如何使用 Python 的内置线程模块探索 Python 的多线程功能。

了解多线程在 Python 中的工作原理,从进程和线程的基础知识开始,了解并发和并行的概念。接下来,了解如何使用内置threading模块在 Python 中启动和运行一个或多个线程。

让我们开始吧。

进程和线程:区别

流程是怎样的?

进程是需要运行的程序的实例

它可以是任何东西,从 Python 脚本和 Web 浏览器(如 Chrome)到视频会议应用程序。您可以通过在计算机上启动任务管理器并导航到性能–> CPU来查看当前正在 CPU 内核上运行的进程和线程。

cpu-proc-线程
cpu-proc-线程

了解进程和线程

在内部,进程有专用的内存来存储与进程相对应的代码和数据。

一个进程由一个或多个线程组成。线程是操作系统可以执行的最小指令序列,代表执行流程。

每个线程都有自己的堆栈和寄存器,但没有专用的内存。与该进程关联的所有线程都可以访问该数据。因此,数据和内存由进程的所有线程共享。

进程和线程
进程和线程

具有 N 个核心的 CPU 可以同时并行运行 N 个进程。但是,同一个进程的两个线程不能同时运行,但是可以并发运行。以下各节解释并发和并行的概念。

根据到目前为止所学的知识,我们来总结一下进程和线程之间的区别。

特征过程线
记忆专用内存共享内存
执行模式并行的,同时的同时;但不是并行
执行处理人操作系统CPython解释器

Python 中的多线程

在Python中, 全局解释器锁(GIL)确保任何时候只有一个线程可以获得锁并执行。所有线程都必须获得该锁才能执行。这允许任何时候只运行一个线程,避免同时多线程。

例如,考虑同一进程中的两个线程t1t2 。线程共享相同的数据,因此当t1读取特定值k时, t2可能会更改相同的值k 。这可能会导致僵局和不良结果。但是,任何时候只有一个线程可以获得锁并执行。因此,GIL也保证了线程安全

那么我们如何在Python中实现多线程呢?为了理解这一点,我们先讨论一下并发和并行的概念。

并发和并行:概述

考虑具有多个核心的 CPU。在下图中,CPU 有四个核心。这意味着可以随时并行执行四个不同的操作。

如果有四个进程,则每个进程可以在四个核心上独立且同时运行。假设每个进程有两个线程。

多核并行处理
多核并行处理

为了了解线程如何工作,让我们从多核处理器架构切换到单核处理器架构。如前所述,在给定的执行实例中只能有一个线程处于活动状态。然而,处理器核心可以切换线程。

代码
代码

例如,I/O 绑定线程经常等待 I/O 操作,例如读取用户输入、读取数据库和文件操作。在此等待时间内,I/O 绑定线程会释放其锁以允许其他线程运行。等待时间也可以是一个简单的操作,比如休眠n秒。

摘要:在等待操作期间,线程释放锁,从而允许处理器核心切换到另一个线程。一旦等待期结束,前一个线程就会恢复执行。处理器核心同时在线程之间切换的过程有利于多线程处理。 ✅

如果您想在应用程序中实现进程级并行性,请考虑使用多处理

Python 线程模块:第一步

Python 附带了一个threading模块,您可以将其导入到 Python 脚本中。

 import threading

要在 Python 中创建线程对象,可以使用Thread构造函数threading.Thread(...) 。对于大多数线程实现来说,这是足够通用的语法。

 threading.Thread(target=...,args=...)

这里,

  • target是一个关键字参数,指示 Python 可调用对象。
  • args是目标接收的参数元组。

运行本教程中的代码示例需要 Python 3.x。 下载代码并按照步骤操作。

在 Python 中定义和运行线程

让我们定义将执行目标函数的线程。

目标函数是some_func

 import threading
import time

def some_func():
    print("Running some_func...")
    time.sleep(2)
    print("Finished running some_func.")

thread1 = threading.Thread(target=some_func)
thread1.start()
print(threading.active_count())

我们来分析一下上面的代码片段做了什么。

  • 导入threadingtime模块。
  • 函数some_func有一个解释性的print()语句,并包含 2 秒的睡眠操作。 time.sleep(n)使函数休眠n秒。
  • 接下来,使用目标some_func定义线程thread_1threading.Thread(target=...)创建一个线程对象。
  • 注意:指定函数的名称,而不是函数调用。使用some_func而不是some_func()
  • 创建线程对象并不启动线程。当您调用线程对象的start()方法时执行。
  • 要获取活动线程数,请使用active_count()函数。

Python 脚本在主线程上运行,并创建另一个运行函数some_func的线程 ( thread1 )。因此,正如您在输出中看到的,活动线程数为 2。

 # Output
Running some_func...
2
Finished running some_func.

如果仔细观察输出,您会发现第一个 print 语句是在thread1启动时执行的。然而,在睡眠操作期间,处理器不会等待thread1完成执行,而是切换到主线程并打印活动线程的数量。

线程 1-ex
线程 1-ex

等待线程完成执行

如果想让thread1结束执行,可以在启动线程后调用join()方法。这样做会等到thread1完成执行而不会切换到主线程。

 import threading
import time

def some_func():
    print("Running some_func...")
    time.sleep(2)
    print("Finished running some_func.")

thread1 = threading.Thread(target=some_func)
thread1.start()
thread1.join()
print(threading.active_count())

在这里, thread1在打印活动线程数之前完成了执行。所以只有主线程在运行。即活动线程数为1。 ✅

 # Output
Running some_func...
Finished running some_func.
1

如何在Python中运行多线程

接下来,让我们创建两个执行两个不同功能的线程。

这里, count_down是一个函数,它接受一个数字作为参数,并从该数字倒数到 0。

 def count_down(n):
    for i in range(n,-1,-1):
        print(i)

定义count_up 。这是另一个从零计数到指定数字的 Python 函数。

 def count_up(n):
    for i in range(n+1):
        print(i)

📑 range()函数与range(start, stop, step)语法一起使用时,默认stop终点站。

– 要从特定数字倒数到零,请使用负step值 -1 并将stop值设置为 -1 以包含零。

– 同样,要计数到nstop值必须设置为n + 1startstep的默认值分别是0和1,因此可以使用range(n + 1)来获取从0到n的序列。

接下来,定义两个线程thread1thread2分别执行count_downcount_up函数。向这两个函数添加print语句和sleep操作。

请注意,创建线程对象时,必须在args参数中将目标函数参数指定为元组。这两个函数( count_downcount_up )都采用一个参数,因此您必须在值后面显式插入一个逗号。这会将后续元素推断为None ,因此参数仍作为元组传递。

 import threading
import time

def count_down(n):
    for i in range(n,-1,-1):
        print("Running thread1....")
        print(i)
        time.sleep(1)


def count_up(n):
    for i in range(n+1):
        print("Running thread2...")
        print(i)
        time.sleep(1)

thread1 = threading.Thread(target=count_down,args=(10,))
thread2 = threading.Thread(target=count_up,args=(5,))
thread1.start()
thread2.start()

在输出中,它看起来像这样:

  • 函数count_upthread2中执行,从0开始计数到5。
  • count_down函数在thread1上运行并从 10 倒数到 0。
 # Output
Running thread1....
10
Running thread2...
0
Running thread1....
9
Running thread2...
1
Running thread1....
8
Running thread2...
2
Running thread1....
7
Running thread2...
3
Running thread1....
6
Running thread2...
4
Running thread1....
5
Running thread2...
5
Running thread1....
4
Running thread1....
3
Running thread1....
2
Running thread1....
1
Running thread1....
0

可以看到thread1thread2交替运行,因为它们都需要等待操作(睡眠)。当count_up函数计数到 5 时, thread2不再处于活动状态。因此,您将仅获得与thread1相对应的输出。

概括

在本教程中,您学习了如何使用 Python 的内置线程模块实现多线程。要点总结如下。

  • 您可以使用Thread构造函数创建线程对象。 threading.Thread(target=<callable>,args=(<tuple of args>))创建一个线程,该线程使用args指定的参数运行目标可调用对象
  • Python程序在主线程上运行,因此您创建的线程对象是附加线程。调用active_count()函数会返回任何实例中的活动线程数。
  • 您可以使用线程对象的start()方法启动线程,并使用join()方法等待它执行完毕。

您可以通过调整等待时间或尝试不同的 I/O 操作来编写其他示例。请务必在未来的 Python 项目中实现多线程。祝你编码愉快!🎉

在本教程中,您将学习如何使用 Python 的内置线程模块探索 Python 的多线程功能。

了解多线程在 Python 中的工作原理,从进程和线程的基础知识开始,了解并发和并行的概念。接下来,了解如何使用内置threading模块在 Python 中启动和运行一个或多个线程。

让我们开始吧。

进程和线程:区别

流程是怎样的?

进程是需要运行的程序的实例

它可以是任何东西,从 Python 脚本和 Web 浏览器(如 Chrome)到视频会议应用程序。您可以通过在计算机上启动任务管理器并导航到性能–> CPU来查看当前正在 CPU 内核上运行的进程和线程。

cpu-proc-线程
cpu-proc-线程

了解进程和线程

在内部,进程有专用的内存来存储与进程相对应的代码和数据。

一个进程由一个或多个线程组成。线程是操作系统可以执行的最小指令序列,代表执行流程。

每个线程都有自己的堆栈和寄存器,但没有专用的内存。与该进程关联的所有线程都可以访问该数据。因此,数据和内存由进程的所有线程共享。

进程和线程
进程和线程

具有 N 个核心的 CPU 可以同时并行运行 N 个进程。但是,同一个进程的两个线程不能同时运行,但是可以并发运行。以下各节解释并发和并行的概念。

根据到目前为止所学的知识,我们来总结一下进程和线程之间的区别。

特征过程线
记忆专用内存共享内存
执行模式并行的,同时的同时;但不是并行
执行处理人操作系统CPython解释器

Python 中的多线程

在Python中, 全局解释器锁(GIL)确保任何时候只有一个线程可以获得锁并执行。所有线程都必须获得该锁才能执行。这允许任何时候只运行一个线程,避免同时多线程。

例如,考虑同一进程中的两个线程t1t2 。线程共享相同的数据,因此当t1读取特定值k时, t2可能会更改相同的值k 。这可能会导致僵局和不良结果。但是,任何时候只有一个线程可以获得锁并执行。因此,GIL也保证了线程安全

那么我们如何在Python中实现多线程呢?为了理解这一点,我们先讨论一下并发和并行的概念。

并发和并行:概述

考虑具有多个核心的 CPU。在下图中,CPU 有四个核心。这意味着可以随时并行执行四个不同的操作。

如果有四个进程,则每个进程可以在四个核心上独立且同时运行。假设每个进程有两个线程。

多核并行处理
多核并行处理

为了了解线程如何工作,让我们从多核处理器架构切换到单核处理器架构。如前所述,在给定的执行实例中只能有一个线程处于活动状态。然而,处理器核心可以切换线程。

代码
代码

例如,I/O 绑定线程经常等待 I/O 操作,例如读取用户输入、读取数据库和文件操作。在此等待时间内,I/O 绑定线程会释放其锁以允许其他线程运行。等待时间也可以是一个简单的操作,比如休眠n秒。

摘要:在等待操作期间,线程释放锁,从而允许处理器核心切换到另一个线程。一旦等待期结束,前一个线程就会恢复执行。处理器核心同时在线程之间切换的过程有利于多线程处理。 ✅

如果您想在应用程序中实现进程级并行性,请考虑使用多处理

Python 线程模块:第一步

Python 附带了一个threading模块,您可以将其导入到 Python 脚本中。

 import threading

要在 Python 中创建线程对象,可以使用Thread构造函数threading.Thread(...) 。对于大多数线程实现来说,这是足够通用的语法。

 threading.Thread(target=...,args=...)

这里,

  • target是一个关键字参数,指示 Python 可调用对象。
  • args是目标接收的参数元组。

运行本教程中的代码示例需要 Python 3.x。 下载代码并按照步骤操作。

在 Python 中定义和运行线程

让我们定义将执行目标函数的线程。

目标函数是some_func

 import threading
import time

def some_func():
    print("Running some_func...")
    time.sleep(2)
    print("Finished running some_func.")

thread1 = threading.Thread(target=some_func)
thread1.start()
print(threading.active_count())

我们来分析一下上面的代码片段做了什么。

  • 导入threadingtime模块。
  • 函数some_func有一个解释性的print()语句,并包含 2 秒的睡眠操作。 time.sleep(n)使函数休眠n秒。
  • 接下来,使用目标some_func定义线程thread_1threading.Thread(target=...)创建一个线程对象。
  • 注意:指定函数的名称,而不是函数调用。使用some_func而不是some_func()
  • 创建线程对象并不启动线程。当您调用线程对象的start()方法时执行。
  • 要获取活动线程数,请使用active_count()函数。

Python 脚本在主线程上运行,并创建另一个运行函数some_func的线程 ( thread1 )。因此,正如您在输出中看到的,活动线程数为 2。

 # Output
Running some_func...
2
Finished running some_func.

如果仔细观察输出,您会发现第一个 print 语句是在thread1启动时执行的。然而,在睡眠操作期间,处理器不会等待thread1完成执行,而是切换到主线程并打印活动线程的数量。

线程 1-ex
线程 1-ex

等待线程完成执行

如果想让thread1结束执行,可以在启动线程后调用join()方法。这样做会等到thread1完成执行而不会切换到主线程。

 import threading
import time

def some_func():
    print("Running some_func...")
    time.sleep(2)
    print("Finished running some_func.")

thread1 = threading.Thread(target=some_func)
thread1.start()
thread1.join()
print(threading.active_count())

在这里, thread1在打印活动线程数之前完成了执行。所以只有主线程在运行。即活动线程数为1。 ✅

 # Output
Running some_func...
Finished running some_func.
1

如何在Python中运行多线程

接下来,让我们创建两个执行两个不同功能的线程。

这里, count_down是一个函数,它接受一个数字作为参数,并从该数字倒数到 0。

 def count_down(n):
    for i in range(n,-1,-1):
        print(i)

定义count_up 。这是另一个从零计数到指定数字的 Python 函数。

 def count_up(n):
    for i in range(n+1):
        print(i)

📑 range()函数与range(start, stop, step)语法一起使用时,默认stop终点站。

– 要从特定数字倒数到零,请使用负step值 -1 并将stop值设置为 -1 以包含零。

– 同样,要计数到nstop值必须设置为n + 1startstep的默认值分别是0和1,因此可以使用range(n + 1)来获取从0到n的序列。

接下来,定义两个线程thread1thread2分别执行count_downcount_up函数。向这两个函数添加print语句和sleep操作。

请注意,创建线程对象时,必须在args参数中将目标函数参数指定为元组。这两个函数( count_downcount_up )都采用一个参数,因此您必须在值后面显式插入一个逗号。这会将后续元素推断为None ,因此参数仍作为元组传递。

 import threading
import time

def count_down(n):
    for i in range(n,-1,-1):
        print("Running thread1....")
        print(i)
        time.sleep(1)


def count_up(n):
    for i in range(n+1):
        print("Running thread2...")
        print(i)
        time.sleep(1)

thread1 = threading.Thread(target=count_down,args=(10,))
thread2 = threading.Thread(target=count_up,args=(5,))
thread1.start()
thread2.start()

在输出中,它看起来像这样:

  • 函数count_upthread2中执行,从0开始计数到5。
  • count_down函数在thread1上运行并从 10 倒数到 0。
 # Output
Running thread1....
10
Running thread2...
0
Running thread1....
9
Running thread2...
1
Running thread1....
8
Running thread2...
2
Running thread1....
7
Running thread2...
3
Running thread1....
6
Running thread2...
4
Running thread1....
5
Running thread2...
5
Running thread1....
4
Running thread1....
3
Running thread1....
2
Running thread1....
1
Running thread1....
0

可以看到thread1thread2交替运行,因为它们都需要等待操作(睡眠)。当count_up函数计数到 5 时, thread2不再处于活动状态。因此,您将仅获得与thread1相对应的输出。

概括

在本教程中,您学习了如何使用 Python 的内置线程模块实现多线程。要点总结如下。

  • 您可以使用Thread构造函数创建线程对象。 threading.Thread(target=<callable>,args=(<tuple of args>))创建一个线程,该线程使用args指定的参数运行目标可调用对象
  • Python程序在主线程上运行,因此您创建的线程对象是附加线程。调用active_count()函数会返回任何实例中的活动线程数。
  • 您可以使用线程对象的start()方法启动线程,并使用join()方法等待它执行完毕。

您可以通过调整等待时间或尝试不同的 I/O 操作来编写其他示例。请务必在未来的 Python 项目中实现多线程。祝你编码愉快!🎉