python多进程卡死_Python 多进程中使用logging导致死锁

当Python多进程中一个线程在logging时,若通过其他线程fork子进程,可能导致子进程因无法获取logging.lock而卡死。问题源于线程锁不跨进程。解决方案包括使用spawn或forkserver启动新进程,避免资源继承。在确定无多线程锁情况下,可谨慎使用fork。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这个问题算是个很老问题了,09年的issue一直到现在还是open的状态:https://bugs.python.org/issue6721

原因

简单的说,如果主进程A中有多个线程A.t1,A.t2...,而其中有一个线程A.t1刚好在logging,此时线程锁是获取状态(对,logging是通过线程锁实现线程安全的),同时通过另一个线程A.t2 又fork出一个子进程B的话,那么B进程初始化之后,logging.lock会是未释放的状态,此时在B里面尝试logging,B就会卡死,就算A.t1 logging完毕释放了logging.lock,B一样获取不到这个lock,因为这个线程锁不会跨进程,是不同的对象。

代码

import os

import sys

import threading

import time

import multiprocessing

class ThreadWorker(threading.Thread):

def __init__(self):

print('ThreadWorker: init')

super().__init__()

def run(self):

print('ThreadWorker: running (rlock = {0})'.format(global_rlock))

global_rlock.acquire()

print('ThreadWorker: i got lock {0}'.format(global_rlock))

time.sleep(5)

