Python的进程和线程——一些基础概念

python · 浏览次数 : 0

小编点评

**3.3 主线程和子线程的关系创建** * 主线程可以创建一个或多个子线程。 * 子线程和主线程通过同步机制(如锁)交互。 * 子线程只能执行其所负责的任务,因为它们需要同步访问共享资源。 **4. 锁** * 是一种用来控制对共享资源访问的机制。 * 当多个线程需要同时运行时,锁可以帮助避免它们在不适当的时刻相互干扰。 * 常用的锁类型包括互斥锁、递归锁、读写锁、信号量。 **4.1 什么是锁?** * 是一种用来控制对共享资源访问的机制。 * 当多个线程需要同时运行时,锁可以帮助防止它们在不适当的时刻相互干扰。 **4.2为什么要使用锁?** * 使用锁可以确保安全地执行对共享资源的修改。 * 避免竞态条件,确保程序的正确性。 **4.3 锁的工作原理** * 获取锁: 线程首先要获取锁,这通常通过调用 `acquire()` 方法实现。 * 执行操作: 线程获得锁后可以安全地修改共享资源。 * 释放锁: 线程释放锁后,它调用 `release()` 方法释放锁。 **4.4 常用的锁类型** * 互斥锁 (Mutex Lock): 线程只能获取锁一次。 * 递归锁 (Recursive Lock): 允许同一个线程多次获取同一个锁,避免死锁问题。 *读写锁 (RWLock): 允许多个读取操作同时进行,但写操作是排他的。 * 信号量 (Semaphore): 类似于锁,但可以设定一个计数值,允许多个线程同时访问资源。 * 条件锁 (Condition): 允许线程在某个条件成立之前挂起,直到其他线程发出信号。 **4.5 使用锁的例子** ```python import threading counter = 0 lock = threading.Lock() def increment(): global counter for _ in range(100000): lock.acquire() # 获取锁 counter += 1 lock.release() # 释放锁 def decrement(): global counter for _ in range(100000): lock.acquire() # 获取锁 counter -= 1 lock.release() # 释放锁 if __name__ == "__main__": thread1 = threading.Thread(target=increment) thread2 = threading.Thread(target=decrement) thread1.start() thread2.start() thread1.join() thread2.join() print(counter) # 应该打印 200000 ``` **4.6 注意事项** * 死锁:如果不正确地使用锁,可能会导致死锁,即两个或多个线程相互等待对方释放锁,但没有一个线程愿意放弃,导致程序停止响应。 *性能:过度使用锁可能会降低程序的性能,因为线程需要等待获取锁。 *避免锁:在可能的情况下,尽量避免使用共享资源,或者使用线程局部数据,这样可以减少对锁的需求。

正文

1. 线程和进程

1.1 线程和进程

  1. 进程可以包含多个并行运行的线程;

  2. 通常,操作系统创建和管理线程比进程更省CPU资源;

  3. 线程用于一些小任务,进程用于繁重的任务;

  4. 同一个进程下的线程共享地址空间和其他资源,进程之间相互独立;

1.2 线程 v.s. 进程:

1.2.1 多线程

  • 多线程是在同一进程内部创建多个线程来执行任务的技术。每个线程共享相同的内存空间,因此可以轻松共享数据。
  • 多线程适合于 I/O 密集型任务,如网络请求、文件操作等,因为线程之间的切换开销较小。
  • Python 中的多线程通常由 threading 模块实现。

1.2.2 多进程

  • 多进程是在操作系统级别创建多个独立的进程来执行任务的技术。每个进程都有自己独立的内存空间,数据不共享,需要通过 IPC(进程间通信)来传递数据。
  • 多进程适合于 CPU 密集型任务,如数值计算、图像处理等,因为多进程能够充分利用多核处理器的能力。
  • Python 中的多进程通常由 multiprocessing 模块实现。

