python之多线程 threading.Lock() 和 threading.RLock()

本文详细探讨了Python中线程同步的重要性和实现方法,包括使用threading.Lock()和threading.RLock()进行资源保护,避免多线程环境下的竞态条件,并通过实例展示了不同情况下锁的使用效果。

0.目录

2. threading.Lock() 的必要性
3.观察block
4.threading.RLock() 的应用场景

1.参考

Thread Synchronization Mechanisms in Python

count += 1 不是原子操作,三步操作可能被中断,通过lock将三步操作“封装”为一步操作,要么执行,要么不执行。

counter = 0

def process_item(item):
    global counter
    ... do something with item ...
    counter += 1

# The reason for this is that the increment operation is actually executed in three steps; 
#first, the interpreter fetches the current value of the counter,
# then it calculates the new value,
# and finally, it writes the new value back to the variable.
View Code

Atomic Operations #

The simplest way to synchronize access to shared variables or other resources is to rely on atomic operations in the interpreter.

An atomic operation is an operation that is carried out in a single execution step, without any chance that another thread gets control.

 

 What kinds of global value mutation are thread-safe?

python的原子操作

A global interpreter lock (GIL) is used internally to ensure that only one thread runs in the Python VM at a time. In general, Python offers to switch among threads only between bytecode instructions; how frequently it switches can be set via sys.setcheckinterval(). Each bytecode instruction and therefore all the C implementation code reached from each instruction is therefore atomic from the point of view of a Python program.

In theory, this means an exact accounting requires an exact understanding of the PVM bytecode implementation. In practice, it means that operations on shared variables of built-in data types (ints, lists, dicts, etc) that “look atomic” really are.

For example, the following operations are all atomic (L, L1, L2 are lists, D, D1, D2 are dicts, x, y are objects, i, j are ints):

L.append(x)
L1.extend(L2)
x = L[i]
x = L.pop()
L1[i:j] = L2
L.sort()
x = y
x.field = y
D[x] = y
D1.update(D2)
D.keys()

These aren’t:

i = i+1
L.append(L[-1])
L[i] = L[j]
D[x] = D[x] + 1

Operations that replace other objects may invoke those other objects’ __del__() method when their reference count reaches zero, and that can affect things. This is especially true for the mass updates to dictionaries and lists. When in doubt, use a mutex!
View Code

 

2. threading.Lock()  的必要性

#!usr/bin/env python
#coding:utf-8
import sys
import time
import random
import logging

import threading
import Queue


lock = threading.Lock()     #'function-call ownership'
rlock = threading.RLock()   #thread ownership

logging.basicConfig(level=logging.DEBUG,
                    format = '%(asctime)s - %(threadName)-10s - %(levelname)s - %(message)s')
logger = logging.getLogger()


count = 0

