并发模型:线程与锁(2)

> 这是并发模型:线程与锁 的第二篇,第一篇地址为: 《并发模型:线程与锁(1)》https://mp.weixin.qq.com/s/6Xxhw31yJNUCh-79Sg8ckQ

超越内置锁

可重入锁

Lock() 虽然方便,但限制很多:

  1. 一个线程因为等待内置锁而进入阻塞之后,就无法中断该线程

  2. Lock() 不知道当前拥有锁的线程是否是当前线程,如果当前线程获取了锁,再次获取也会阻塞。

重入锁是(threading.RLock)一个可以被同一个线程多次获取的同步基元组件。在内部,它在基元锁的锁定/非锁定状态上附加了 "所属线程" 和 "递归等级" 的概念。在锁定状态下,某些线程拥有锁 ; 在非锁定状态下, 没有线程拥有它。

若要锁定锁,线程调用其 acquire() 方法;一旦线程拥有了锁,方法将返回。若要解锁,线程调用 release() 方法。 acquire()/release() 对可以嵌套;只有最终 release() (最外面一对的 release() ) 将锁解开,才能让其他线程继续处理 acquire() 阻塞。

threading.RLock 提供了显式的 acquire() 和 release() 方法 一个好的实践是:

lock = threading.RLock()

Lock 和 RLock 的使用区别如下:

#rlock_tut.py
import threading
num = 0
lock = Threading.Lock()
lock.acquire()
num += 1
lock.acquire() # 这里会被阻塞
num += 2
lock.release()

# With RLock, that problem doesn’t happen.
lock = Threading.RLock()
lock.acquire()
num += 3
lock.acquire() # 不会被阻塞.
num += 4
lock.release()
lock.release() # 两个锁都需要调用 release() 来释放.

超时

使用内置锁时,阻塞的线程无法被中断,程序不能从死锁恢复,可以给锁设置超时时间来解决这个问题。

timeout 参数需要 python3.2+

import time
from threading import Thread, Lock

lock1 = RLock()
lock2 = RLock()

# 这个程序会一直死锁下去,如果想突破这个限制,可以在获取锁的时候加上超时时间
# > python threading 没有实现 销毁(destroy),停止(stop),暂停(suspend),继续(resume),中断(interrupt)等


class T1(Thread):
    def run(self):
        print("start run T1")
        lock1.acquire()
        # lock1.acquire(timeout=2) # 设置超时时间可避免死锁
        time.sleep(1)
        lock2.acquire()
        # lock2.acquire(timeout=2) # 设置超时时间可避免死锁
        lock1.release()
        lock2.release()


class T2(Thread):
    def run(self):
        print("start run T2")
        lock2.acquire()
        # lock2.acquire(timeout=2) # 设置超时时间可避免死锁
        time.sleep(1)
        lock1.acquire()
        # lock1.acquire(timeout=2) # 设置超时时间可避免死锁
        lock2.release()
        lock1.release()


def test():
    t1, t2 = T1(), T2()
    t1.start()
    t2.start()

    t1.join()
    t2.join()


if __name__ == "__main__":
    test()

交替锁

如果我们要在链表中插入一个节点。一种做法是用锁保护整个链表,但链表加锁时其它使用者无法访问。交替锁可以只所追杀链表的一部分,允许不涉及被锁部分的其它线程自由访问。

交替锁

from random import randint
from threading import Thread, Lock

class Node(object):

    def __init__(self, value, prev=None, next=None):
        self.value = value
        self.prev = prev
        self.next = next
        self.lock = Lock()


class SortedList(Thread):

    def __init__(self, head):
        Thread.__init__(self)
        self.head = head

    def insert(self, value):
        head = self.head
        node = Node(value)
        print("insert: %d" % value)
        while True:
            if head.value <= value:
                if head.next != None:
                    head = head.next
                else:
                    head.lock.acquire()
                    head.next = node
                    node.prev = head
                    head.lock.release()
                    break
            else:
                prev = head.prev
                prev.lock.acquire()
                head.lock.acquire()
                if prev != None:
                    prev.next = node
                else:
                    self.head = node
                node.prev = prev
                prev.lock.release()
                node.next = head
                head.prev = node
                head.lock.release()
                break

    def run(self):
        for i in range(5):
            self.insert(randint(10, 20))


