python-threading.LOCK互斥锁

一. 引言:

    在 Python 的 threading 模块中,当多个线程同时使用一个公共资源时,可能会导致线程抢占资源,造成数据错跑。比如:跑200个线程,这200个线程都会去访问counter(公共资源),并对该资源进行处理(counter += 1),如下:

#!/usr/bin/python

import threading
import time

counter = 0

class MyThread(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)

    def run(self):
        global counter
        time.sleep(10);
        counter += 1
        print "I am %s, set counter:%s" % (self.name, counter)

if __name__ == "__main__":
    for i in range(0, 200):
        my_thread = MyThread()
        my_thread.start()

部分输出如下:

I am Thread-117, set counter:116
I am Thread-119, set counter:117
I am Thread-124, set counter:118
I am Thread-120, set counter:120
I am Thread-122, set counter:122
I am Thread-121, set counter:119
I am Thread-123, set counter:121
I am Thread-128, set counter:123

可发现, 确实有抢占资源的情况,导致输出错乱。问题产生的原因就是没有控制多个线程对同一资源的访问,使得线程运行的结果不可预期。这种现象称为“线程不安全”。因此,在开发过程中我们必须要避免上文中的资源抢占问题

二.   关于互斥锁

        Python编程中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个可称为” 互斥锁” 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。在Python中我们使用threading模块提供的Lock类。添加一个互斥锁变量thr = threading.Lock(),然后在争夺资源的时候之前我们会先抢占这把锁trh.acquire(),对资源使用完成之后我们在释放这把锁thr.release() , 修改后代码如下: 

#!/usr/bin/python

import threading
import time

counter = 0
thr = threading.Lock()

class MyThread(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)

    def run(self):
        global counter
        time.sleep(1);
        if thr.acquire():  #添加
            counter += 1
            print "I am %s, set counter:%s" % (self.name, counter)
            thr.release()

if __name__ == "__main__":
    for i in range(0, 200):
        my_thread = MyThread()
        my_thread.start()

删除如下: 

I am Thread-3, set counter:1
I am Thread-4, set counter:2
I am Thread-2, set counter:3
I am Thread-5, set counter:4
I am Thread-1, set counter:5
I am Thread-8, set counter:6
I am Thread-6, set counter:7
I am Thread-7, set counter:8
I am Thread-9, set counter:9
I am Thread-10, set counter:10
I am Thread-13, set counter:11

结果并不会出现错乱的情况。

三.  更简洁的用法: with 语句:

   lock 支持上下文管理器协议,可使用 with 语句自动获取和释放锁,避免手动调用 acquire() 和 release() 导致的遗漏或错误:

def run(self):
    global counter
    time.sleep(1);
    with thr:
        counter += 1
        print "I am %s, set counter:%s" % (self.name, counter)

四.  关于死锁:

   当一个线程调用Lock对象的acquire()方法获得锁时,就会进入“locked”状态。因为每次只有一个线程可以获得锁,所以如果此时另一个线程试图获得这个锁,该线程就会变为“blo同步阻塞状态。直到拥有锁的线程调用锁的release()方法释放锁之后,该锁进入 “unlocked”状态。线程调度程序从处于同步阻塞状态的线程中选择一个来获得锁,并使得该线程进入运行(running)状态。 当两个或多个线程互相等待对方释放锁而陷入无限阻塞, 称之为“死锁”。

如下: 

lock1 = threading.Lock()
lock2 = threading.Lock()

def thread1():
    with lock1:
        # 线程1持有lock1,等待lock2
        with lock2:
            print("Thread1 完成")

def thread2():
    with lock2:
        # 线程2持有lock2,等待lock1
        with lock1:
            print("Thread2 完成")

t1 = threading.Thread(target=thread1)
t2 = threading.Thread(target=thread2)
t1.start()
t2.start()  # 大概率发生死锁

