首页 > 编程语言 >【python学习】如何利用threading 库提升性能:深入解析与实战应用 模拟温格高的环法冠军之路

【python学习】如何利用threading 库提升性能:深入解析与实战应用 模拟温格高的环法冠军之路

时间:2024-08-17 10:55:33浏览次数:11  
标签:温格 thread python 赛段 threading 线程 stage

开头部分

大家好,今天通过一个实际的小项目——模拟温格高在2023年环法自行车赛中的比赛,来深入学习Python中的 threading 库。threading 是Python处理多线程操作的核心库,掌握它能够帮助我们更高效地进行并发编程,尤其是在处理I/O密集型任务时。我们不仅会讨论线程的基本使用方法,还将深入探讨一些高级特性和最佳实践,确保你在实际项目中能自信地使用多线程技术。

在开始之前,让我们先简单回顾一下环法自行车赛,以及温格高如何在2023年以卓越的表现赢得了比赛。


正文规范

1. 环法自行车赛与温格高的胜利

环法自行车赛是全球最著名的公路自行车赛事之一,每年吸引世界各地的顶尖车手参与竞争。2023年,丹麦车手温格高(Jonas Vingegaard)以优异的成绩赢得了冠军,他在每一个赛段都表现出色,以坚韧和速度征服了每一段艰难的赛道。

在今天的项目中,我们将通过Python的 threading 库来模拟温格高在环法不同赛段中的表现。每个赛段将由一个独立的线程进行模拟,最终我们将汇总所有赛段的成绩,看看温格高是如何一步步走向胜利的。

2. 线程的基本概念与threading库简介

什么是线程?

线程是程序中最小的执行单元,多个线程可以在同一个进程中并发执行。这意味着我们可以同时处理多个任务,例如在一个图形界面程序中同时响应用户输入和处理后台数据。

threading 库简介

Python 的 threading 库提供了一些基本的工具来管理和控制线程。我们可以创建、启动、暂停和停止线程,还可以使用同步原语(如锁、信号量)来避免多线程编程中的数据竞争问题。

3. 项目实现:模拟温格高的环法赛段表现
3.1 项目概述

我们将模拟温格高在不同赛段中的骑行,假设他每完成一个赛段都会耗费一定时间,并在完成后记录赛段成绩。为了体现多线程的并发执行效果,我们将让多个线程同时运行,分别代表温格高在不同赛段中的表现。

3.2 代码实现

首先,让我们来看一个简单的代码实现,模拟温格高在三个赛段的骑行表现:

import threading
import time
import random

# 赛段成绩记录
stage_results = {}

# 线程锁,确保线程同步
lock = threading.Lock()

def ride_stage(stage_name, duration):
    """
    模拟温格高在某个赛段的骑行。
    
    :param stage_name: 赛段名称
    :param duration: 骑行耗时(秒)
    """
    print(f"温格高开始赛段:{stage_name}")
    time.sleep(duration)  # 模拟骑行时间
    result = duration + random.uniform(-0.5, 0.5)  # 模拟随机误差
    
    # 使用锁确保线程安全地更新赛段成绩
    with lock:
        stage_results[stage_name] = result
    
    print(f"温格高完成赛段:{stage_name},用时:{result:.2f} 秒")

# 模拟三个赛段,每个赛段由不同的线程来完成
stages = [
    ("赛段1", 5),
    ("赛段2", 7),
    ("赛段3", 4)
]

threads = []

for stage_name, duration in stages:
    thread = threading.Thread(target=ride_stage, args=(stage_name, duration))
    threads.append(thread)
    thread.start()

# 等待所有线程完成
for thread in threads:
    thread.join()

# 输出最终成绩
print("\n温格高的赛段成绩:")
for stage_name, result in stage_results.items():
    print(f"{stage_name}: {result:.2f} 秒")

代码解析:

  1. 赛段成绩记录与锁机制:

    • 我们使用一个全局字典 stage_results 来记录每个赛段的成绩。由于多个线程会同时访问和修改这个字典,我们使用 threading.Lock 来确保线程同步。锁的作用是确保在同一时刻只有一个线程可以访问共享资源,从而避免数据竞争和不一致的问题。
  2. 模拟骑行过程:

    • 函数 ride_stage 接受两个参数:赛段名称和骑行时间。time.sleep() 用来模拟骑行的实际耗时,random.uniform() 则用来模拟现实中的随机误差。使用锁 (lock) 来确保只有一个线程能在同一时间更新成绩。
  3. 启动与管理线程:

    • 我们通过 threading.Thread 创建多个线程,每个线程代表温格高在不同赛段的骑行。start() 方法启动线程,join() 方法则等待所有线程完成执行。

输出结果:

在程序执行结束后,我们会看到温格高在每个赛段的骑行时间。多个线程并发执行,最终我们得到了每个赛段的成绩汇总。

温格高开始赛段:赛段1
温格高开始赛段:赛段2
温格高开始赛段:赛段3
温格高完成赛段:赛段3,用时:4.20 秒
温格高完成赛段:赛段1,用时:5.10 秒
温格高完成赛段:赛段2,用时:7.30 秒

温格高的赛段成绩:
赛段1: 5.10 秒
赛段2: 7.30 秒
赛段3: 4.20 秒
3.3 守护线程(Daemon Thread)

在某些情况下,我们可能希望线程在主线程终止时自动结束,这种线程称为守护线程。守护线程通常用于后台任务或定时器,这些任务的运行时间并不重要,只要主程序还在运行即可。我们可以通过将线程的 daemon 属性设置为 True 来使其成为守护线程。

def background_task():
    while True:
        print("后台任务正在运行...")
        time.sleep(2)

# 创建守护线程
daemon_thread = threading.Thread(target=background_task)
daemon_thread.daemon = True
daemon_thread.start()

print("主线程完成")

在这个例子中,后台任务作为守护线程不断运行,而主线程执行完毕后,守护线程也会自动终止。

输出示例:

后台任务正在运行...
主线程完成

因为主线程完成后,守护线程自动结束,所以你可能只看到一次“后台任务正在运行…”的输出。

3.4 线程池(ThreadPoolExecutor)

当需要处理大量短时间任务时,使用线程池(如 concurrent.futures.ThreadPoolExecutor)是一个更高效的选择。线程池可以管理线程的复用,避免频繁创建和销毁线程带来的开销。

from concurrent.futures import ThreadPoolExecutor

def ride_stage_pool(stage_name, duration):
    print(f"温格高开始赛段:{stage_name}")
    time.sleep(duration)
    result = duration + random.uniform(-0.5, 0.5)
    print(f"温格高完成赛段:{stage_name},用时:{result:.2f} 秒")
    return result

# 使用线程池来管理多个赛段
with ThreadPoolExecutor(max_workers=3) as executor:
    futures = [executor.submit(ride_stage_pool, f"赛段{i+1}", random.randint(3, 6)) for i in range(3)]

# 处理线程池返回的结果
for future in futures:
    print(f"赛段结果:{future.result():.2f} 秒")

在这个例子中,我们使用 ThreadPoolExecutor 来管理多个赛段的执行,并且可以方便地收集每个赛段的返回结果。

3.5 条件变量(Condition)

条件变量是更复杂的同步机制,允许线程等待某些条件的发生。使用 threading.Condition 可以让线程在满足特定条件时协调工作。我们来看一个简单的生产者-消费者模型。

condition = threading.Condition()
queue = []

def producer():
    global queue
    while True:
        with condition:
            item = random.randint(0, 100)
            queue.append(item)
            print(f"生产者生成了:{item}")
            condition.notify()  # 通知消费者
        time.sleep(2)

def consumer():
    global queue
    while True:
        with condition:
            condition.wait()  # 等待生产者通知
            item = queue.pop(0)
            print(f"消费者消费了:{item}")

# 启动生产者和消费者线程
threading.Thread(target=producer).start()
threading.Thread(target=consumer).start()

在这个例子中,生产者线程生成一个数字并通知消费者,而消费者线程则等待通知并消费该数字。

3.6 事件对象(Event)

threading.Event 是另一种线程间通信的机制。Event 对象允许一个线程等待其他线程的信号。

event = threading.Event()

def starter():
    print("准备开始比赛...")
    time.sleep(3)
    print("开始!")
    event.set()  # 发出信号

