美文网首页python
python3线程中的锁机制

python3线程中的锁机制

作者: CurryCoder | 来源:发表于2019-01-23 22:58 被阅读17次

    1.锁的形象解释

    有一个奇葩的房东,他家里有两个房间想要出租。这个房东很抠门,家里有两个房间,但却只有一把锁,不想另外花钱是去买另一把锁,也不让租客自己花钱加锁。这样租客只有先租到的那个人才能分配到锁。X先生,率先租到了房子,并且拿到了锁。而后来者Y先生,由于锁已经已经被X取走了,自己拿不到锁,也不能自己加锁,Y就不愿意了,也就不租了。换作其他人也一样,没有人会租第二个房间,直到X先生退租,把锁还给房东,可以让其他房客来取,第二间房间才能租出去。
    换句话说,就是房东同时只能出租一个房间,一但有人租了一个房间,拿走了唯一的锁,就没有人再在租另一间房了。
    回到线程中来,假设有两个线程A和B,A和B里的程序都加了同一个锁对象,当线程A率先执行到lock.acquire()(拿到全局唯一的锁后),线程B只能等到线程A释放锁lock.release()后(归还锁)才能运行lock.acquire()(拿到全局唯一的锁)并执行后面的代码。

    2.如何使用"锁"?

    import threading
    
    # 生成锁对象,全局唯一
    lock = threading.Lock()
    # 获取锁,没有获得到锁的程序会陷入阻塞,直到程序重新获取到锁才能往下执行
    lock.acquire()
    # 释放锁,此时,其他程序可以使用锁了
    lock.release()
    

    注意:lock.acquire()与lock.release()必须成对使用,否则会造成死锁!!!为了有时候忘记,推荐使用上下文管理器来加锁,类似于tensorflow中的with tf.Session() as sess:

    lock = threading.Lock()
    with lock:
        # 写自己的业务逻辑代码
        pass
    

    上面的with语句会在代码执行前自动获取锁,在执行结束后自动释放锁

    3.可重入锁(RLock)

    有时候在同一个线程中,我们可能会多次请求同一资源(就是,获取同一个锁的钥匙),俗称锁嵌套。如果还是按照常规的做法,会造成死锁的。比如,下面这段代码,你可以试着运行一下,会发现并没有输出结果。

    import threading
    
    def main():
        n = 0
        lock = threading.Lock()
        with lock:
            for i in range(10):
                n += 1
                with lock:
                    print(n)
    
    t1 = threading.Thread(target=main)
    t1.start()
    

    原因:在第二次获取锁时,发现锁已经被同一线程的人拿走了,自己也就理所当然拿不到锁了,所以程序卡住了。
    解决方法:threading模块除了提供Lock锁之外,还提供了一种可重入锁RLock,专门来处理这个问题。

    import threading
    
    def main():
        n = 0
        lock = threading.RLock()    # 生成可重入锁对象
        with lock:
            for i in range(10):
                n += 1
                with lock:
                    print(n)
    
    t1 = threading.Thread(target=main)
    t1.start()
    

    注意: 可重入锁只能用在同一线程里,放松对锁钥匙的获取,其他与普通的Lock没啥不同。

    4.防止死锁的加锁机制

    死锁出现的情况:1.同一线程中,嵌套获取同一把锁,造成死锁;2.多个线程,不按顺序同时获取多个锁,造成死锁。例如:线程1:嵌套获取A,B两个锁;线程2:嵌套获取B,A两个锁。 由于两个线程是交替执行的,是有机会遇到线程1获取到锁A,而未获取到锁B,在同一时刻,线程2获取到锁B,而未获取到锁A。由于锁B已经被线程2获取了,所以线程1就卡在了获取锁B处,由于是嵌套锁,线程1未获取并释放B,是不能释放锁A的,这是导致线程2也获取不到锁A,也卡住了。两个线程,各执一锁,各不让步。造成死锁。
    解决方法: 只要两个(或多个)线程获取嵌套锁时,按照固定顺序就能保证程序不会进入死锁状态。那么问题就转化成如何保证这些锁是按顺序的?(人工自觉,人工识别( 写一个辅助函数来对锁进行排序))

    import threading
    from contextlib import contextmanager
    
    # 人工识别方法来排序
    # Thread-local state to stored information on locks already acquired
    _local = threading.local()
    
    @contextmanager
    def acquire(*locks):
        # Sort locks by object identifier
        locks = sorted(locks, key=lambda x: id(x))
    
        # Make sure lock order of previously acquired locks is not violated
        acquired = getattr(_local,'acquired',[])
        if acquired and max(id(lock) for lock in acquired) >= id(locks[0]):
            raise RuntimeError('Lock Order Violation')
    
        # Acquire all of the locks
        acquired.extend(locks)
        _local.acquired = acquired
    
        try:
            for lock in locks:
                lock.acquire()
            yield
        finally:
            # Release locks in reverse order of acquisition
            for lock in reversed(locks):
                lock.release()
            del acquired[-len(locks):]
    # 使用上面定义的人工识别方法
    import threading
    x_lock = threading.Lock()
    y_lock = threading.Lock()
    
    def thread_1():
    
        while True:
            with acquire(x_lock):
                with acquire(y_lock):
                    print('Thread-1')
    
    def thread_2():
        while True:
            with acquire(y_lock):
                with acquire(x_lock):
                    print('Thread-2')
    
    t1 = threading.Thread(target=thread_1)
    t1.daemon = True
    t1.start()
    
    t2 = threading.Thread(target=thread_2)
    t2.daemon = True
    t2.start()
    

    分析:表面上thread_1的先获取锁x,再获取锁y,而thread_2是先获取锁y,再获取x。 但是实际上,acquire函数,已经对x,y两个锁进行了排序。所以thread_1,hread_2都是以同一顺序来获取锁的,是不会造成死锁的。

    5.GIL(全局锁)

    多进程是真正的并行,而多线程是伪并行,实际上他只是交替执行。由于GIL导致多线程实际上是伪并行的。因为任何Python线程执行前,必须先获得GIL锁。然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都给上了锁。所以,多线程在Python中只能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核。
    注意:GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。而Python解释器,并不是只有CPython。除它之外,还有PyPy,Psyco,JPython,IronPython等。在绝大多数情况下,我们通常都认为 Python == CPython,所以也就默许了Python具有GIL锁这个事。
    解决方法: 1.使用多进程代替多线程;2.更换Python解释器,不使用CPython

    6.参考博客链接

    python编程时光

    相关文章

      网友评论

        本文标题:python3线程中的锁机制

        本文链接:https://www.haomeiwen.com/subject/kzkejqtx.html