Python线程:简介

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

从进程和线程的基础知识开始,您将了解Python中的多线程工作原理,同时理解并发性和并行性的概念。然后,您将学习如何使用内置的threading模块在Python中启动和运行一个或多个线程。

让我们开始吧。

进程 vs. 线程:区别

什么是进程?

进程是需要运行的程序的任何实例。

它可以是任何东西-比如一个Python脚本或一个网页浏览器(如Chrome)或视频会议应用程序。如果在计算机上启动任务管理器并导航到性能-> CPU,您将能够看到当前正在运行在CPU核心上的进程和线程。

了解进程和线程

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

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

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

在具有N个核心的CPU上,N个进程可以同时并行执行。但是,同一进程的两个线程永远不能同时执行,但可以并发执行。我们将在下一节讨论并发性与并行性的概念。

基于我们目前学到的知识,让我们总结一下进程和线程之间的区别。

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

Python中的多线程

在Python中,Global Interpreter Lock (GIL)确保只有一个线程可以获取锁并在任何时刻运行。所有线程都应该获取此锁才能运行。这确保了在任何给定的时间点上只能有一个线程在执行,并避免了同时多线程。

例如,考虑同一进程的两个线程t1t2。因为线程共享相同的数据,当t1正在读取特定值k时,t2可能会修改同样的值k。这可能导致死锁和不希望的结果。但是在任何时刻只有一个线程可以获取锁并运行。因此,GIL也确保了线程安全

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

并发性 vs. 并行性:概述

考虑一个具有多个核心的CPU。在下面的示例中,CPU有四个核心。这意味着我们可以在任何给定的时刻并行运行四个不同的操作。

如果有四个进程,那么每个进程都可以独立地并同时在四个核心上运行。假设每个进程都有两个线程。

为了理解线程如何工作,让我们从多核处理器架构切换到单核处理器架构。正如前面提到的,每次执行实例只能有一个线程处于活动状态,但处理器核心可以在不同线程之间切换。

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

总结一下:在等待操作期间,线程释放锁,使处理器核心可以切换到另一个线程。等待时间结束后,之前的线程恢复执行。处理器核心在不同线程之间并发切换的这个过程促进了多线程。

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

Python线程模块:入门

Python附带了一个可以导入到Python脚本中的线程模块。

要在Python中创建一个线程对象,可以使用Thread构造函数:threading.Thread(…)。这是大多数线程实现所适合的通用语法:

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

在这里,

target是指定Python可调用对象的关键字参数
args是目标函数接收的参数元组。

要运行本教程中的代码示例,您需要Python 3.x和Download the code

定义和运行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())

让我们解析一下上面的代码片段的功能:

导入threading和time模块。
函数some_func具有描述性的print()语句,并包括一个两秒的睡眠操作:time.sleep(n)使函数睡眠n秒。
接下来,我们使用some_func作为目标定义一个线程thread_1。threading.Thread(target=…)创建一个线程对象。
注意:指定函数的名称而不是函数调用;使用some_func而不是some_func()。
创建线程对象不会启动线程;调用线程对象上的start()方法会启动线程。
使用active_count()函数获取活动线程的数量。
Python脚本正在主线程上运行,我们正在创建另一个线程(thread1)来运行函数some_func;因此,活动线程数为2,如输出所示:

# 输出
Running some_func…
2
Finished running some_func.

如果我们仔细观察输出,可以看到在启动thread1后,第一个打印语句被执行。但是在睡眠操作期间,处理器切换到主线程并打印出活动线程的数量,而不等待thread1执行完成。

等待线程完成执行

如果您希望thread1完成执行,可以在启动线程后调用join()方法。这样做将等待thread1完成执行,而不切换到主线程。

import threading
import time

def some_func():
    print("运行some_func...")
    time.sleep(2)
    print("some_func运行完成。")

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

现在,thread1在我们打印出活动线程数之前已经执行完成。因此,只有主线程在运行,这意味着活动线程数为1。✅

# 输出
运行some_func...
some_func运行完成。
1

如何在Python中运行多个线程

接下来,让我们创建两个线程来运行两个不同的函数。

这里,count_down是一个以一个数字作为参数的函数,并从该数字倒数到零。

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,以包括零。

– 类似地,要计数到n,您必须将stop值设置为n + 1。因为startstep的默认值分别为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("运行thread1....")
        print(i)
        time.sleep(1)


def count_up(n):
    for i in range(n+1):
        print("运行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。
# 输出
运行thread1....
10
运行thread2...
0
运行thread1....
9
运行thread2...
1
运行thread1....
8
运行thread2...
2
运行thread1....
7
运行thread2...
3
运行thread1....
6
运行thread2...
4
运行thread1....
5
运行thread2...
5
运行thread1....
4
运行thread1....
3
运行thread1....
2
运行thread1....
1
运行thread1....
0

你可以看到thread1thread2交替执行,因为它们都涉及到等待操作(睡眠)。一旦count_up函数计数到5,thread2就不再活动。所以我们得到与thread1相对应的输出。

总结

在本教程中,你学会了如何使用Python的内置线程模块实现多线程。以下是主要要点的总结:

  • Thread构造函数可用于创建线程对象。使用threading.Thread(target=,args=())创建一个以target可调用对象和args参数指定的线程。
  • Python程序在主线程上运行,因此你创建的线程对象是额外的线程。你可以调用active_count()函数在任何时刻返回活动线程的数量。
  • 你可以使用线程对象上的start()方法启动线程,并使用join()方法等待它执行完成。

你可以通过调整等待时间、尝试不同的I/O操作等来进行调试。记得在即将到来的link_4中实现多线程。愉快编码!🎉

类似文章