Python教程:一文掌握Python多线程(很详细)

目录

1.什么是多线程?

1.1多线程与单线程的区别

1.2 Python 中的多线程实现方式

2.使用 threading 模块创建和管理线程

2.1创建线程:Thread 类的基本用法

2.2线程的启动和执行:start() 方法

2.3线程的同步和阻塞:join() 方法

2.4线程的名称和标识:name 和 ident 属性

3.线程间的通信与同步

3.1使用 Lock 实现线程同步

3.2使用 Queue 实现线程间通信

4.线程池的使用

5.理解全局解释器锁(GIL)对多线程的影响

5.1作用和原理:

5.2对多线程并发执行的限制:

6.多线程中的常见问题与解决方法

6.1死锁(Deadlock)的原因及避免方法:

6.2线程间数据共享与安全访问:

7.线程越多越好么?

8.面试问题:解析Python中的GIL(全局解释器锁)是什么?它如何影响多线程编程?

问题描述:

详细答案:

 9.多线程实战示例

9.1项目说明:

9.2完整代码示例:

9.3代码说明:


1.什么是多线程?


多线程是指在同一进程内同时运行多个线程,每个线程执行不同的任务,实现并发执行。每个线程都有自己的执行路径,可以独立运行和调度,共享进程的资源。

1.1多线程与单线程的区别

  • 单线程: 指在程序中只有一个执行线程,按照顺序依次执行任务。单线程模型简单直观,但无法充分利用多核处理器的性能。

  • 多线程: 可以同时执行多个线程,提高程序的响应速度和效率。多线程模型适用于需要同时处理多个任务或需要利用多核处理器的场景。

1.2 Python 中的多线程实现方式

Python 提供了 threading 模块来支持多线程编程。通过创建 Thread 对象并指定目标函数,可以实现线程的创建和管理。以下是一个简单的示例:

import threadingdef print_numbers():for i in range(1, 6):print(i)# 创建线程
t = threading.Thread(target=print_numbers)# 启动线程
t.start()# 主线程继续执行其他任务

2.使用 threading 模块创建和管理线程


2.1创建线程:Thread 类的基本用法

在 Python 中,可以使用 threading 模块中的 Thread 类来创建线程。以下是创建线程的基本用法:

import threadingdef my_function():print("This is a thread.")# 创建线程
my_thread = threading.Thread(target=my_function)

2.2线程的启动和执行:start() 方法

通过调用线程对象的 start() 方法来启动线程,让线程开始执行目标函数中的代码:

my_thread.start()

2.3线程的同步和阻塞:join() 方法

可以使用 join() 方法来等待线程执行完成,实现线程的同步和阻塞:

my_thread.join()
print("Thread has finished.")

2.4线程的名称和标识:name 和 ident 属性

每个线程都有一个名称和一个唯一的标识符。可以通过 name 属性获取线程的名称,通过 ident 属性获取线程的标识符:

print("Thread name:", my_thread.name)
print("Thread identifier:", my_thread.ident)

通过以上方法,可以创建、启动和管理线程,并实现线程间的同步和阻塞操作。线程的名称和标识符可以帮助我们对线程进行标识和跟踪,便于调试和管理多线程程序。

3.线程间的通信与同步


3.1使用 Lock 实现线程同步

在多线程编程中,为了避免多个线程同时访问共享资源导致数据混乱或不一致的问题,可以使用 Lock 对象实现线程同步。下面是一个简单的示例:

import threadingcounter = 0
lock = threading.Lock()def increment_counter():global counterwith lock:counter += 1# 创建多个线程并启动
threads = []
for _ in range(5):t = threading.Thread(target=increment_counter)t.start()threads.append(t)# 等待所有线程执行完成
for t in threads:t.join()print("Final counter value:", counter)

在上面的示例中,通过 Lock 对象确保了 counter 变量的原子性操作,避免了多线程同时修改导致的问题。

3.2使用 Queue 实现线程间通信

另一种常见的方式是使用 Queue 实现线程间的通信。Queue 是线程安全的数据结构,可以在多个线程之间安全地传递数据。以下是一个简单的示例:

import threading
import queuedef producer(q):for i in range(5):q.put(i)def consumer(q):while not q.empty():item = q.get()print("Consumed:", item)# 创建队列
q = queue.Queue()# 创建生产者和消费者线程
producer_thread = threading.Thread(target=producer, args=(q,))
consumer_thread = threading.Thread(target=consumer, args=(q))# 启动线程
producer_thread.start()
consumer_thread.start()# 等待线程执行完成
producer_thread.join()
consumer_thread.join()

