社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

Python并行编程(二):多线程锁机制利用Lock与RLock实现线程同步

若数 • 6 年前 • 483 次点击  
阅读 37

Python并行编程(二):多线程锁机制利用Lock与RLock实现线程同步

什么是锁机制?

要回答这个问题,我们需要知道为什么需要使用锁机制。前面我们谈到一个进程内的多个线程的某些资源是共享的,这也是线程的一大优势,但是也随之带来一个问题,即当两个及两个以上的线程同时访问共享资源时,如果此时没有预设对应的同步机制,就可能带来同一时刻多个线程同时访问同一个共享资源,即出现竞态,多数情况下我们是不希望出现这样的情况的,那么怎么避免呢?

Lock() 管理线程

先看一段代码:

import threading
import time
resource = 0

count = 1000000

resource_lock = threading.Lock()


def increment():
    global resource
    for i in range(count):
        resource += 1


def decerment():
    global resource
    for i in range(count):
        resource -= 1


increment_thread = threading.Thread(target=increment)
decerment_thread = threading.Thread(target=decerment)

increment_thread.start()
decerment_thread.start()

increment_thread.join()
decerment_thread.join()

print(resource)

复制代码

运行截图如下:

运行结果
当我们多次运行时,可以看到最终的结果都几乎不等于我们期待的值即resource初始值0

为什么呢? 原因就是因为 += 和 -=并不是原子操作。可以使用dis模块查看字节码:

import dis
def add(total):
    total += 1
def desc(total):
    total -= 1
total = 0
print(dis.dis(add))
print(dis.dis(desc))
# 运行结果:
#   3           0 LOAD_FAST                0 (total)
#               3 LOAD_CONST               1 (1)
#               6 INPLACE_ADD
#               7 STORE_FAST               0 (total)
#              10 LOAD_CONST               0 (None)
#              13 RETURN_VALUE
# None
#   5           0 LOAD_FAST                0 (total)
#               3 LOAD_CONST               1 (1)
#               6 INPLACE_SUBTRACT
#               7 STORE_FAST               0 (total)
#              10 LOAD_CONST               0 (None)
#              13 RETURN_VALUE
# None

复制代码

那么如何保证初始值为0呢? 我们可以利用Lock(),代码如下:

import threading
import time
resource = 0

count = 1000000

resource_lock = threading.Lock()


def increment():
    global resource
    for i in range(count):
        resource_lock.acquire()
        resource += 1
        resource_lock.release()


def decerment():
    global resource
    for i in range(count):
        resource_lock.acquire()
        resource -= 1
        resource_lock.release()


increment_thread = threading.Thread(target=increment)
decerment_thread = threading.Thread(target=decerment)

increment_thread.start()
decerment_thread.start()

increment_thread.join()
decerment_thread.join()

print(resource)

复制代码

运行截图如下:

运行结果
从运行结果可以看到,不论我们运行多少次改代码,其resource的值都为初始值0, 这就是Lock()的功劳,即它可以将某一时刻的访问限定在单个线程或者单个类型的线程上,在访问锁定的共享资源时,必须要现获取对应的锁才能访问,即要等待其他线程释放资源,即resource_lock.release() 当然为了防止我们对某个资源锁定后,忘记释放锁,导致死锁,我们可以利用上下文管理器管理锁实现同样的效果:

import threading
import time
resource = 0

count = 1000000

resource_lock = threading.Lock()


def increment():
    global resource
    for i in range(count):
        with resource_lock:
                resource += 1


def decerment():
    global resource
    for i in range(count):
        with resource_lock:
                resource -= 1
        


increment_thread = threading.Thread(target=increment)
decerment_thread = threading.Thread(target=decerment)

increment_thread.start()
decerment_thread.start()
复制代码

RLock() 与Lock()的区别

我们需要知道Lock()作为一个基本的锁对象,一次只能一个锁定,其余锁请求,需等待锁释放后才能获取,否则会发生死锁:

import threading
resource.lock = threading.lock()

resource = 0

resource.lock.acquire()
resource.lock.acquire()
resource += 1
resource.lock.release()
resource.lock.release()
复制代码

为解决同一线程中不能多次请求同一资源的问题,python提供了“可重入锁”:threading.RLockRLock内部维护着一个Lock 和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次acquire。直到一个线程所有的acquire都被release,其他的线程才能获得资源 。用法和threading.Lock类相同,即比如递归锁的使用:

import threading
lock = threading.RLock()
def dosomething(lock):
    lock.acquire()
    # do something
    lock.release()
    
lock.acquire()
dosomething(lock)
lock.release()
复制代码
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/31398
 
483 次点击