<think>嗯,用户现在想了解如何在Python多进程编程中确保进程不重复运行。首先,我需要回顾一下之前关于锁机制的讨论。用户之前询问的是Python 3.14的锁机制,虽然3.14还未发布,但根据现有知识,线程锁如Lock和RLock是用来管理多线程中的资源竞争的。然而,多进程的情况不同,因为每个进程有独立的内存空间,线程锁在这种情况下可能不适用。 接下来,用户提到了三个引用,都是关于Python多进程编程的。这些引用指出多进程适合CPU密集型任务,避免了GIL的限制,但需要处理进程间通信和同步。用户现在的问题是避免进程重复执行,也就是确保某个任务只被一个进程执行一次,或者在多进程环境中防止多个进程同时执行同一段代码。 首先,我需要区分多线程和多进程的锁机制。线程锁在同一个进程内有效,而多进程情况下,每个进程有自己的内存空间,所以普通的线程锁无法跨进程工作。这时候需要使用进程间同步机制,比如multiprocessing模块提供的Lock、RLock,或者Semaphore。 然后,用户的问题可能涉及到单例模式,或者确保某个初始化操作只执行一次。例如,可能需要确保一个文件只被一个进程写入,或者某个配置只加载一次。这时候,可能需要使用进程的锁,或者利用文件锁等其他机制。 另外,用户可能想防止同一个脚本被多次启动,这时候可能需要使用进程间通信或者系统级的锁,比如使用文件锁或端口绑定来检测是否已有实例在运行。 我需要考虑几种情况:一种是多进程程序中,某个任务需要只执行一次;另一种是防止同一脚本被多个进程同时运行。针对第一种情况,可以使用multiprocessing.Lock来同步不同进程,确保只有一个进程执行关键代码。对于第二种情况,可能需要系统级的锁,比如使用fcntl模块在Unix系统上创建文件锁,或者在Windows上使用msvcrt.locking。 此外,还需要考虑进程间的通信机制,如共享内存、管道、队列等,但这些可能不是用户当前问题的重点。用户的核心是避免重复执行,所以重点在同步机制。 可能还需要提到使用Singleton模式,但要注意在多进程中,每个进程会有自己的实例,所以需要跨进程的单例管理,可能需要借助Manager模块中的共享对象。 另外,用户可能对进程池中的任务去重感兴趣,比如在进程池中提交多个任务,但希望相同的任务只执行一次。这可能需要任务队列的管理,比如使用队列去重,或者在任务分发前进行检查。 总结可能的解决方案: 1. 使用multiprocessing.Lock来同步进程,确保关键代码段只被一个进程执行。 2. 使用系统级文件锁防止脚本重复运行。 3. 使用Manager模块创建共享变量来标记任务是否已执行。 4. 在进程使用信号量(Semaphore)控制并发访问。 5. 利用数据库或外部存储来记录任务状态,实现跨进程的幂等性。 需要验证这些方法的正确性。例如,multiprocessing.Lock是否真的能在不同进程间同步。查阅资料可知,multiprocessing模块提供的锁是基于操作系统原语的,确实可以跨进程同步。而线程锁threading.Lock只能在同一个进程内有效,不能跨进程。 接下来,应该给出具体的代码示例,比如使用multiprocessing.Lock来保护关键代码,确保只有一个进程执行。同时,可以演示如何使用文件锁防止多实例运行。 还需要注意,使用锁时要避免死锁,确保锁的获取和释放。此外,文件锁的实现可能因操作系统而异,需要处理不同平台的兼容性问题。 用户可能存在的误区是认为多进程的锁和多线程的锁一样,所以需要明确说明两者的区别,强调multiprocessing模块的锁是必要的。 最后,需要总结最佳实践,比如推荐使用with语句自动管理锁,或者结合超时机制防止死锁,以及在多进程环境中如何有效地避免重复执行任务。</think>### Python多进程避免重复执行的实现方法 #### 一、跨进程同步锁机制 使用`multiprocessing.Lock`实现多进程间的同步控制,确保关键代码段只能被一个进程执行: ```python from multiprocessing import Process, Lock import time shared_resource = [] process_lock = Lock() # 创建跨进程锁对象 def task_process(lock, item): with lock: # 自动获取/释放锁(推荐方式) if item not in shared_resource: time.sleep(0.1) # 模拟处理延迟 shared_resource.append(item) print(f"Added {item} by PID: {os.getpid()}") if __name__ == "__main__": items = [1,2,3,2,1,4,5] processes = [] for item in items: p = Process(target=task_process, args=(process_lock, item)) processes.append(p) p.start() for p in processes: p.join() print(f"Final list: {shared_resource}") # 输出结果保证元素唯一性,如[1,2,3,4,5] ``` *该方案通过进程级锁确保资源修改的原性[^1][^3]* #### 二、单实例进程控制 使用文件锁防止程序重复启动(跨平台方案): ```python import fcntl import sys class SingleInstance: def __init__(self, lockfile): self.lock_file = open(lockfile, 'w') try: # 非阻塞模式获取排他锁 fcntl.flock(self.lock_file, fcntl.LOCK_EX | fcntl.LOCK_NB) except IOError: print("Another instance is running") sys.exit(1) if __name__ == "__main__": lock = SingleInstance("/tmp/myapp.lock") # 主程序逻辑... ``` *该方案通过文件系统锁确保系统级单实例运行[^2]* #### 三、共享内存标记法 使用`multiprocessing.Manager`创建跨进程共享数据结构: ```python from multiprocessing import Process, Manager def worker(shared_dict, key): if key not in shared_dict: # 模拟耗时操作 result = key.upper() shared_dict[key] = result print(f"Processed {key}") if __name__ == "__main__": with Manager() as manager: shared_data = manager.dict() # 进程安全字典 tasks = ['a','b','a','c','b'] processes = [] for task in tasks: p = Process(target=worker, args=(shared_data, task)) processes.append(p) p.start() for p in processes: p.join() print("Final results:", dict(shared_data)) # 输出结果保证每个键唯一处理 ``` #### 四、进程级信号量控制 限制特定操作的并发数量(如数据库连接池): ```python from multiprocessing import Semaphore, Process import random db_semaphore = Semaphore(3) # 允许3个进程同时访问 def db_operation(sem, query): with sem: print(f"Start processing {query}") time.sleep(random.uniform(1,3)) print(f"Finished {query}") if __name__ == "__main__": queries = [f"Q{i}" for i in range(10)] processes = [] for q in queries: p = Process(target=db_operation, args=(db_semaphore, q)) processes.append(p) p.start() for p in processes: p.join() ``` ### 技术选型对比表 | 方法 | 适用场景 | 优点 | 缺点 | |--------------------|--------------------------------|------------------------------|------------------------------| | 跨进程锁 | 资源访问同步 | 精确控制临界区 | 需要显式管理锁对象 | | 文件锁 | 系统级单实例运行 | 跨进程/跨程序有效 | 依赖文件系统权限 | | 共享内存结构 | 状态标记/缓存共享 | 数据结构丰富 | 需要序列化/性能损耗 | | 信号量 | 资源池管理 | 灵活控制并发量 | 需要预估资源容量 | ### 最佳实践建议 1. **优先使用上下文管理器**:`with lock`语句可自动处理锁的获取/释放,避免忘记释放锁导致死锁[^3] 2. **设置超时机制**:`lock.acquire(timeout=5)`防止无限期等待 3. **结合日志监控**:使用`logging`模块记录锁操作,便于调试 4. **压力测试验证**:通过`multiprocessing.Pool`模拟高并发场景 ```python from multiprocessing import Pool def stress_test(key): with process_lock: # 执行关键操作 return key**2 if __name__ == "__main__": with Pool(8) as p: print(p.map(stress_test, range(1000))) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值