华为OD技术面真题 - Python开发 -3

文章目录

Python中进程和线程的区别

方面 多进程(multiprocessing) 多线程(threading)
定义 在同一个进程中运行的多个线程 操作系统中独立运行的多个进程
内存空间 多个线程共享进程内存 每个进程有独立的内存空间
创建开销 小,线程比进程轻量 大,创建进程比线程开销大
切换开销 低,线程切换快 高,进程切换慢
共享数据 同一进程的线程共享内存,线程间可以通过全局变量交换数据 不共享内存,需要通过PipeQueue等传递数据
线程安全 需要使用锁来放置数据竞争,进行线程同步 每个进程独立,天然安全,但通信需要序列化和 IPC 开销

说说Python中GIL锁

GIL(全局解释器锁)是Cpython解释器中内部的一个互斥锁,它保证同一时刻在同一个进程中,任意时刻只有一个线程在执行 Python 字节码。

GIL锁的作用:

  1. 保证对象模型在多线程环境下的安全,例如python使用引用计数标记对象是否可回收,多线程同时修改可能会导致计数错误从而可能发生内存泄漏或者程序崩溃。
  2. 通过保证只有一个线程执行,也可以简化python的内部实现,执行很多对象之前无需额外的锁。

GIL锁的特点:

  1. 单线程执行:同一个时刻只能有一个线程执行字节码,所以即使在多核CPU环境下也无法同时运行多个线程的Python代码。
  2. 释放条件:在 I/O 操作(如文件、网络、数据库)时,线程会释放 GIL,允许其他线程运行。
  3. 影响:在I/O密集型任务中,多线程仍然可以提高执行效率。但是在CPU密集型任务中,多线程仍然无法真正并行,效果可能不如单线程执行。

由于GIL锁的机制,当你想利用多核CPU做计算,可以选择多进程或者使用例如C/C++拓展等机制绕过GIL锁机制。

Python中的线程同步

Lock

threading.Lock 本质互斥访问共享资源,特点:

  • 同一时刻只允许一个线程进入临界区
  • 其它线程会被阻塞,阻塞的线程会被OS挂起,不会进行空转。
  • 不可重入,同一线程拿到锁 - 再次尝试获取锁, 会造成死锁

常用使用形式:

python 复制代码
import threading

lock = threading.Lock()
count = 0

def worker():
    global count
    for _ in range(100000):
        with lock:          # 等同于acquire + release
            count += 1

适用场景

  • 修改共享变量
  • 操作共享容器
  • 计数器、状态机修改。

RLock

threading.RLock可重入锁互斥锁,特点:

  • 同一时刻只允许一个线程进入临界区
  • 同一线程可以多次获取同一把锁,因为内部维护owner(拥有锁线程ID),count(重入次数) 可以保证一个线程可以多次安全获取一个锁。

常见使用形式

python 复制代码
lock = threading.RLock()

def f1():
    with lock:
        f2()

def f2():
    with lock:
        print("safe")

适用场景:需要多次访问同一资源的场景。

Semaphore(信号量)

threading.Semaphore(n)信号量,特点:

  • 初始化函数threading.Semaphore(n)可以指定最多n个线程可以同时访问资源。
  • 本质上是一个计数器。当一个线程尝试获取时 n > 0 => n-=1, n == 0 =>线程阻塞

常见使用形式

python 复制代码
# 单个线程可以同时访问
sem = threading.Semaphore(3)

def worker():
    with sem:
        print("running")

适用场景:

  • 连接池资源限制
  • 爬虫并发控制速率

Condition

threading.Condition条件变量用于实现线程间复杂同步,其核心方法包含:

  • wait(): 释放锁并等待通知
  • notify(): 唤醒一个等待线程
  • notify_all(): 唤醒所有等待线程

常用使用形式(生产者-消费者模型):

python 复制代码
import threading

condition = threading.Condition()
buffer = []

def consumer():
    while True:
        with condition:
            condition.wait()  # 等待数据
            if buffer:
                data = buffer.pop()
                print(f"Consumed: {data}")

def producer():
    for i in range(3):
        with condition:
            buffer.append(i)
            print(f"Produced: {i}")
            condition.notify()  # 通知消费者
        import time
        time.sleep(0.5)

threading.Thread(target=consumer, daemon=True).start()
producer()

适用场景:

  • 生产者-消费者模型实现
  • 任务调度

Event

threading.Event 事件,本质是一个布尔标志位

  • 初始情况默认为False
  • set(),设置为True
  • clear(),设置为False
  • wait():等待事件变为True,进入阻塞状态

常见适用形式

python 复制代码
import threading
import time

start_event = threading.Event()

def worker(name):
    print(f"{name}:等待开始信号")
    start_event.wait()   # 阻塞在这里
    print(f"{name}:收到信号,开始工作")
    time.sleep(1)
    print(f"{name}:工作完成")

