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

什么是锁机制?

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

Lock() 管理线程

先看一段代码:ui

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)

复制代码

运行截图以下: spa

运行结果
当咱们屡次运行时,能够看到最终的结果都几乎不等于咱们期待的值即 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(),代码以下:code

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)

复制代码

运行截图以下: cdn

运行结果
从运行结果能够看到,不论咱们运行多少次改代码,其 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类相同,即好比递归锁的使用:blog

import threading
lock = threading.RLock()
def dosomething(lock):
    lock.acquire()
    # do something
    lock.release()
    
lock.acquire()
dosomething(lock)
lock.release()
复制代码
相关文章
相关标签/搜索