1.2.3 区别

  1. 内存和资源开销:多线程共享同一进程的内存空间,因此创建线程的开销较小;而多进程每个进程都有独立的内存空间,创建进程的开销较大。

  2. 数据共享:多线程可以轻松共享数据,因为它们共享相同的内存空间;而多进程需要通过 IPC 来传递数据,因为它们的内存空间是独立的。

  3. CPU 利用率:多线程适合于 I/O 密集型任务,因为线程切换开销较小;多进程适合于 CPU 密集型任务,因为可以充分利用多核处理器的能力。

总的来说,多线程适合于需要频繁进行 I/O 操作的任务,而多进程适合于需要大量 CPU 计算的任务。

2. I/O密集型任务和CPU密集型任务

在计算机编程中,任务通常可以根据它们是如何使用计算机资源的来分类。主要有两种类型:I/O 密集型任务CPU 密集型任务

2.1 I/O 密集型任务

I/O 是 Input/Output(输入/输出)的缩写。当说到 I/O 密集型任务时,我们通常是指那些需要大量数据输入和输出的任务。这些任务往往涉及到磁盘、网络或其他类型的数据传输。

特点:

  • 等待时间: I/O 操作往往需要等待,比如等待磁盘旋转到正确的位置来读取数据,或者等待网络上的数据包到达。
  • CPU 使用率: 在等待数据传输的过程中,CPU 往往是空闲的,因为它不能在没有数据的情况下继续工作。
  • 例子: 比如,从一个很大的文件中读取数据,或者从互联网上下载一个文件。这些操作大部分时间都在等待数据到来,而不是在进行大量的计算。

2.2 CPU 密集型任务

CPU 密集型任务是指那些需要大量计算的任务,它们会充分利用 CPU 的计算能力。

特点:

  • 计算量大: 这类任务涉及复杂的计算,如图形处理、数据分析、加密解密等。
  • CPU 使用率: 在执行这类任务时,CPU 通常会保持高使用率,因为它一直在进行计算。
  • 例子: 比如,对一张高分辨率图片进行复杂的图像处理,或者计算一个大型数据集的统计分析。这些任务需要 CPU 进行大量的数学运算。

想象一下,你在厨房里准备晚餐。I/O 密集型任务就像是你在等待水烧开或者烤箱预热。在这期间,你基本上无事可做,只能等待。CPU 密集型任务则像你在切菜、拌沙拉或进行烹饪,你需要不断地工作,直到完成。

在编程中,我们选择多线程、多进程或其他并发模型,取决于任务的类型。如果任务主要是 I/O 密集型的,那么多线程可能会有所帮助,因为线程可以在等待 I/O 操作完成时被操作系统挂起,让其他线程运行。而如果任务是 CPU 密集型的,那么多进程可能更合适,因为每个进程可以在 CPU 上真正地并行运行,从而实现更快的处理速度。

3. 主线程和子线程

在多线程编程中,主线程(Main Thread)子线程(Child Thread) 是用来描述线程之间关系的术语。

3.1 主线程(Main Thread)

  • 定义: 主线程通常指的是程序启动时创建的第一个线程,它是执行程序主流程的线程
  • 作用: 主线程负责程序的主要逻辑控制流程,包括启动其他线程(子线程)和管理它们。
  • 生命周期: 通常,主线程会持续运行直到程序的所有任务完成,或者直到它被显式终止。

3.2 子线程(Child Thread)

  • 定义: 子线程是指由主线程创建的额外线程,用于并行执行任务
  • 作用: 子线程可以用于执行特定的任务,如后台处理、I/O 操作、计算密集型任务等,以提高程序的效率和响应性。
  • 生命周期: 子线程的生命周期通常依赖于它们所执行的任务。一旦任务完成,子线程就会结束。

3.3 主线程和子线程的关系

  • 创建: 主线程可以创建一个或多个子线程。
  • 同步: 主线程可能会等待(通过 join() 方法)子线程完成,以确保在继续执行之前子线程的任务已经结束。
  • 协作: 主线程和子线程可以共享数据和资源,但需要同步机制(如锁)来避免竞态条件和数据不一致。
  • 结束: 主线程的结束通常意味着程序的结束,除非它等待子线程完成。子线程的结束仅表示它们所负责的任务已经完成。

3.4 示例

