再谈Python多线程--threading各类锁

使用多线程的好处是提高执行效率,但同时带来了数据同步的问题。即多个线程同时对一个对象进行操作时,可能会出现资源冲突的问题;在不加锁的情况下,代码可能并未像我们想向的那样工作。举个栗子:
import threading

n = 2
max_n = 10000
x = 0

def countup(n):
    global x
    for i in range(n):
        x += 1
        print '%s: %s\r\n' % (threading.currentThread().getName(), x)

for i in range(n):
    t = threading.Thread(target=countup, args=(max_n,))
    t.start()
执行结果:
Thread-1: 1
Thread-2: 2
...
Thread-2: 19912
按照期望最后应该打印到20000才结束,而实际上只打印了19912次,并且每次执行的结果很可能是不一样的。

为了保证多线程对共享资源的访问顺序,一般会引入锁机制。有了锁的加入,当子线程在操作共享资源时会先对其进行锁定,之后再进行操作;而此时其它的线程是不能对该共享资源进行操作的。这样就保证了同一个时间只有一个线程在操作共享资源。在Python中多线程锁有几种实现形式:
  • threading.Lock
  • threading.RLock
  • threading.Condition 
  • threading.Semaphore 
  • threading.Event 
  • threading.Timer 
  • threading.Barrier

首先, Lock是我们最常用的锁,它主要提供2个操作方法:acquire【申请锁】、release【释放锁】。
import threading

n = 2
max_n = 10000
x = 0
lock = threading.Lock()

def countup(n):
    global x
    for i in range(n):
        lock.acquire()
        x += 1
        lock.release()
        print '%s: %s\r\n' % (threading.currentThread().getName(), x)

for i in range(n):
    t = threading.Thread(target=countup, args=(max_n,))
    t.start()
这次在加了锁之后,程序终于可以正常工作了。因为我们在对x变量进行操作的前进行了加锁操作,保证了对x的修改操作是一个原子操作(不可被分割的操作)。

RLock是Recursion Lock的简称,即递归锁。它主要用于递归操作中,比如:递归函数。它与Lock的区别在于RLock在同一个线程内可以重复申请,所以它可以被用于递归操作中。
import threading

n = 2
max_n = 10
x = 0
lock = threading.RLock()


def countup(m):
    global x
    if m > 0:
        lock.acquire()
        x += 1
        countup(m - 1)
        lock.release()
        print '%s: %s\r\n' % (threading.currentThread().getName(), x)
    else:
        return


for i in range(n):
    t = threading.Thread(target=countup, args=(max_n,))
    t.start()
上面我们使用递归替代了for循环,并且递归调用是在获取到锁的过程中。如果你把上面的RLock替换成Lock,那么它将会被自己组塞住。当然你也可以使用尾递归的写法,那样还是可以使用Lock模块;这里只是说明RLock的应用场景。

Condition是一把有条件的锁。本质上它是对Lock/RLock等基本锁对象的封装,在实例Condition对象时需要传入需要的基本锁。而在使用上Condition除了可以使用acquire和release方法,还有wait、notify、notifyAll等方法。



猜你喜欢

转载自blog.csdn.net/five3/article/details/78553823