Python线程详解

  • Post category:Python

Python线程详解

什么是线程

线程是一个执行流,是操作系统能够进行运算调度的最小单位。线程针对同一个进程中的不同执行流,它们共享进程的内存和上下文,所以线程间的信息可以非常容易地共享。

在Python中,可以通过threading模块来创建和管理线程。

创建线程

创建线程有两种方式,一种是直接使用threading模块中的Thread类,另一种是通过继承Thread类来创建自定义线程类,下面我们将分别进行讲解。

使用Thread类创建线程

要使用Thread类创建线程,需要实例化一个Thread对象,并传入线程执行的函数以及函数的参数。下面是一个示例:

import threading

def worker(num):
    """线程执行的函数"""
    print("我是线程%d" % num)

threads = []
for i in range(5):
    t = threading.Thread(target=worker, args=(i,))
    threads.append(t)
    t.start()

在上面的代码中,我们创建了5个线程,每个线程都执行worker函数,打印出自己的编号。

继承Thread类创建线程

通过继承Thread类,我们可以自定义线程类,在其中实现线程执行的函数,然后通过实例化自定义线程类来创建线程。下面是一个示例:

import threading

class MyThread(threading.Thread):
    def __init__(self, num):
        threading.Thread.__init__(self)
        self.num = num

    def run(self):
        """线程执行的函数"""
        print("我是线程%d" % self.num)

threads = []
for i in range(5):
    t = MyThread(i)
    threads.append(t)
    t.start()

在上面的代码中,我们定义了一个MyThread类,继承了Thread类,并重写了run方法,该方法就是线程执行的函数。我们通过实例化MyThread类来创建线程。

线程同步

多个线程访问同一个变量时,可能会造成数据的不一致,因此需要使用线程同步,避免数据竞争。Python提供了多种线程同步方式,如Lock、Semaphore、Condition等。

下面我们将以Lock为例进行讲解。

import threading

# 定义全局变量num
num = 0
# 定义一个互斥锁
lock = threading.Lock()

def worker():
    global num
    # 获取锁
    lock.acquire()
    for i in range(100000):
        num += 1
    # 释放锁
    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(num)

在上面的代码中,我们定义了一个全局变量num,并使用互斥锁lock保护对num的访问。每个线程都执行worker函数,该函数对num进行加1操作。最后我们将num输出,期望结果为500000。

示例说明

示例一:使用线程请求网页

下面是一个示例,我们使用线程来请求多个网页,并将结果输出到控制台中。

import requests
import threading

def request_url(url):
    """请求网页"""
    r = requests.get(url)
    print("[%d] %s %s" % (threading.get_ident(), url, len(r.content)))

urls = [
    "https://www.baidu.com",
    "https://www.sogou.com",
    "https://cn.bing.com",
    "https://www.google.com",
    "https://www.yahoo.com",
    "https://www.taobao.com"
]

threads = []
for url in urls:
    t = threading.Thread(target=request_url, args=(url,))
    threads.append(t)
    t.start()

for t in threads:
    t.join()

在上面的代码中,我们定义了一个request_url函数,用于请求网页。我们创建了6个线程,分别请求6个网页,并在控制台中输出线程id、网址和返回内容长度。

示例二:使用线程下载文件

下面是另一个示例,我们使用线程来下载多个文件,并将下载进度输出到控制台中。

import requests
import threading

def download(url, filename):
    """下载文件"""
    r = requests.get(url, stream=True)
    with open(filename, "wb") as f:
        for chunk in r.iter_content(chunk_size=1024):
            if chunk:
                f.write(chunk)

urls = [
    "https://download.jetbrains.com/python/pycharm-community-2021.3.1.exe",
    "https://download.sublimetext.com/Sublime%20Text%20Build%203195%20x64.zip",
    "https://cdn.mysql.com//Downloads/MySQLInstaller/mysql-installer-community-8.0.28.0.msi",
    "https://cdn.npm.taobao.org/dist/node/v17.4.0/node-v17.4.0-x64.msi",
    "https://aka.ms/win32-xmrig-amd64-6.17.1.zip",
    "https://support.apple.com/downloads/DL2097/en_US/safari14.3bigsur.dmg"
]

threads = []
for i, url in enumerate(urls):
    t = threading.Thread(target=download, args=(url, "file%d" % i,))
    threads.append(t)
    t.start()

while any([t.is_alive() for t in threads]):
    progress = [t.name + ("(running)" if t.is_alive() else "(finished)") for t in threads]
    print("\r" + " ".join(progress), end="")

在上面的代码中,我们定义了一个download函数,用于下载文件。我们创建了6个线程,分别下载6个文件,并在控制台中输出下载进度。每个线程执行下载时,将文件保存到本地,以文件编号作为文件名。在最后,我们使用while循环等待所有线程执行完成,并输出线程的状态。