假设你有一个 Python 程序,它使用 threading 模块创建了多个线程:

import threading
import time

def child_thread_task(name):
    print(f"Child thread {name} is running.")
    time.sleep(1)
    print(f"Child thread {name} has finished.")

def main_thread_task():
    print("Main thread is setting up child threads.")
    threads = []
    for i in range(3):
        t = threading.Thread(target=child_thread_task, args=(i,))
        threads.append(t)
        t.start()

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

    print("All child threads have completed. Main thread is finishing.")

if __name__ == "__main__":
    main_thread_task()  # 主线程的任务

[Run]

Main thread is setting up child threads.
Child thread 0 is running.
Child thread 1 is running.
Child thread 2 is running.
Child thread 2 has finished.Child thread 1 has finished.
Child thread 0 has finished.

All child threads have completed. Main thread is finishing.

在这个示例中:

  • main_thread_task() 函数中的代码运行在主线程上,它负责创建和启动子线程。
  • child_thread_task() 函数中的代码运行在子线程上,每个子线程执行一个独立的任务。
  • 主线程使用 join() 方法等待所有子线程完成后才结束。

4. 锁

4.1 什么是锁

在计算机编程中,锁是一种用来控制对共享资源(如内存、文件等)访问的机制。当多个线程(程序执行的独立流)需要同时运行时,锁可以帮助避免它们在不适当的时刻相互干扰。

4.2 为什么要使用锁

想象一下,如果有多个小孩同时在一个图书馆里,他们都想写同一本书。如果没有规则,他们可能会相互覆盖对方的内容,导致书变得乱七八糟。锁就像是图书馆的规则,它确保一次只有一个人能够写这本书。

在计算机中,当多个线程需要修改同一个变量时,如果不使用锁,就可能出现类似的情况,这被称为 “竞态条件” 。竞态条件会导致程序的行为变得不可预测,因为最终结果取决于线程执行的顺序,而这个顺序是无法控制的。

4.3 锁是如何工作的

锁的工作原理很简单:

  1. 获取锁: 当一个线程想要修改一个共享资源时,它首先需要“获取”(或“锁定”)一个锁。这通常通过调用 acquire() 方法来实现。

  2. 执行操作: 一旦线程获取了锁,它就可以安全地执行对共享资源的修改,因为其他线程必须等待锁被释放。

  3. 释放锁: 修改完成后,线程会“释放”(或“解锁”)锁,这通常通过调用 release() 方法来实现。这样,其他等待锁的线程就可以继续执行。

4.4 锁的类型

  1. 互斥锁(Mutex Lock): 最常见的锁类型,一次只允许一个线程获取锁。

  2. 递归锁(Recursive Lock): 允许同一个线程多次获取同一个锁,避免了递归函数中的死锁问题。

  3. 读写锁(RWLock): 允许多个读取操作同时进行,但写操作是排他的。

  4. 信号量(Semaphore): 类似于锁,但可以设定一个计数值,允许多个线程同时访问资源。

  5. 条件锁(Condition): 允许线程在某个条件成立之前挂起,直到其他线程发出信号。

4.5 使用锁的例子

假设我们有一个变量 counter,我们希望两个线程能够交替地对其进行加一操作。

import threading

counter = 0
lock = threading.Lock()

def increment():
    global counter
    for _ in range(100000):
        lock.acquire()  # 获取锁
        counter += 1
        lock.release()  # 释放锁

thread1 = threading.Thread(target=increment)
thread2 = threading.Thread(target=increment)

thread1.start()
thread2.start()

thread1.join()
thread2.join()

print(counter)  # 应该打印 200000

在这个例子中,lock 确保了 counter 的增加操作是安全的,即使两个线程在同时运行。如果没有 lock,两个线程可能会同时读取和修改 counter,导致最终结果不正确。

4.6 注意事项:

  • 死锁: 如果不正确地使用锁,可能会导致死锁,即两个或多个线程相互等待对方释放锁,但没有一个线程愿意放弃,导致程序停止响应。

  • 性能: 过度使用锁可能会降低程序的性能,因为线程需要等待获取锁。

  • 避免锁: 在可能的情况下,尽量避免使用共享资源,或者使用线程局部数据,这样可以减少对锁的需求。

