python学习之路--threading Lock对象

前言
多线程编程最需要注意的是两个点:1.线程同步,2.竞争资源互斥访问。同步,更多的是指线程之间的协同,比如线程B的工作依赖于线程A的结果,即线程之间具有依赖协同的关系。互斥,指的是多个线程对临界资源的访问而产生互斥的关系。这种访问的方式可以是读,也可以是写。举个简单的例子:
Producer-Consumer
这里有两种工作线程,一种Producer,负责向Buffer里面放资源(假设是一个整数)。另一种是Consumer,负责从Buffer里面取出资源来做一些计算。Buffer,可以理解为一个存资源的地方,大小为100。Producer和Consumer可以有多个。那么这里就同步和互斥的问题了。首先,不官是生产者还是消费者,都需要对Buffer进行互斥访问,这是对Buffer这个资源本身而言,可以用一个二值信号量(mutex)来控制对Buffer的互斥访问。其次,对Buffer里面的资源(整数)的访问也需要进行控制。如:Buffer初始为空时,对生产者而言,它有100个资源(空位)可以使用。而对于消费者而言,它能使用的资源个数则为0(Buffer里面没有数可以取)。所以可以使用信号量(Semaphore)来分别表示可用资源的多少。(具体参考《操作系统》相关书籍的互斥和同步章节)
下面进入我们的正题,谈谈python中threading.Lock的使用。

threading.Lock

先上一段代码

import threading
ticket_lock = threading.Lock()
ticket_nums = 20

class SaleTicketThread(threading.Thread):
    def __init__(self, name):
        threading.Thread.__init__(self)
        self.name = name
        print "init thread:%s" % self.name

    def run(self):
        global ticket_nums
        while ticket_nums > 0:
            ticket_lock.acquire()

            ticket_nums = ticket_nums - 1
            print "%s sell a ticket and %s tickets left" % (self.name, ticket_nums)
            ticket_lock.release()

thread_names_list = ["Thread-1", "Thread-2"]
threads = []

for name in thread_names_list:
    thread = SaleTicketThread(name)
    threads.append(thread)

for thread in threads:
    thread.start()

for thread in threads:
    thread.join()

print "Exit main thread"

运行结果:

init thread:Thread-1
init thread:Thread-2
Thread-1 sell a ticket and 19 tickets left
Thread-1 sell a ticket and 18 tickets left
Thread-1 sell a ticket and 17 tickets left
Thread-1 sell a ticket and 16 tickets left
Thread-2 sell a ticket and 15 tickets left
Thread-1 sell a ticket and 14 tickets left
Thread-2 sell a ticket and 13 tickets left
Thread-1 sell a ticket and 12 tickets left
Thread-2 sell a ticket and 11 tickets left
Thread-1 sell a ticket and 10 tickets left
Thread-2 sell a ticket and 9 tickets left
Thread-1 sell a ticket and 8 tickets left
Thread-2 sell a ticket and 7 tickets left
Thread-1 sell a ticket and 6 tickets left
Thread-2 sell a ticket and 5 tickets left
Thread-1 sell a ticket and 4 tickets left
Thread-2 sell a ticket and 3 tickets left
Thread-1 sell a ticket and 2 tickets left
Thread-2 sell a ticket and 1 tickets left
Thread-1 sell a ticket and 0 tickets left
Thread-2 sell a ticket and -1 tickets left
Exit main thread

这段代码,简单的模拟了卖票的过程,这里的tickets就是临界资源。对tickets进行访问(读写操作)的代码段叫做临界区。ticket_lock这把锁的作用就是控制对ticket的正确访问。在访问tickets前,都需要去获取这把锁(ticket_lock.acquire()),若这把锁已经被其他线程所获取,则acquire()将会被阻塞。访问完tickets资源(这里对tickets的数量进行-1操作,并打印剩余票数),要释放资源锁(ticket_lock.release())。
threading.Lock对象两个核心方法:
acquire(bloking =True)
用来获取锁,锁处于locked=True状态则会阻塞,Python3.x对该方法加入了timeout机制(非阻塞)。
release()
用于释放锁对象,若锁未被任何线程持有(locked=False),调用该方法会抛出RuntimeError异常。
从上面的运行结果中,细心的同学会发现最后一行出现了ticket_num = -1的情况,这并不是我们所期望的。来看看为何会产生这样的结果。首先,线程跑起来后,会竞争cpu的使用权,这是我们控制不了的。看看我们的run()方法:

def run(self):
        global ticket_nums
        while ticket_nums > 0:
            ticket_lock.acquire()

            ticket_nums = ticket_nums - 1
            print "%s sell a ticket and %s tickets left" % (self.name, ticket_nums)
            ticket_lock.release()

我们想象这样一种情况:当ticket_nums = 1时,此时线程1运行到:

ticket_lock.acquire()# 刚刚获取到锁,但未将   ticket_nums减一。

而线程2在:

 while ticket_nums > 0:

线程2处于while 判断条件处,由于ticket_nums=1(由于ticket_nums还未被减到0),所以线程2也能进去while循环并尝试获取锁。当线程1卖完票,对ticket_nums - 1后释放锁。线程2获取到该锁,也去对ticket_num做减一操作(线程2以为ticket_nums =1,其实已经为0了),得到ticket_nums = -1的结果。由于我们读取数据(while ticket_nums > 0:)和操作数据(ticket_nums = ticket_nums - 1)构成的操作序列并非原子操作,所以可能出现这种非预期的结果。我们改代码如下能解决这个问题(可能不是完全安全的方法),在修改值前再判断一下该值是否满足条件(拉近判断条件和修改操作的距离):

def run(self):
        global ticket_nums
        while ticket_nums > 0:
            ticket_lock.acquire()
            if ticket_nums !=0:
                ticket_nums = ticket_nums - 1
                print "%s sell a ticket and %s tickets left" % (self.name, ticket_nums)
            ticket_lock.release()

猜你喜欢

转载自blog.csdn.net/Mybigkid/article/details/78375529