避免死锁的方法:

  1. 按固定顺序获取锁(如所有线程都先获取 lock1 再获取 lock2)。

  2. 给 acquire() 设置超时时间(lock.acquire(timeout=5)),超时后放弃并释放已获取的锁。

  3. 减少锁的持有时间,仅在必要的代码段(临界区)加锁。

 ---------------------------------------------------------------------------------------------------------------------

                         深耕运维行业多年,擅长运维体系建设,方案落地。欢迎交流!

                                                     “V-x”: ywjw996

                                                     《 运维经纬 》
 

<think>首先,用户的问题是:“我想了解python中multiprocessing.Lock()和threading.Lock()的区别”。这是一个关于Python并发编程的问题,涉及多进程和多线程中的锁机制。 我需要根据系统级指令来构建我的回答: - 所有行内数学表达式必须使用$...$格式。 - 独立公式必须使用$$...$$格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 在回答中引用的段落末尾自然地添加引用标识。 回顾提供的引用: - 引用[1]:介绍了multiprocessing包,与threading.Thread类似,用于多进程管理。 - 引用[2]:解释了Python的多线程由于GIL无法充分利用多核,而multiprocessing可以规避GIL问题。 - 引用[3]:详细讨论了multiprocessing.Lock用于进程间同步。 - 引用[4]:关于multiprocessing.Pool类的详解。 - 引用[5]:涉及multiprocessing.managers方法。 用户问题焦点是multiprocessing.Lock()和threading.Lock()的区别。我需要比较它们在多进程和多线程环境中的使用。 关键区别: 1. **作用域**: - `threading.Lock()`:用于多线程环境,保护共享资源在同一进程内的线程间同步。 - `multiprocessing.Lock()`:用于多进程环境,保护共享资源在不同进程间同步。 2. **GIL的影响**: - Python的GIL(全局解释锁)限制多线程在多核CPU上的执行。`threading.Lock()`在GIL环境下工作。 - `multiprocessing.Lock()`通过创建独立进程规避GIL,允许真正的并行执行。 3. **实现机制**: - `threading.Lock()`:基于线程同步原语,如互斥锁- `multiprocessing.Lock()`:通常使用操作系统级的同步机制,如信号量或IPC(进程间通信)。 4. **性能开销**: - 进程锁的开销通常比线程锁大,因为涉及进程间通信。 5. **共享数据**: - 在多线程中,线程共享进程的内存空间,所以锁容易实现。 - 在多进程中,进程不共享内存(除非使用共享内存),因此锁通过IPC实现。 从引用中提取相关信息: - 引用[2]强调GIL问题:多线程不能充分利用多核,multiprocessing可以。 - 引用[3]讨论multiprocessing.Lock用于进程同步。 - 引用[1]提到multiprocessing与threading类似,但有进程上下文。 确保回答结构清晰: - 逐步解释区别。 - 使用中文。 - 添加引用标识。 生成相关问题: - 根据主题,生成几个后续问题。 最终回答结构: 1. **引言**:简要介绍锁的概念。 2. **详细区别**:分点比较两种锁。 3. **总结**:强调关键差异。 4. **相关问题**:生成相关问题。 相关问题建议: 1. 如何在Python中使用multiprocessing.Lock()? 2. GIL对Python多线程有什么影响? 3. 什么情况下应该使用多进程而不是多线程? 4. 进程间同步的其他机制有哪些? 添加引用:在适当的地方自然地添加引用标识。例如: - 提到GIL时引用[2]。 - 提到multiprocessing.Lock时引用[3]。 无数学公式需求,因此不需要LaTeX数学表达式。 </think>### Python中`multiprocessing.Lock()`和`threading.Lock()`的区别 在Python并发编程中,锁(Lock)用于同步共享资源的访问,防止竞态条件(race condition)。`multiprocessing.Lock()`和`threading.Lock()`都实现了锁机制,但它们在作用域、实现方式和适用场景上有显著差异。下面我将逐步解释这些区别,帮助您深入理解。 #### 1. **作用域和适用环境** - **`threading.Lock()`**: - 用于多线程环境(在同一进程内)。 - 保护线程间共享的资源(如内存变量),但不涉及进程间通信。 - 由于Python的GIL(全局解释锁)限制,多线程无法充分利用多核CPU,但对I/O密集型任务高效[^2]。 - 示例代码: ```python import threading lock = threading.Lock() def thread_task(): with lock: # 获取锁 # 访问共享资源 shared_variable += 1 ``` - 引用说明:`threading.Lock`是基于线程的同步原语,适用于单一进程内的并发控制[^1]。 - **`multiprocessing.Lock()`**: - 用于多进程环境(跨多个进程)。 - 保护进程间共享的资源,常用于共享内存或其他IPC(进程间通信)场景下的同步。 - 通过创建独立进程(每个进程有单独的Python解释器),规避GIL问题,适合CPU密集型任务[^2][^3]。 - 示例代码: ```python from multiprocessing import Process, Lock lock = Lock() def process_task(): with lock: # 获取锁 # 访问共享资源(如通过Manager共享的数据) shared_value.value += 1 ``` - 引用说明:`multiprocessing.Lock`依赖于操作系统级机制(如信号量),确保进程间数据一致性[^3]。 #### 2. **实现机制和性能开销** - **`threading.Lock()`**: - 实现较简单:基于Python内部的线程同步原语(如互斥锁),开销小。 - 性能:由于GIL的存在,线程锁的获取和释放速度快,适合高频率锁操作。 - 缺点:在CPU密集型任务中,线程可能被GIL阻塞,降低并行效率[^2][^1]。 - **`multiprocessing.Lock()`**: - 实现较复杂:通常使用IPC机制(如管道或共享锁文件),通过操作系统内核同步。 - 性能:锁操作涉及进程间通信,开销较大(例如,获取锁需要系统调用),可能导致延迟。 - 优点:在Python中,它是实现真正并行(利用多核CPU)的关键,避免GIL瓶颈[^2][^3]。 - 引用说明:进程锁的设计确保了对共享资源的独占访问,防止多进程数据冲突[^3]。 #### 3. **共享数据和一致性** - **`threading.Lock()`**: - 数据共享:线程在同一个进程的内存空间中运行,共享变量直接可见。 - 风险:如果锁使用不当,线程间仍可能发生数据竞争,但内存一致性易维护。 - **`multiprocessing.Lock()`**: - 数据共享:进程默认不共享内存;锁通常与`multiprocessing.Manager`或其他共享机制(如Value/Array)配合使用。 - 风险:进程间共享数据需要显式管理(如通过管道或共享内存),否则锁无法保护独立内存空间的数据[^5][^3]。 - 引用说明:进程锁常与`Manager`类结合,实现跨进程数据同步[^5]。 #### 4. **适用场景总结** - **使用`threading.Lock()`时**: - 任务类型:I/O密集型任务(如网络请求、文件读写),其中GIL影响较小。 - 优势:轻量级,开发简单。 - 劣势:不适合CPU密集型任务,因为GIL会限制并行性。 - **使用`multiprocessing.Lock()`时**: - 任务类型:CPU密集型任务(如数值计算),需要利用多核处理器。 - 优势:规避GIL,实现真正并行。 - 劣势:开销大,代码更复杂(需处理进程创建和数据共享)。 #### 关键区别对比表 | 特性 | `threading.Lock()` | `multiprocessing.Lock()` | |------|---------------------|--------------------------| | **作用域** | 同一进程内的线程 | 跨进程 | | **GIL影响** | 受GIL限制 | 规避GIL | | **性能开销** | 低(线程级) | 高(进程间通信) | | **数据共享** | 直接(共享内存) | 间接(需IPC机制) | | **适用任务** | I/O密集型 | CPU密集型 | | **实现机制** | 用户态锁 | 内核态同步原语 | 总之,主要区别在于:`threading.Lock()`用于线程同步,适合单一进程内并发;而`multiprocessing.Lock()`用于进程同步,支持分布式计算和CPU并行处理。选择哪种锁取决于任务类型:优先用线程锁简化开发,但对于计算密集型任务,必须使用进程锁来提升性能[^1][^2][^3]。 ####
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值