def test():
    head = Node(10)
    t1 = SortedList(head)
    t2 = SortedList(head)
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    while head:
        print(head.value)
        head = head.next


if __name__ == "__main__":
    test()

这种方案不仅可以让多个线程并发的进行链表插入操作,还能让其他的链表操作安全的并发。

条件变量

并发编程经常需要等待某个事件发生。比如从队列删除元素前需要等待队列非空、向缓存添加数据前需要等待缓存有足够的空间。条件变量就是为这种情况设计的。

条件变量总是与某种类型的锁对象相关联,锁对象可以通过传入获得,或者在缺省的情况下自动创建。当多个条件变量需要共享同一个锁时,传入一个锁很有用。锁是条件对象的一部分,不必单独地跟踪它。

条件变量服从上下文管理协议:使用 with 语句会在它包围的代码块内获取关联的锁。 acquire() 和 release() 方法也能调用关联锁的相关方法。

其它方法必须在持有关联的锁的情况下调用。 wait() 方法释放锁,然后阻塞直到其它线程调用 notify() 方法或 notify_all() 方法唤醒它。一旦被唤醒, wait() 方法重新获取锁并返回。它也可以指定超时时间。

#condition_tut.py
import random, time
from threading import Condition, Thread
"""
'condition' variable will be used to represent the availability of a produced
item.
"""
condition = Condition()
box = []
def producer(box, nitems):
    for i in range(nitems):
        time.sleep(random.randrange(2, 5))  # Sleeps for some time.
        condition.acquire()
        num = random.randint(1, 10)
        box.append(num)  # Puts an item into box for consumption.
        condition.notify()  # Notifies the consumer about the availability.
        print("Produced:", num)
        condition.release()
def consumer(box, nitems):
    for i in range(nitems):
        condition.acquire()
        condition.wait()  # Blocks until an item is available for consumption.
        print("%s: Acquired: %s" % (time.ctime(), box.pop()))
        condition.release()
threads = []
"""
'nloops' is the number of times an item will be produced and
consumed.
"""
nloops = random.randrange(3, 6)
for func in [producer, consumer]:
    threads.append(Thread(target=func, args=(box, nloops)))
    threads[-1].start()  # Starts the thread.
for thread in threads:
    """Waits for the threads to complete before moving on
       with the main script.
    """
    thread.join()
print("All done.")

原子变量

与锁相比使用原子变量的优点:

  1. 不会忘记在正确的时候获取锁

  2. 由于没有锁的参与,对原子变量的操作不会引发死锁。

  3. 原子变量时无锁(lock-free)非阻塞(non-blocking)算法的基础,这种算法可以不用锁和阻塞来达到同步的目的。

python 不支持原子变量

总结

优点

线程与锁模型最大的优点是适用面广,更接近于“本质”--近似于对硬件工作方式的形式化--正确使用时效率高。 此外,线程与锁模型也可轻松的集成到大多数编程语言。

缺点

  1. 线程与锁模型没有为并行提供直接的支持

  2. 线程与锁模型只支持共享内存模型,如果要支持分布式内存模型,就需要寻求其他技术的帮助。

  3. 用线程与锁模型编写的代码难以测试(比如死锁问题可能很久才会出现),出了问题后很难找到问题在哪,并且bug难以复现

  4. 代码难以维护(要保证所有对象的同步都是正确的、必须按 顺序来获取多把锁、持有锁时不调用外星方法。还要保证维护代码的开发者都遵守这个规则

参考链接

  • Let’s Synchronize Threads in Python

  • 哲学家进餐问题

References

[1] 哲学家进餐问题: https://zh.wikipedia.org/wiki/%E5%93%B2%E5%AD%A6%E5%AE%B6%E5%B0%B1%E9%A4%90%E9%97%AE%E9%A2%98 

[2] Let’s Synchronize Threads in Python: https://hackernoon.com/synchronization-primitives-in-python-564f89fee732?gi=ce162d119247

[3] 并发模型:线程与锁(1)https://mp.weixin.qq.com/s/6Xxhw31yJNUCh-79Sg8ckQ

最后,感谢女朋友支持和包容,比❤️

也可以在公号输入以下关键字获取历史文章:公号&小程序 | 设计模式 | 并发&协程

扫码关注,也可以点下面的广告支持一下
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值