正确地使用锁对于避免竞态条件和确保程序的正确性至关重要。

与Python的进程和线程——一些基础概念相似的内容:

Python的进程和线程——一些基础概念

1. 线程和进程 1.1 线程和进程 进程可以包含多个并行运行的线程; 通常,操作系统创建和管理线程比进程更省CPU资源; 线程用于一些小任务,进程用于繁重的任务; 同一个进程下的线程共享地址空间和其他资源,进程之间相互独立; 1.2 线程 v.s. 进程: 1.2.1 多线程 多线程是在同一进程内

8.0 Python 使用进程与线程

python 进程与线程是并发编程的两种常见方式。进程是操作系统中的一个基本概念,表示程序在操作系统中的一次执行过程,拥有独立的地址空间、资源、优先级等属性。线程是进程中的一条执行路径,可以看做是轻量级的进程,与同一个进程中的其他线程共享相同的地址空间和资源。

python轻量级性能工具-Locust

Locust基于python的协程机制,打破了线程进程的限制,可以能够在一台测试机上跑高并发 性能测试基础 1.快慢:衡量系统的处理效率:响应时间 2.多少:衡量系统的处理能力:单位时间内能处理多少个事务(tps) 性能测试根据测试需求最常见的分为下面三类 1 负载测试load testing 不断

Python并行运算——threading库详解(持续更新)

0. 写在前面:进程和线程 博文参考: Python的并行(持续更新)_python 并行-CSDN博客 《Python并行编程 中文版》 一些相关概念请见上一篇博文。 1. 在Python中使用线程 1.1 多线程简介 线程是独立的处理流程,可以和系统的其他线程并行或并发地执行。 多线程可以共享数

一文带你搞清楚Python的多线程和多进程

本文分享自华为云社区《Python中的多线程与多进程编程大全【python指南】》,作者:柠檬味拥抱。 Python作为一种高级编程语言,提供了多种并发编程的方式,其中多线程与多进程是最常见的两种方式之一。在本文中,我们将探讨Python中多线程与多进程的概念、区别以及如何使用线程池与进程池来提高并

< Python全景系列-5 > 解锁Python并发编程:多线程和多进程的神秘面纱揭晓

深入探讨Python中的并发编程,特别关注多线程和多进程的应用。我们将先从基本概念开始,然后通过详细举例探讨每一种机制,最后分享一些实战经验以及一种优雅的编程技巧。

Python:对程序做性能分析及计时统计

如果只是想简单地对整个程序做计算统计,通常使用UNIX下的time命令就足够了。由于我用的是Mac系统,和Linux系统的输出可能有不同,不过关键都是这三个时间:user: 运行用户态代码所花费的时间,也即CPU实际用于执行该进程的时间,其他进程和进程阻塞的时间不计入此数字;system: 在内核中执行系统调用(如I/O调用)所花费的CPU时间。total(Linux下应该是real):即挂钟时间

一文掌握Python多线程与多进程

# Python的多线程和多进程 ## 一、简介 并发是今天计算机编程中的一项重要能力,尤其是在面对需要大量计算或I/O操作的任务时。Python 提供了多种并发的处理方式,本篇文章将深入探讨其中的两种:多线程与多进程,解析其使用场景、优点、缺点,并结合代码例子深入解读。 ## 二、多线程 Pyth

基于Python的性能优化

通过多线程、协程和多进程可以显著提升程序的性能。多线程适用于I/O密集型任务,尽管受限于Python的GIL,但能在I/O等待期间提高并发性。协程则更为轻量和高效,特别适合处理大量异步I/O操作。

python | 连接数据库

介绍一些python中用于连接常用数据库的依赖库。 SQLite3 SQLite3是Python 中自带的数据库模块,适用于小型应用和快速原型开发。 SQLite是一个进程内的库,实现了自给自足的、无服务器的、是非常小的,是轻量级的、事务性的 SQL 数据库引擎。它是一个零配置的数据库,不需要在系统