在上面的示例中,通过 Queue 实现了生产者和消费者模式,生产者向队列中放入数据,消费者从队列中取出数据进行消费,实现了线程间的通信。

4.线程池的使用


使用 ThreadPoolExecutor 可以方便地管理线程池,控制线程数量并提交任务。以下是 ThreadPoolExecutor 的基本用法示例:

from concurrent.futures import ThreadPoolExecutor
import time# 定义一个任务函数
def task(n):print(f"Task {n} started")time.sleep(2)return f"Task {n} completed"# 创建 ThreadPoolExecutor
with ThreadPoolExecutor(max_workers=3) as executor:  # 控制线程池大小为3# 提交任务给线程池future1 = executor.submit(task, 1)future2 = executor.submit(task, 2)future3 = executor.submit(task, 3)# 获取任务执行结果print(future1.result())print(future2.result())print(future3.result())

在上面的示例中,通过 ThreadPoolExecutor 创建了一个最大容纳3个线程的线程池。然后使用 submit() 方法提交了三个任务,并使用 result() 方法获取任务执行结果。

如果想要控制线程池的大小,可以将 max_workers 参数设置为所需的线程数量。通过 ThreadPoolExecutor 可以方便地管理线程池,提高多线程编程的效率。

5.理解全局解释器锁(GIL)对多线程的影响


全局解释器锁(GIL)是在 CPython 解释器中使用的一种机制,它对多线程并发执行产生了一定的限制和影响。以下是关于 GIL 的作用、原理以及对多线程并发执行的限制:

5.1作用和原理:

  • 作用: GIL 的作用是确保在解释器级别上,同一时刻只有一个线程可以执行 Python 字节码。这意味着在多核处理器上,Python 程序不能利用多个 CPU 核心同时执行线程。
  • 原理: 在 CPython 中,GIL 是由一个互斥锁来实现的。当一个线程获得了 GIL 后,其他线程就无法在同一时间执行 Python 字节码,直到持有 GIL 的线程释放锁。

5.2对多线程并发执行的限制:

  1. 性能影响: 由于同一时刻只有一个线程可以执行 Python 字节码,因此在多核 CPU 上,并发执行的效率受到限制。特别是对于计算密集型的多线程任务,GIL 可能导致性能瓶颈。
  2. IO 密集型任务的影响较小: 对于涉及大量 IO 操作的线程,GIL 的影响相对较小,因为在 IO 操作时,线程会主动释放 GIL,让其他线程执行。
  3. 影响解决方案: 为了充分利用多核 CPU,可以使用多进程、使用其他语言的扩展模块(如使用 C/C++ 编写的扩展模块)或者使用异步编程(如 asyncio)等方式来规避 GIL 的影响。

总之,GIL 的存在使得在 CPython 中的多线程并发执行受到了一定的限制,开发者需要根据具体情况选择合适的解决方案来充分利用多核 CPU 资源。

6.多线程中的常见问题与解决方法


当涉及到多线程编程中的常见问题如死锁(Deadlock)以及线程间数据共享与安全访问时,以下是代码示例和解释:

6.1死锁(Deadlock)的原因及避免方法:

原因: 死锁是指两个或多个线程互相等待对方释放资源而无法继续执行的情况。

避免方法: 避免死锁的一种常见方法是确保线程获取资源的顺序是一致的,或者使用超时机制来打破死锁。下面是一个简单的示例:

import threading# 创建两个锁
lock1 = threading.Lock()
lock2 = threading.Lock()def thread1():lock1.acquire()print("Thread 1 acquired lock 1")# 假设这里需要一段时间lock2.acquire()print("Thread 1 acquired lock 2")lock2.release()lock1.release()def thread2():lock2.acquire()print("Thread 2 acquired lock 2")# 假设这里需要一段时间lock1.acquire()print("Thread 2 acquired lock 1")lock1.release()lock2.release()t1 = threading.Thread(target=thread1)
t2 = threading.Thread(target=thread2)t1.start()
t2.start()t1.join()
t2.join()

在上述示例中,如果 thread1thread2 同时运行,由于它们试图以不同的顺序获取锁,可能导致死锁。为了避免死锁,可以尝试统一锁的获取顺序。

6.2线程间数据共享与安全访问:

在多线程编程中,线程之间共享数据时需要确保线程安全,可以使用互斥锁来保护共享资源。下面是一个简单的示例:

import threadingcounter = 0
lock = threading.Lock()def increment_counter():global counterfor _ in range(100000):lock.acquire()counter += 1lock.release()threads = []
for _ in range(10):t = threading.Thread(target=increment_counter)threads.append(t)for t in threads:t.start()for t in threads:t.join()print("Final counter value:", counter)

在这个示例中,我们使用互斥锁 lock 来确保对 counter 全局变量的安全访问。每个线程在增加 counter 值之前获取锁,操作完成后释放锁,从而避免竞态条件。这样可以确保线程安全地访问共享资源。

7.线程越多越好么?


在Python中使用多线程可以提高程序的并发性,但并不意味着线程越多越好。这是因为 Python 中的全局解释器锁(Global Interpreter Lock,GIL)限制了同一时间只有一个线程可以执行 Python 字节码,因此多线程并不能充分利用多核处理器的优势。

虽然多线程在某些场景下可以提高效率,比如I/O密集型任务,但如果是CPU密集型任务(如大量计算),多线程并不能有效提升性能。此外,线程数过多也会增加线程切换的开销,并可能导致系统资源竞争和调度开销,进而影响整体性能。

因此,在决定使用多线程时,需要考虑以下几点:

  1. 任务类型:针对不同类型的任务选择合适的并发模型,如I/O密集型任务可以考虑使用多线程,而CPU密集型任务可能更适合使用多进程。
  2. 平台和环境:要考虑程序运行的平台和环境对多线程的支持情况,以及是否受到 GIL 的影响。
  3. 系统资源:合理评估系统资源(CPU、内存等)的使用情况,避免过多线程导致资源浪费和性能下降。

综上所述,虽然多线程可以在适当的情况下提高程序的并发性和效率,但并不意味着线程越多越好。在实际应用中,需要根据具体情况慎重考虑线程数量,并结合任务类型、系统资源和性能需求进行合理的设计和调优。

8.面试问题:解析Python中的GIL(全局解释器锁)是什么?它如何影响多线程编程?


问题描述:

请解释 Python 中的 GIL 是什么,以及它如何影响多线程编程。同时,讨论在受 GIL 限制的情况下如何提高 Python 多线程程序的性能。

详细答案:
  • GIL 是什么?

    • GIL 是全局解释器锁(Global Interpreter Lock)的缩写,是 Python 解释器中的一个机制。它的作用是保证在解释器级别同一时刻只有一个线程执行 Python 字节码,从而防止多线程同时执行字节码导致的数据竞争和不一致性。
  • GIL 对多线程编程的影响:

    • 由于 GIL 的存在,Python 中的多线程无法利用多核处理器来实现真正的并行执行。即使有多个线程,它们依然是以串行的方式执行,因为同一时刻只有一个线程能够获取到 GIL。
  • 提高 Python 多线程程序性能的方法:

    • 使用多进程替代多线程: Python 中的多进程可以绕过 GIL 的限制,实现真正的并行执行。
    • 使用 C 扩展或 Cython: 将性能关键的部分使用 C 语言或 Cython 编写,可以减少对 GIL 的依赖,提高性能。
    • 使用异步编程: 使用异步编程库(如 asyncio、aiohttp)可以在不受 GIL 影响的情况下实现并发执行。

 9.多线程实战示例

当涉及到使用Python多线程的实际项目示例时,一个常见的场景是同时下载多个文件并将它们保存到本地。在这个示例中,我们将创建一个简单的多线程下载器,每个线程负责下载一个文件,并最后将它们保存到本地。

9.1项目说明:

我们将使用Python的threading模块来实现多线程下载器。每个线程将会下载一个文件,然后将文件保存到指定的目录。为了模拟真实下载过程,我们会使用一个虚拟的文件URL列表。

9.2完整代码示例:

import threading
import requests
import os# 虚拟文件URL列表
file_urls = ["https://www.example.com/file1.txt","https://www.example.com/file2.txt","https://www.example.com/file3.txt"
]# 下载函数
def download_file(url, save_path):response = requests.get(url)with open(save_path, 'wb') as file:file.write(response.content)print(f"Downloaded {url} and saved to {save_path}")# 下载器类
class DownloaderThread(threading.Thread):def __init__(self, url, save_path):threading.Thread.__init__(self)self.url = urlself.save_path = save_pathdef run(self):download_file(self.url, self.save_path)# 创建保存文件的目录
download_dir = "downloads"
os.makedirs(download_dir, exist_ok=True)# 创建并启动多个下载线程
threads = []
for i, url in enumerate(file_urls):file_name = f"file{i+1}.txt"save_path = os.path.join(download_dir, file_name)downloader = DownloaderThread(url, save_path)threads.append(downloader)downloader.start()# 等待所有线程完成下载
for thread in threads:thread.join()print("All downloads completed!")

9.3代码说明:

  1. 定义了一个download_file函数用于下载文件,并将其保存到本地。
  2. 创建了一个DownloaderThread类,继承自threading.Thread,用于表示下载线程。每个线程负责下载一个文件。
  3. 创建了一个download_dir目录用于保存下载的文件。
  4. 遍历虚拟文件URL列表,为每个文件创建一个下载线程,并启动下载。
  5. 最后等待所有线程完成下载,并打印提示信息。

通过这个示例,您可以看到如何使用Python多线程实现一个简单的文件下载器,同时了解了如何在实际项目中应用多线程进行并发处理。请注意,对于大规模文件下载或需要更复杂逻辑的情况,可能需要进一步优化和改进。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2024年华为OD机试真题-图像物体的边界-Java-OD统一考试(C卷)

题目描述: 给定一个二维数组M行N列,二维数组里的数字代表图片的像素,为了简化问题,仅包含像素1和5两种像素,每种像素代表一个物体,2个物体相邻的格子为边界,求像素1代表的物体的边界个数。 像素1代表的物体的边界指与像素5相邻的像素1的格子,边界相邻的属于同一个边界,…

docker 安装Sentinel

1.拉取镜像:docker pull bladex/sentinel-dashboard 2.运行镜像:docker run --name sentinel -d -p 8858:8858 -d bladex/sentinel-dashboard 3. 访问地址: 本地地址:http://localhost:8858 (默认端口为8080) 远程地址&#xf…

【学习】软件测试人员如何设计出优秀的测试用例

在软件开发的过程中,测试用例如同工程质量的守护者,它们的存在确保了软件产品的稳定性和可靠性。然而,如何设计出优秀的测试用例,让其在千变万化的软件世界中独领风骚,成为众多测试工程师追寻的目标。本文将为你揭示其…

Android RecyclerView 滑动后选中的条目居中显示

话不多说先看效果: 实录效果视频如下 滚动居中 RecyclerView 在原有的RecyclerView 基础上操作,其他步骤不变,只是替换一下 manager 步骤 导入依赖 maven { url https://www.jitpack.io }//无限滚动implementation com.github.ZhaoChanghu:GalleryLayou…

(Java学习)File类与IO流

File类与IO流 文章目录 File类与IO流1. File类1.1 File类理解1.2 构造器1.3 常用方法1.4 练习 2. IO流概述2.1 IO流理解2.2 流的分类2.3 基础IO流的框架 3. FileReader/FileWriter3.1 执行步骤3.2 注意 4. FileInputStream / FileOutputStream5. 缓冲流6. 转换流的使用7. 对象流…

1、Cocos Creator 基础入门

目录 Cocos Creator 是什么? 语言支持 功能特性 工作流程 功能模块 相关游戏 参考 Cocos Creator 是什么? Cocos Creator 既是一款高效、轻量、免费开源的跨平台 2D&3D 图形引擎,也是一个实时 2D&3D 数字内容创作平台。拥有…

本地GDB 在geoserver发布地图服务

文章目录 简要说明maven依赖样例代码 简要说明 geoserver本身是不支持GDB的发布&#xff0c;不过可以利用gdal&#xff0c;将GDB的图层转为shpfile&#xff0c;就可以进行发布。maven依赖 <!--geoserver需要的jar--><dependency><groupId>com.github.dov-vl…

Java开发过程中如何进行进制换换

最近由于工作上的需要&#xff0c;遇到进制转换的问题。涉及到的进制主要是十进制、十六进制、二进制转换。 1、十进制转十六进制、二进制 调用java自带的api,测试十进制转16进制、2进制 package com.kangning.common.utils.reflect;/*** 十进制 转 十六进制* 十进制 转 二进…

AcWing第147场周赛(5556.牛的语言学)

牛语单词通过以下规则构造&#xff1a; 牛语单词仅由小写字母构成。牛语单词的具体结构为&#xff1a;词根若干个&#xff08;0 个或更多&#xff09;后缀&#xff0c;其中&#xff1a; 词根为长度大于 4 的字符串。后缀为长度 2 或 3 的字符串。在构成单词时&#xff0c;不允…

机器学习算法的另一个分支-贝叶斯算法原理(贝叶斯要解决什么问题)

目录 一、贝叶斯简介 二、贝叶斯要解决的问题 三、例子&#xff08;公式推导&#xff09; 四、实例 1. 拼写纠正实例 2. 垃圾邮件过滤实例 一、贝叶斯简介 1. 贝叶斯&#xff1a;英国数学家。1702年出生于伦敦&#xff0c;做过神甫。贝叶斯在数学方面主要研究概率论.对于…

Golang基础-3

Go语言基础 介绍 基础 1. 条件语句 2. 选择语句 3. 循环语句 4. goto关键字 介绍 本文介绍Go语言中条件语句、选择语句、循环语句、break、continue、goto关键字等相关知识。 基础 1. 条件语句 实现程序逻辑时&#xff0c;需要通过不同的判断条件去执行相应的语句。…

初识C++ · 入门(1)

目录 前言&#xff1a; 1 命名空间 2 输入和输出 3 缺省参数 5 函数重载 前言&#xff1a; C与C语言是有一定交集的&#xff0c;可以理解为本贾尼在使用C语言的时候认为有缺陷&#xff0c;于是加了一些小语法进行改良&#xff0c;后来经过委员会的修改&#xff0c;C98问世…

深度学习故障诊断实战 | 数据预处理之基于滑动窗的数据样本增强

前言 本期给大家分享介绍如何基于滑动窗方法进行数据样本增强 背景 深度学习模型训练需要大量的样本。在故障诊断领域&#xff0c;每个类别大都会达到300个样本。但是在实际公开数据集中&#xff0c;以CWRU数据集为例&#xff0c;每个类别只有24组数据&#xff0c;这明显是不…

CSGO赛事管理系统的设计与实现|Springboot+ Mysql+Java+ B/S结构(可运行源码+数据库+设计文档)

本项目包含可运行源码数据库LW&#xff0c;文末可获取本项目的所有资料。 推荐阅读100套最新项目持续更新中..... 2024年计算机毕业论文&#xff08;设计&#xff09;学生选题参考合集推荐收藏&#xff08;包含Springboot、jsp、ssmvue等技术项目合集&#xff09; 目录 1. 系…

ExpandableNotificationRow的父类layout

ExpandableNotificationRow的父类layout NotificationsQuickSettingsContainer NotificationPanelView NotificationShadeWindowView ExpandableNotificationRow 就是下图的一个 Notification&#xff1a; USB连接 。 何时创建一个ExpandableNotificationRow 并且被添加到Not…

Spring Boot 工程开发常见问题解决方案,日常开发全覆盖

本文是 SpringBoot 开发的干货集中营&#xff0c;涵盖了日常开发中遇到的诸多问题&#xff0c;通篇着重讲解如何快速解决问题&#xff0c;部分重点问题会讲解原理&#xff0c;以及为什么要这样做。便于大家快速处理实践中经常遇到的小问题&#xff0c;既方便自己也方便他人&…

护眼台灯对眼睛有危害吗?多款预防近视的台灯推荐

在日常生活中&#xff0c;灯光对于我们而言&#xff0c;是非常重要的&#xff0c;尤其是在夜晚&#xff0c;不管是学习还是办公都需要合适的光线环境。很多家长为了保护孩子的视力会选择从台灯下手&#xff0c;但又不知道护眼台灯对眼睛有危害吗&#xff1f;今天就来好好的告诉…

【MySQL】数据库--表操作

目录 一、创建表 二、查看表 三、修改表 1. 添加字段--add 2.修改表名--rename to 3.修改列名--change 4.修改字段的数据类型--modify 5.删除字段&#xff08;列&#xff09;--drop 四、删除表 一、创建表 create [temporary]table[if not exists]table_name [([colu…

vscode插件

一、codeGeex 自动解析代码添加注释 二、Prettier - Code formatter 代码格式化插件 // “prettier.useEditorConfig”: false, // 是否使用editorConfig配置文件中的设置 “prettier.semi”: true, // 句尾是否填加分号 “prettier.singleQuote”: false, //是否使用单引号 “…

python如何获取word文档的总页数

最近在搞AI. 遇到了一个问题&#xff0c;就是要进行doc文档的解析。并且需要展示每个文档的总页数。 利用AI. 分别尝试了chatGPT, 文心一言&#xff0c; github copilot&#xff0c;Kimi 等工具&#xff0c;给出来的答案都不尽如人意。 给的最多的查询方式就是下面这种。 这个…