class MyThread(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        
    def run(self):
        global count
        
        for i in range(100):
            count += 1
        logger.debug('{} finished, count is {}'.format(self.name, count))     

def main():
    logger.debug('initial count: {}'.format(count))
    
    thread_list = [MyThread() for i in range(2)]
    for t in thread_list:
        t.start()
    for t in thread_list:
        t.join()   

    logger.debug('final count: {}'.format(count))


if __name__ == '__main__':
    main()
View Code

修改run函数代码的不同输出:

    def run(self):
        global count

        for i in range(100):
            count += 1
        logger.debug('{} finished, count is {}'.format(self.name, count))     
# 在切换线程之前,某一线程已经完成,两个线程顺序完成,结果几乎不会有误
# 2017-08-20 12:19:30,857 - MainThread - DEBUG - initial count: 0
# 2017-08-20 12:19:30,858 - Thread-1   - DEBUG - Thread-1 finished, count is 100
# 2017-08-20 12:19:30,858 - Thread-2   - DEBUG - Thread-2 finished, count is 200
# 2017-08-20 12:19:30,858 - MainThread - DEBUG - final count: 200        
        
        time.sleep(0.001)
        for i in range(100):
            count += 1
        logger.debug('{} finished, count is {}'.format(self.name, count)) 
# 开头sleep导致两个线程几乎同时启动,结果可能有误
# 2017-08-20 12:24:59,046 - MainThread - DEBUG - initial count: 0
# 2017-08-20 12:24:59,048 - Thread-2   - DEBUG - Thread-2 finished, count is 124
# 2017-08-20 12:24:59,048 - Thread-1   - DEBUG - Thread-1 finished, count is 153
# 2017-08-20 12:24:59,048 - MainThread - DEBUG - final count: 153
        
        for i in range(10000):
            count += 1
        logger.debug('{} finished, count is {}'.format(self.name, count))  
# bytecodes足够导致两个线程交替运行,结果大概率有误      
# 2017-08-20 12:20:17,719 - MainThread - DEBUG - initial count: 0
# 2017-08-20 12:20:17,723 - Thread-1   - DEBUG - Thread-1 finished, count is 12438
# 2017-08-20 12:20:17,723 - Thread-2   - DEBUG - Thread-2 finished, count is 12616
# 2017-08-20 12:20:17,723 - MainThread - DEBUG - final count: 12616        
        
            
        with lock:
            for i in range(10000):
                count += 1
        logger.debug('{} finished, count is {}'.format(self.name, count))    
# lock直到某一线程完成,结果正确
# 2017-08-20 12:20:37,630 - MainThread - DEBUG - initial count: 0
# 2017-08-20 12:20:37,631 - Thread-1   - DEBUG - Thread-1 finished, count is 10000
# 2017-08-20 12:20:37,632 - Thread-2   - DEBUG - Thread-2 finished, count is 20000
# 2017-08-20 12:20:37,634 - MainThread - DEBUG - final count: 20000
        

        for i in range(10000):
            with lock:
                count += 1
        logger.debug('{} finished, count is {}'.format(self.name, count)) 
# 两个线程交替lock,结果正确        
# 2017-08-20 12:21:03,921 - MainThread - DEBUG - initial count: 0
# 2017-08-20 12:21:03,973 - Thread-1   - DEBUG - Thread-1 finished, count is 19979
# 2017-08-20 12:21:03,973 - Thread-2   - DEBUG - Thread-2 finished, count is 20000
# 2017-08-20 12:21:03,973 - MainThread - DEBUG - final count: 20000

 

3.观察block

    def run(self):
        global count

        all = range(10000)  #确保每个线程 +1 的次数
        while all != []: 
            if not lock.acquire(False):  #假设没有参数会导致block,则马上返回false当不block;否则返回true且acquire
                logger.debug('{} wait...{}'.format(self.name, len(all)))   
            else:
                try:
                    count += 1
                    all.pop()
                except Exception as err:
                    logger.debug('{} err, count is {}'.format(self.name, count))   
                finally:
                    # logger.debug('{} release {} {}'.format(self.name, count, len(all)))  #导致两个线程顺序执行???
                    lock.release()
        logger.debug('{} finished, count is {}'.format(self.name, count))   

输出:

2017-08-20 12:32:55,204 - MainThread - DEBUG - initial count: 0
2017-08-20 12:32:55,210 - Thread-1   - DEBUG - Thread-1 wait...9925
2017-08-20 12:32:55,210 - Thread-1   - DEBUG - Thread-1 wait...9925
2017-08-20 12:32:55,210 - Thread-1   - DEBUG - Thread-1 wait...9925
2017-08-20 12:32:55,210 - Thread-1   - DEBUG - Thread-1 wait...9925
2017-08-20 12:32:55,210 - Thread-1   - DEBUG - Thread-1 wait...9925
2017-08-20 12:32:55,210 - Thread-1   - DEBUG - Thread-1 wait...9925
2017-08-20 12:32:55,210 - Thread-1   - DEBUG - Thread-1 wait...9925
2017-08-20 12:32:55,211 - Thread-1   - DEBUG - Thread-1 wait...9925
2017-08-20 12:32:55,213 - Thread-1   - DEBUG - Thread-1 wait...9925
2017-08-20 12:32:55,213 - Thread-1   - DEBUG - Thread-1 wait...9925
2017-08-20 12:32:55,213 - Thread-1   - DEBUG - Thread-1 wait...9925
2017-08-20 12:32:55,213 - Thread-1   - DEBUG - Thread-1 wait...9925
2017-08-20 12:32:55,214 - Thread-1   - DEBUG - Thread-1 wait...9925
2017-08-20 12:32:55,214 - Thread-1   - DEBUG - Thread-1 wait...9925
2017-08-20 12:32:55,214 - Thread-1   - DEBUG - Thread-1 wait...9925
2017-08-20 12:32:55,214 - Thread-1   - DEBUG - Thread-1 wait...9925
2017-08-20 12:32:55,216 - Thread-2   - DEBUG - Thread-2 wait...6036
2017-08-20 12:32:55,216 - Thread-2   - DEBUG - Thread-2 wait...6036
2017-08-20 12:32:55,216 - Thread-2   - DEBUG - Thread-2 wait...6036
2017-08-20 12:32:55,216 - Thread-2   - DEBUG - Thread-2 wait...6036
2017-08-20 12:32:55,216 - Thread-2   - DEBUG - Thread-2 wait...6036
2017-08-20 12:32:55,217 - Thread-2   - DEBUG - Thread-2 wait...6036
2017-08-20 12:32:55,217 - Thread-2   - DEBUG - Thread-2 wait...6036
2017-08-20 12:32:55,217 - Thread-2   - DEBUG - Thread-2 wait...6036
2017-08-20 12:32:55,217 - Thread-2   - DEBUG - Thread-2 wait...6036
2017-08-20 12:32:55,217 - Thread-2   - DEBUG - Thread-2 wait...6036
2017-08-20 12:32:55,217 - Thread-2   - DEBUG - Thread-2 wait...6036
2017-08-20 12:32:55,217 - Thread-2   - DEBUG - Thread-2 wait...6036
2017-08-20 12:32:55,217 - Thread-2   - DEBUG - Thread-2 wait...6036
2017-08-20 12:32:55,217 - Thread-2   - DEBUG - Thread-2 wait...6036
2017-08-20 12:32:55,219 - Thread-2   - DEBUG - Thread-2 wait...6036
2017-08-20 12:32:55,219 - Thread-2   - DEBUG - Thread-2 wait...6036
2017-08-20 12:32:55,219 - Thread-2   - DEBUG - Thread-2 wait...6036
2017-08-20 12:32:55,219 - Thread-2   - DEBUG - Thread-2 wait...6036
2017-08-20 12:32:55,219 - Thread-2   - DEBUG - Thread-2 wait...6036
2017-08-20 12:32:55,220 - Thread-2   - DEBUG - Thread-2 wait...6036
2017-08-20 12:32:55,220 - Thread-2   - DEBUG - Thread-2 wait...6036
2017-08-20 12:32:55,220 - Thread-2   - DEBUG - Thread-2 wait...6036
2017-08-20 12:32:55,220 - Thread-2   - DEBUG - Thread-2 wait...6036
2017-08-20 12:32:55,220 - Thread-2   - DEBUG - Thread-2 wait...6036
2017-08-20 12:32:55,220 - Thread-2   - DEBUG - Thread-2 wait...6036
2017-08-20 12:32:55,220 - Thread-2   - DEBUG - Thread-2 wait...6036
2017-08-20 12:32:55,220 - Thread-2   - DEBUG - Thread-2 wait...6036
2017-08-20 12:32:55,220 - Thread-2   - DEBUG - Thread-2 wait...6036
2017-08-20 12:32:55,220 - Thread-2   - DEBUG - Thread-2 wait...6036
2017-08-20 12:32:55,221 - Thread-2   - DEBUG - Thread-2 wait...6036
2017-08-20 12:32:55,221 - Thread-2   - DEBUG - Thread-2 wait...6036
2017-08-20 12:32:55,221 - Thread-2   - DEBUG - Thread-2 wait...6036
2017-08-20 12:32:55,221 - Thread-2   - DEBUG - Thread-2 wait...6036
2017-08-20 12:32:55,226 - Thread-1   - DEBUG - Thread-1 finished, count is 13964
2017-08-20 12:32:55,236 - Thread-2   - DEBUG - Thread-2 finished, count is 20000
2017-08-20 12:32:55,236 - MainThread - DEBUG - final count: 20000

 

4.threading.RLock() 的应用场景

Problems with Simple Locking

lock = threading.Lock()

def get_first_part():
    with lock: # any thread that attempts to acquire the lock will block, even if the same thread is already holding the lock. 
        ... fetch data for first part from shared object
    return data    
 
def get_second_part():
    with lock:
        ... fetch data for second part from shared object
    return data  
    
def get_both_parts():
    with lock:  # other thread may modify the resource between the two calls
        first = get_first_part()
        # between the two calls
        second = get_second_part()
    return first, second    
    
# While simple locks will block if the same thread attempts to acquire the same lock twice, 
# a re-entrant lock only blocks if another thread currently holds the lock. 
rlock = threading.RLock()  

 

转载于:https://www.cnblogs.com/my8100/p/7399600.html

<think>好的,我现在要帮用户解决关于Pythonthreading.Lock()的正确使用,特别是与with语句结合的问题。首先,我得回顾一下用户的问题,确保我理解正确。 用户问的是如何正确使用threading.Lock(),以及self.lock = threading.Lock()with self.lock的结合用法。他们可能是在多线程编程中遇到了资源竞争的问题,想要确保线程安全。用户可能已经知道Lock的基本概念,但需要更具体的代码示例最佳实践。 接下来,我需要回忆PythonLock的机制。根据引用[1][3],LockRLock的区别在于RLock允许同一线程多次acquire,而Lock不行。但用户这里提到的是threading.Lock(),所以应该使用普通的Lock。 用户提到with语句,这让我想到上下文管理器,Lock对象支持上下文管理协议,所以使用with可以自动获取释放锁,避免忘记释放导致死锁。引用[3]中提到acquirerelease必须成对出现,使用with语句能很好地保证这一点。 接下来,我需要构建一个示例代码,展示如何将Lock作为实例变量,并在方法中使用with self.lock。比如,在一个类中初始化self.lock,然后在需要线程安全的方法中使用with语句。 然后,我需要分步骤解释代码。首先创建Lock实例,接着在需要同步的代码块前获取锁,使用with自动管理。还要强调锁的范围应尽量小,避免不必要的性能损耗。 用户可能还会关心常见错误,比如忘记释放锁,或者错误地共享锁实例。需要指出这些情况,并给出避免方法,比如使用with语句,或确保每个实例有自己的锁。 最后,根据用户的要求生成相关问题。比如LockRLock的区别,处理死锁的方法,以及多线程数据共享的技术。这些扩展问题可以帮助用户深入理解。 检查引用内容是否适用。引用[1][3]提到了RLockLock的区别,但用户的问题是关于Lock的,所以主要参考这些引用来强调正确使用acquirerelease的必要性,以及with语句的优势。 总结回答结构:先介绍Lock的作用,然后给出代码示例,分步解释,常见错误及解决方法,最后相关问题。确保内容清晰,符合用户需求,并正确应用引用中的知识点。</think>### Pythonthreading.Lock()与`with`语句的结合用法 #### 1. threading.Lock()基础作用 线程锁用于解决多线程并发操作共享资源时的竞争问题。通过`acquire()`获取锁`release()`释放锁,保证临界区代码的原子性执行[^3]。 #### 2. 推荐结合`with`语句的用法 ```python import threading class SharedCounter: def __init__(self): self.value = 0 self.lock = threading.Lock() # 创建Lock实例 def increment(self): with self.lock: # 自动获取释放锁 self.value += 1 print(f"Current value: {self.value}") ``` #### 3. 关键步骤解析 - **创建锁对象**:通过`self.lock = threading.Lock()`将锁作为实例属性 - **上下文管理器**:`with self.lock`语句等价于: ```python self.lock.acquire() try: # 临界区代码 finally: self.lock.release() ``` - **原子操作保证**:在`with`代码块内对`self.value`的操作不会被其他线程中断 #### 4. 典型错误场景 ```python # 错误示例:忘记释放锁 def unsafe_increment(self): self.lock.acquire() # 获取锁 self.value += 1 # 若后续代码抛出异常,锁将无法释放 ``` #### 5. 最佳实践建议 1. **锁作用域最小化**:只在必要代码段加锁 2. **避免嵌套锁**:`threading.Lock()`不支持重入,重复获取会导致死锁 3. **实例级锁策略**:建议为每个资源实例创建独立锁 ```python class DatabaseConnection: def __init__(self): self.lock = threading.Lock() self.connection = create_connection() def query(self, sql): with self.lock: # 保证连接操作的线程安全 return self.connection.execute(sql) ``` #### 6. 性能优化提示 - 当需要同时锁定多个资源时,应**按固定顺序获取锁**,避免死锁 - 对于读多写少场景,可考虑使用`threading.RLock()`或更高级的同步原语 $$ \text{锁持有时间} \propto \frac{1}{\text{系统吞吐量}} $$ (公式说明:锁持有的时间越长,系统并发性能越低)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值