def runner():
    print("等待开始信号...")
    event.wait()  # 等待信号
    print("比赛进行中!")

# 启动比赛的开始和跑步者线程
threading.Thread(target=starter).start()
threading.Thread(target=runner).start()

在这个例子中,runner 线程会等待 starter 线程的信号,只有当 starter 线程调用 event.set() 之后,runner 线程才会继续执行。

3.7 线程中断与退出

在多线程编程中,我们通常需要安全地中断或停止线程。Python不支持直接中断线程,但可以使用共享变量或 Event 对象来实现线程的安全退出。

exit_flag = False

def long_running_task():
    global exit_flag
    while not exit_flag:
        print("任务运行中...")
        time.sleep(1)
    print("任务结束")

thread = threading.Thread(target=long_running_task)
thread.start()

# 等待5秒后设置退出标志
time.sleep(5)
exit_flag = True
thread.join()

通过设置 exit_flagTrue,我们可以通知线程安全地退出。

3.8 GIL(全局解释器锁)与多线程的局限性

Python的GIL(Global Interpreter Lock)限制了同一时刻只有一个线程能执行Python字节码,这对CPU密集型任务的多线程并行处理造成了限制。GIL的存在意味着在Python中,threading 更适合处理I/O密集型任务,而不是CPU密集型计算。

解决方案:

对于CPU密集型任务,可以考虑使用 multiprocessing 模块,它通过创建独立的进程来绕过GIL限制,从而充分利用多核CPU。

3.9 上下文管理器与锁

我们之前介绍了锁的基本使用,但没有提到如何通过上下文管理器简化锁的使用。上下文管理器(通过 with 语句)可以确保锁在代码块执行后自动释放,避免因异常导致锁未被释放的情况。

def safe_increment():
    with lock:
        global counter
        counter += 1

通过 with lock 语句,确保在 safe_increment 执行完毕后自动释放锁。

3.10 定时器线程(Timer)

threading.Timer 是一种用于延迟执行任务的线程。它可以在指定的时间后执行某个函数,非常适合需要延迟执行的任务。

def delayed_start():
    print("延迟任务开始...")

timer = threading.Timer(5, delayed_start)
timer.start()

print("等待5秒后执行任务")

在这个例子中,delayed_start 函数将在5秒后执行。

3.11 线程本地数据(Thread-Local Data)

threading.local() 提供了一种方式来为每个线程存储独立的数据,避免线程之间的数据污染。

local_data = threading.local()

def process_data():
    local_data.value = random.randint(0, 100)
    print(f"线程 {threading.current_thread().name} 的数据:{local_data.value}")

threads = []
for i in range(5):
    thread = threading.Thread(target=process_data)
    threads.append(thread)
    thread.start()

for thread in threads:
    thread.join()

在这个例子中,每个线程都有自己独立的 local_data.value,互不影响。

3.12 多线程的调试与监控

多线程程序的调试比单线程程序更具挑战性。建议使用日志记录或调试工具来监控线程的执行情况。

import logging

logging.basicConfig(level=logging.DEBUG, format='%(threadName)s: %(message)s')

def threaded_function():
    logging.debug("线程开始")
    time.sleep(2)
    logging.debug("线程结束")

threads = []
for i in range(3):
    thread = threading.Thread(target=threaded_function, name=f"Thread-{i+1}")
    threads.append(thread)
    thread.start()

for thread in threads:
    thread.join()

通过日志,我们可以更清晰地看到每个线程的执行情况。


其他技巧

多线程编程并不总是简单易行的,它涉及到并发操作的许多细节,尤其是在数据共享和同步问题上。建议大家多参考优秀的开源项目,学习他人的最佳实践,并通过实验来不断优化自己的代码。


结尾

通过这个扩展的项目和深入的探讨,我们详细讲解了Python threading 库的核心功能及其高级用法。多线程编程在实际项目中能够显著提升程序的性能,但也要求我们对线程同步和线程安全问题有深入的理解和有效的应对策略。

希望这篇文章能够帮助大家更好地掌握Python中的多线程编程技巧。如果你有任何问题或想要更深入地探讨相关内容,欢迎随时与我联系,期待在课堂上看到大家的精彩表现!


参考资料

标签:温格,thread,python,赛段,threading,线程,stage
From: https://blog.csdn.net/m0_54007171/article/details/141270000

相关文章

  • 【python学习】Asyncio库的真正力量:如何用Python编写高效并发程序
    目标读者群体及文章解决的问题这篇文章适合对Python编程有一定了解的开发者,尤其是希望深入理解并掌握异步编程的读者。通过对asyncio库的全面解析,我们将结合2023年环法冠军温格高的夺冠故事,帮助你掌握如何在Python中使用异步编程处理复杂的并发任务,避免常见的错误,并提高代......
  • Python解释器如何下载+如何安装+配置环境+踩坑 一文搞定【保姆级图文教程】
    如果你要学Python,那么第一件事情就是先去安装。因为你的电脑里面没有Python,编写好的.py文件就没有解释器可以运行,所以安装Python环境/解释器就是最重要的一件事。PS:本文仅为笔记,为个人整理而成,如有不足之处请多多指正。目录第一步:下载1.官方网站2.镜像源网站第二步......
  • 基于Python+Flask实现宿舍管理系统
    作者主页:编程千纸鹤作者简介:Java领域优质创作者、CSDN博客专家、CSDN内容合伙人、掘金特邀作者、阿里云博客专家、51CTO特邀作者、多年架构师设计经验、多年校企合作经验,被多个学校常年聘为校外企业导师,指导学生毕业设计并参与学生毕业答辩指导,有较为丰富的相关经验。期待......
  • 基于Python爬虫实现招聘数据分析可视化大屏
    作者简介:Java领域优质创作者、CSDN博客专家、CSDN内容合伙人、掘金特邀作者、阿里云博客专家、51CTO特邀作者、多年架构师设计经验、多年校企合作经验,被多个学校常年聘为校外企业导师,指导学生毕业设计并参与学生毕业答辩指导,有较为丰富的相关经验。期待与各位高校教师、企业......
  • 快速排序算法详解及Python实现
    目录引言快速排序算法步骤快速排序的Python实现性能分析注意事项引言快速排序(QuickSort)是一种高效的排序算法,由C.A.R.Hoare在1960年提出。它的基本思想是:通过一趟排序将待排序的数据分割成独立的两部分,其中一部分的所有数据都比另一部分的所有数据要小,然后再按此......
  • python入门篇-day04-函数
    介绍概述函数也叫方法,可以用单词Function(函数,功能),Method(方法)来表示.函数=把具有独立功能的代码封装到一起,使其成为具有特殊功能的代码集.好处提到代码的复用性.模块化编程.格式def函数名(形式参数1,形参2...):  函数体,就是以前写的逻辑代......
  • python入门篇-day05-函数进阶
    函数返回多个值不管返回多少都会默认封装成一个元组的形式返回(可以自定义返回的数据类型,如:[]{})思考:1个函数可以同时返回多个结果(返回值)?答案:错误,因为1个函数只能返回1个结果,如果同时返回多个值则会封装成1个元素进行返回.演示#需求:定义函数my_cal......
  • Python教程(十五):IO 编程
    目录专栏列表引言基础概念什么是IO?同步IOvs异步IO同步IO(SynchronousIO)异步IO(AsynchronousIO)Python中的IO标准IO标准输入和输出文件IO文件操作的上下文管理器打开文件读取文件操作内存中的数据高级文件操作读写二进制文件使用文件指针网络IO使用`requests`库使用......
  • python自学
    基础语法字面量定义:在代码中,被写下来的值,称之为字面量类型:python中常用的有6种数据类型数字整数(int)浮点数(float)复数(complex),如4+3j布尔(bool):True本质为1,False本质为0字符串(String)字符串需要使用'xxx',"xxx"或者'''xxx'''格式,而三引号中的字符串可以跨行。......
  • python循环语句之while循环和for循环
    文章目录1.while循环1.1介绍1.1.1生活中的循环1.1.2程序中的循环1.2总结2.while循环应用:1~100求和2.1需求2.2分析3.while嵌套循环以及运用3.1while嵌套循环语句的语法格式3.2while嵌套循环使用3.2.1要求3.2.2参考代码4.for循环4.1for循环基本格式4.......