摘要
首先讲解不加锁在多线程中会导致的问题,然后用实例说明如何通过加锁让函数变为线程安全的函数。也通过实例说明了RLock
和Lock
的区别:在同一线程内,对RLock
进行多次acquire()
操作,程序不会阻塞。
threading.Lock
的用法
下面是一个python多线程的例子:
import threading
# global var
count = 0
# Define a function for the thread
def print_time(threadName):
global count
c=0
while(c<100):
c+=1
count+=1
print("{0}: set count to {1}".format( threadName, count) )
# Create and run threads as follows
try:
threading.Thread( target=print_time, args=("Thread-1", ) ).start()
threading.Thread( target=print_time, args=("Thread-2", ) ).start()
threading.Thread( target=print_time, args=("Thread-3", ) ).start()
except Exception as e:
print("Error: unable to start thread")
在这个例子中,我们start了3个线程,每个线程都会对全局资源count
进行改写操作。得到的结果如下,每个thread都会交替对count值进行修改。
Thread-1: set count to 198
Thread-2: set count to 199
Thread-1: set count to 200
Thread-2: set count to 201
Thread-1: set count to 202
Thread-2: set count to 203
Thread-1: set count to 204
Thread-2: set count to 205
由于多线程共享进程的资源和地址空间,因此,在对这些公共资源进行操作时,为了防止这些公共资源出现异常的结果,必须考虑线程的同步和互斥问题。我们可以对上例中的print_time()
中访问资源的代码加锁,就可以把这个函数变为线程安全的函数。具体代码如下:
import threading
# global var
count = 0
lock = threading.Lock()
# Define a function for the thread
def print_time(threadName):
global count
c=0
with lock:
while(c<100):
c+=1
count+=1
print("{0}: set count to {1}".format( threadName, count) )
# Create and run threads as follows
try:
threading.Thread( target=print_time, args=("Thread-1", ) ).start()
threading.Thread( target=print_time, args=("Thread-2", ) ).start()
threading.Thread( target=print_time, args=("Thread-3", ) ).start()
except Exception as e:
print("Error: unable to start thread")
通过threading.Lock()
,就能实现加锁。这样每个thread对count进行改动期间,就不会有其它的thread插入进来改动count。得到的输出如下:
Thread-2: set count to 199
Thread-2: set count to 200
Thread-3: set count to 201
Thread-3: set count to 202
Thread-3: set count to 203
Thread-3: set count to 204
锁的使用,有两种方法,上面的是最简单的通过with lock
来操作。
还有另一种用法,是通过Lock的acquire()
和release()
函数来控制加锁和解锁,如下例,得到的结果和上例相同:
import threading
# global var
count = 0
lock = threading.Lock()
# Define a function for the thread
def print_time(threadName):
global count
c=0
if(lock.acquire()):
while(c<100):
c+=1
count+=1
print("{0}: set count to {1}".format( threadName, count) )
lock.release()
# Create and run threads as follows
try:
threading.Thread( target=print_time, args=("Thread-1", ) ).start()
threading.Thread( target=print_time, args=("Thread-2", ) ).start()
threading.Thread( target=print_time, args=("Thread-3", ) ).start()
except Exception as e:
print("Error: unable to start thread")
Lock
与RLock
的区别
上例中,我们使用threading.Lock()
来进行加锁。threading
中还提供了另外一个threading.RLock()
,那么问题来了,Lock
与RLock
有什么区别呢?
既然要讨论区别,那我们应该明白,他们的功能,大部分是相同的,很多情况下可以通用,但有细微的区别。
从原理上来说:在同一线程内,对RLock
进行多次acquire()
操作,程序不会阻塞。
用一个例子来说明:
import threading
lock = threading.RLock()
def f():
with lock:
g()
h()
def g():
with lock:
h()
do_something1()
def h():
with lock:
do_something2()
def do_something1():
print('do_something1')
def do_something2():
print('do_something2')
# Create and run threads as follows
try:
threading.Thread( target=f ).start()
threading.Thread( target=f ).start()
threading.Thread( target=f ).start()
except Exception as e:
print("Error: unable to start thread")
每个thread
都运行f()
,f()
获取锁后,运行g()
,但g()
中也需要获取同一个锁。如果用Lock
,这里多次获取锁,就发生了死锁。
但我们代码中使用了RLock
。在同一线程内,对RLock
进行多次acquire()
操作,程序不会堵塞,所以我们可以得到如下的输出:
do_something2
do_something1
do_something2
do_something2
do_something1
do_something2
do_something2
do_something1
do_something2