MST

星途 面试题库

面试题:Python类上下文管理器与资源竞争及优化

在多线程环境下,使用类实现的上下文管理器管理共享资源(例如文件操作或数据库连接)时,可能会遇到资源竞争问题。请阐述如何利用上下文管理器机制结合Python的同步原语(如锁)来避免资源竞争,并对性能优化提出至少两种可行的策略及代码示例。
14.1万 热度难度
编程语言Python

知识考点

AI 面试

面试题答案

一键面试

利用上下文管理器和锁避免资源竞争

在Python中,可以通过在上下文管理器类中使用锁来避免资源竞争。以下是一个简单的示例,以文件操作为例:

import threading
import time


class FileContextManager:
    def __init__(self, file_path, mode):
        self.file_path = file_path
        self.mode = mode
        self.lock = threading.Lock()
        self.file = None

    def __enter__(self):
        self.lock.acquire()
        self.file = open(self.file_path, self.mode)
        return self.file

    def __exit__(self, exc_type, exc_val, exc_tb):
        if self.file:
            self.file.close()
        self.lock.release()


def worker():
    with FileContextManager('test.txt', 'a') as file:
        file.write(f'Thread {threading.current_thread().name} is writing\n')
        time.sleep(1)


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

for thread in threads:
    thread.join()

在上述代码中:

  1. FileContextManager类是一个上下文管理器,它在初始化时创建了一个锁self.lock
  2. __enter__方法中,先获取锁,然后打开文件并返回文件对象。
  3. __exit__方法中,关闭文件并释放锁。

这样,在多线程环境下,当一个线程进入上下文管理器时,它会获取锁,确保其他线程无法同时访问共享资源(文件),从而避免资源竞争。

性能优化策略及代码示例

1. 使用信号量控制并发访问数量

信号量可以控制同时访问共享资源的线程数量,从而在一定程度上提高性能。例如,假设我们希望同时最多有2个线程访问文件:

import threading
import time


class FileContextManagerWithSemaphore:
    def __init__(self, file_path, mode):
        self.file_path = file_path
        self.mode = mode
        self.semaphore = threading.Semaphore(2)
        self.file = None

    def __enter__(self):
        self.semaphore.acquire()
        self.file = open(self.file_path, self.mode)
        return self.file

    def __exit__(self, exc_type, exc_val, exc_tb):
        if self.file:
            self.file.close()
        self.semaphore.release()


def worker_with_semaphore():
    with FileContextManagerWithSemaphore('test.txt', 'a') as file:
        file.write(f'Thread {threading.current_thread().name} is writing\n')
        time.sleep(1)


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

for thread in threads:
    thread.join()

2. 采用读写锁(适用于读多写少场景)

如果共享资源的读操作远多于写操作,可以使用读写锁(RLock)来提高性能。读操作可以并发执行,而写操作则需要独占资源。

import threading
import time


class FileContextManagerWithRLock:
    def __init__(self, file_path, mode):
        self.file_path = file_path
        self.mode = mode
        self.read_lock = threading.Lock()
        self.write_lock = threading.Lock()
        self.read_count = 0
        self.file = None

    def acquire_read_lock(self):
        with self.read_lock:
            self.read_count += 1
            if self.read_count == 1:
                self.write_lock.acquire()

    def release_read_lock(self):
        with self.read_lock:
            self.read_count -= 1
            if self.read_count == 0:
                self.write_lock.release()

    def __enter__(self):
        if 'r' in self.mode:
            self.acquire_read_lock()
        else:
            self.write_lock.acquire()
        self.file = open(self.file_path, self.mode)
        return self.file

    def __exit__(self, exc_type, exc_val, exc_tb):
        if self.file:
            self.file.close()
        if 'r' in self.mode:
            self.release_read_lock()
        else:
            self.write_lock.release()


def read_worker():
    with FileContextManagerWithRLock('test.txt', 'r') as file:
        content = file.read()
        print(f'Thread {threading.current_thread().name} read: {content}')


def write_worker():
    with FileContextManagerWithRLock('test.txt', 'a') as file:
        file.write(f'Thread {threading.current_thread().name} is writing\n')
        time.sleep(1)


read_threads = []
write_threads = []
for _ in range(3):
    read_thread = threading.Thread(target=read_worker)
    read_threads.append(read_thread)
    read_thread.start()

for _ in range(2):
    write_thread = threading.Thread(target=write_worker)
    write_threads.append(write_thread)
    write_thread.start()

for thread in read_threads:
    thread.join()
for thread in write_threads:
    thread.join()

在上述代码中,读操作通过acquire_read_lockrelease_read_lock方法来管理读锁,允许多个读操作并发进行;而写操作则直接获取写锁,确保独占资源。这种方式在读写比例不同的场景下可以显著提高性能。