threads = []

# 创建并启动线程
for i in range(3):
    t = threading.Thread(target=worker, args=(f"线程{i}",))
    t.start()
    threads.append(t)

time.sleep(2)  # 主线程做准备工作

print("主线程:准备完成,发出开始信号")
start_event.set()   # 一次唤醒所有等待线程

for t in threads:
    t.join()

print("主线程:所有线程结束")

适用场景:

  • 传递启动/停止信号
  • 广播通知
  • 线程生命周期控制

Python中内存管理

Python 内存管理是Python程序性能优化和稳定运行的重要组成部分。内存管理能够确保程序在运行过程中有效地利用系统资源,防止不必要的内存消耗,避免内存泄露,并确保不再使用的对象能被及时释放,从而腾出内存供其他对象使用。

Python 的内存管理主要包括对象的分配、垃圾回收以及内存池机制。其中内存回收主要依赖于引用计数、标记-清除和分带回收三种策略实现自动内存管理,详细说说这几种机制

引用计数

python中一切皆对象,每个对象都有一个引用计数,每当新的引用指向该对象时,引用计数加1;当不再有引用指向该对象时,引用计数减1。当引用计数为0时,对象占用的内存就会认为可以被释放。

python 复制代码
# python中对象包含的基本结构
typedef struct _object {
    Py_ssize_t ob_refcnt;   // 引用计数
    PyTypeObject *ob_type;  // 类型信息
} PyObject;

只使用引用计数会存在循环引用的情况,造成对象的引用计数永远不会变为0,从而造成泄漏。

python 复制代码
# 会造成循环引用的情况
a = list()
b = list()
a.append(b)
b.append(a)

在这种情况下,尽管ab在逻辑上已经不再需要,但由于彼此互相引用,引用计数不会归零,因此常规的引用计数方法无法回收它们占用的内存。

标记清除

python采用"标记-清除"算法来解决容器对象可能产生的循环引用问题。只有容器对象才会产生循环引用的情况,比如列表、字典、用户自定义对象等。像数字、字符串这类简单类型不会出现循环引用

标记-清除算法包含两步:

  • 标记阶段:该阶段会遍历所有的对象,如果是可达的,即还有对象引用它,那么就标记该对象为可达。
  • 清除阶段:再次遍历对象,如果发现某个对象没有标记可达,则将其回收。

python会使用一个双向链表讲这些容器组织起来,构成一个有向图。其中对象构成有向图的节点,而引用关系构成有向图的边。系统会从rootOject(全局变量、调用栈、寄存器,这些不会被删除对象)出发,沿着有向边遍历对象,可达的对象标记为活动对象,不可达的对象就是要被清除的非活动对象。

这种简单粗暴的标记清除算法也有明显的缺点:清除非活动的对象前它必须顺序扫描整个堆内存,哪怕只剩下小部分活动对象也要扫描所有对象。

分代收集

分代回收是建立在标记清除技术基础之上的,是一种以空间换时间的操作方式。

Python将内存根据对象的存活时间划分为不同的集合,每个集合称为一个代,Python将内存分为了3"代",分别为年轻代(第0代)、中年代(第1代)、老年代(第2代),他们对应的是3个链表,它们的垃圾收集频率与对象的存活时间的增大而减小。

  • 新创建的对象都会分配在年轻代,年轻代链表的总数达到上限时,Python垃圾收集机制就会被触发,把那些可以被回收的对象回收掉,而那些不会回收的对象就会被移到中年代去,
  • 依此类推,老年代中的对象是存活时间最久的对象,甚至是存活于整个系统的生命周期内。
相关推荐
好好学习啊天天向上16 小时前
C盘容量不够,python , pip,安装包的位置
linux·python·pip
时见先生16 小时前
Python库和conda搭建虚拟环境
开发语言·人工智能·python·自然语言处理·conda
二十雨辰16 小时前
[python]-循环语句
服务器·python
Yvonne爱编码16 小时前
Java 四大内部类全解析:从设计本质到实战应用
java·开发语言·python
wqwqweee16 小时前
Flutter for OpenHarmony 看书管理记录App实战:搜索功能实现
开发语言·javascript·python·flutter·harmonyos
-To be number.wan18 小时前
Python数据分析:numpy数值计算基础
开发语言·python·数据分析
Loo国昌19 小时前
深入理解 FastAPI:Python高性能API框架的完整指南
开发语言·人工智能·后端·python·langchain·fastapi
chinesegf19 小时前
Ubuntu 安装 Python 虚拟环境:常见问题与解决指南
linux·python·ubuntu
醉舞经阁半卷书119 小时前
Python机器学习常用库快速精通
人工智能·python·深度学习·机器学习·数据挖掘·数据分析·scikit-learn
开源技术20 小时前
Violit: Streamlit杀手,无需全局刷新,构建AI面板
人工智能·python