10.3.8 控制资源访问
除了同步线程操作,还有一点很重要,要能够控制对共享资源的访问,从而避免破坏或丢失数据。Python的内置数据结构(列表、字典等)是线程安全的。这是Python使用原子字节码来管理这些数据结构的一个副作用(更新过程中不会释放保护Python内部数据结构的全局解释器锁GIL(global Interpreter Lock))。Python中实现的其他数据几个或更简单的类型(如整数和浮点数)则没有这个保护。要保证同时安全地访问一个对象,可以使用一个Lock对象。
import logging
import random
import threading
import time
class Counter:
def __init__(self,start=0):
self.lock = threading.Lock()
self.value = start
def increment(self):
logging.debug('Waiting for lock')
self.lock.acquire()
try:
logging.debug('Acquired lock')
self.value = self.value + 1
finally:
self.lock.release()
def worker(c):
for i in range(2):
pause = random.random()
logging.debug('Sleeping %0.02f',pause)
time.sleep(pause)
c.increment()
logging.debug('Done')
logging.basicConfig(
level=logging.DEBUG,
format='(%(threadName)-10s) %(message)s',
)
counter = Counter()
for i in range(2):
t = threading.Thread(target=worker,args=(counter,))
t.start()
logging.debug('Waiting for worker threads')
main_thread = threading.main_thread()
for t in threading.enumerate():
if t is not main_thread:
t.join()
logging.debug('Counter: %d',counter.value)
在这个例子中,worker()函数使一个Counter实例递增,这个示例管理着一个Lock,以避免两个线程同时改变其内部状态。如果没有使用Lock,就有可能丢失一次对value属性的修改。
运行结果:
要确定是否有另一个线程请求这个锁而不影响当前线程,可以向acquire()的blocking参数传入False。在下一个例子找那个,worker()向要分别得到3次锁,并统计为得到锁而尝试的次数。与此同时,;ock_holder()在占有和释放锁之间循环,每个状态会短暂暂停,以模拟负载情况。
import logging
import threading
import time
def lock_holder(lock):
logging.debug('Starting')
while True:
lock.acquire()
try:
logging.debug('Holding')
time.sleep(0.5)
finally:
logging.debug('Not holding')
lock.release()
time.sleep(0.5)
def worker(lock):
logging.debug('Starting')
num_tries = 0
num_acquires = 0
while num_acquires < 3:
time.sleep(0.5)
logging.debug('Trying to acquire')
have_it = lock.acquire(0)
try:
num_tries += 1
if have_it:
logging.debug('Iteration %d: Acquire',num_tries)
num_acquires += 1
else:
logging.debug('Iteration %d: Not acquire',num_tries)
finally:
if have_it:
lock.release()
logging.debug('Done after %d iterations',num_tries)
logging.basicConfig(
level=logging.DEBUG,
format='(%(threadName)-10s) %(message)s',
)
lock = threading.Lock()
holder = threading.Thread(
target=lock_holder,
args=(lock,),
name='LockHolder',
daemon=True,
)
holder.start()
worker = threading.Thread(
target=worker,
args=(lock,),
name='Worker',
)
worker.start()
worker()需要超过3次的迭代才能得到3次锁。
运行结果: