专栏首页逸鹏说道Python3 与 C# 并发编程之~ 线程篇3
原创

Python3 与 C# 并发编程之~ 线程篇3

锁专题扩展

1.加锁机制

在多线程程序中,死锁问题很大一部分是由于线程同时获取多个锁造成的,eg:一个线程获取了第一个锁,然后在获取第二个锁的 时候发生阻塞,那么这个线程就可能阻塞其他线程的执行,从而导致整个程序假死。

解决死锁问题的一种方案是为程序中的每一个锁分配一个唯一的id,然后只允许按照升序规则来使用多个锁,当时举了个小明小张转账的简单例子,来避免死锁,这次咱们再看一个案例:(这个规则使用上下文管理器非常简单)

先看看源码,咱们怎么使用:

# 装饰器方法def contextmanager(func):    """    方法格式    @contextmanager    def some_generator(<arguments>):        <setup>        try:            yield <value>        finally:            <cleanup>    然后就可以直接使用with托管了    with some_generator(<arguments>) as <variable>:        <body>    """    @wraps(func)    def helper(*args, **kwds):        return _GeneratorContextManager(func, args, kwds)    return helper

翻译成代码就是这样了:(简化)

from contextlib import contextmanager  # 引入上下文管理器@contextmanagerdef lock_manager(*args):    # 先排个序(按照id排序)    args = sorted(args, key=lambda x: id(x))    try:        for lock in args:            lock.acquire()        yield    finally:        # 先释放最后加的锁(倒序释放)        for lock in reversed(args):            lock.release()

基础忘记了可以点我(lambda

以上面小明小张转账案例为例子:(不用再管锁顺序之类的了,直接全部丢进去: withlock_manager(...)

from contextlib import contextmanager  # 引入上下文管理器from multiprocessing.dummy import Pool as ThreadPool, Lock@contextmanagerdef lock_manager(*args):    # 先排个序(按照id排序)    args = sorted(args, key=lambda x: id(x))    try:        for lock in args:            lock.acquire()        yield    finally:        # 先释放最后加的锁(倒序释放)        for lock in reversed(args):            lock.release()xiaoming = 5000xiaozhang = 8000m_lock = Lock()  # 小明的锁z_lock = Lock()  # 小张的锁# 小明转账1000给小张def a_to_b():    global xiaoming    global xiaozhang    global m_lock    global z_lock    print(f"[转账前]小明{xiaoming},小张{xiaozhang}")    with lock_manager(m_lock, z_lock):        xiaoming -= 1000        xiaozhang += 1000    print(f"[转账后]小明{xiaoming},小张{xiaozhang}")# 小张转账1000给小明def b_to_a():    global xiaoming    global xiaozhang    global m_lock    global z_lock    print(f"[转账前]小明{xiaoming},小张{xiaozhang}")    with lock_manager(m_lock, z_lock):        xiaozhang -= 1000        xiaoming += 1000    print(f"[转账后]小明{xiaoming},小张{xiaozhang}")def main():    print(f"[互刷之前]小明{xiaoming},小张{xiaozhang}")    p = ThreadPool()    for _ in range(5):        p.apply_async(a_to_b)        p.apply_async(b_to_a)    p.close()    p.join()    print(f"[互刷之后]小明{xiaoming},小张{xiaozhang}")if __name__ == '__main__':    main()

输出:

[互刷之前]小明5000,小张8000[转账前]小明5000,小张8000[转账前]小明5000,小张8000[转账后]小明4000,小张9000[转账前]小明5000,小张8000[转账后]小明5000,小张8000[转账前]小明5000,小张8000[转账前]小明4000,小张9000[转账后]小明4000,小张9000[转账后]小明5000,小张8000[转账前]小明5000,小张8000[转账后]小明4000,小张9000[转账前]小明4000,小张9000[转账前]小明4000,小张9000[转账后]小明5000,小张8000[转账前]小明5000,小张8000[转账后]小明4000,小张9000[转账后]小明5000,小张8000[转账前]小明5000,小张8000[转账后]小明4000,小张9000[转账后]小明5000,小张8000[互刷之后]小明5000,小张8000

再来个验证,在他们互刷的过程中,小潘还了1000元给小明

from time import sleepfrom contextlib import contextmanager  # 引入上下文管理器from multiprocessing.dummy import Pool as ThreadPool, Lock@contextmanagerdef lock_manager(*args):    # 先排个序(按照id排序)    args = sorted(args, key=lambda x: id(x))    try:        for lock in args:            lock.acquire()        yield    finally:        # 先释放最后加的锁(倒序释放)        for lock in reversed(args):            lock.release()xiaopan = 9000xiaoming = 5000xiaozhang = 8000m_lock = Lock()  # 小明的锁z_lock = Lock()  # 小张的锁p_lock = Lock()  # 小潘的锁# 小明转账1000给小张def a_to_b():    global xiaoming    global xiaozhang    global m_lock    global z_lock    print(f"[转账前]小明{xiaoming},小张{xiaozhang}")    with lock_manager(m_lock, z_lock):        xiaoming -= 1000        xiaozhang += 1000    print(f"[转账后]小明{xiaoming},小张{xiaozhang}")# 小张转账1000给小明def b_to_a():    global xiaoming    global xiaozhang    global m_lock    global z_lock    print(f"[转账前]小明{xiaoming},小张{xiaozhang}")    with lock_manager(m_lock, z_lock):        xiaozhang -= 1000        xiaoming += 1000    print(f"[转账后]小明{xiaoming},小张{xiaozhang}")# 小潘还1000给小明def c_to_a():    global xiaoming    global xiaopan    global m_lock    global p_lock    print(f"[转账前]小明{xiaoming},小潘{xiaopan}")    with lock_manager(m_lock, p_lock):        xiaopan -= 1000        xiaoming += 1000    print(f"[转账后]小明{xiaoming},小潘{xiaopan}")def main():    print(f"[互刷之前]小明{xiaoming},小张{xiaozhang},小潘{xiaopan}")    p = ThreadPool()    for _ in range(5):        p.apply_async(a_to_b)        # 在他们互刷的过程中,小潘还了1000元给小明        if _ == 3:            p.apply_async(c_to_a)        p.apply_async(b_to_a)    p.close()    p.join()    print(f"[互刷之后]小明{xiaoming},小张{xiaozhang},小潘{xiaopan}")if __name__ == '__main__':    main()

输出:

[互刷之前]小明5000,小张8000,小潘9000[转账前]小明5000,小张8000[转账前]小明5000,小张8000[转账后]小明4000,小张9000[转账前]小明5000,小张8000[转账前]小明4000,小张9000[转账后]小明5000,小张8000[转账前]小明5000,小张8000[转账后]小明4000,小张9000[转账后]小明5000,小张8000[转账前]小明5000,小张8000[转账后]小明4000,小张9000[转账前]小明4000,小张9000[转账前]小明4000,小潘9000 # 注意下这个[转账后]小明5000,小张8000[转账前]小明5000,小张8000[转账后]小明4000,小张9000[转账后]小明5000,小潘8000 # 注意下这个[转账前]小明5000,小张9000[转账后]小明6000,小张8000[转账后]小明5000,小张9000[转账前]小明6000,小张8000[转账后]小明6000,小张8000[互刷之后]小明6000,小张8000,小潘8000

上下文管理器进一步完善

from contextlib import contextmanagerfrom multiprocessing.dummy import threading # or import threading# ThreadLocal 下节会说_local = threading.local()@contextmanagerdef acquire(*args):    # 以id将锁进行排序    args = sorted(args, key=lambda x: id(x))    # 确保不违反以前获取的锁顺序    acquired = getattr(_local, 'acquired', [])    if acquired and max(id(lock) for lock in acquired) >= id(args[0]):        raise RuntimeError('锁顺序有问题')    # 获取所有锁    acquired.extend(args)    _local.acquired = acquired  # ThreadLocal:每个线程独享acquired    # 固定格式    try:        for lock in args:            lock.acquire()        yield    finally:        # 逆向释放锁资源        for lock in reversed(args):            lock.release()        # 把释放掉的锁给删了        del acquired[-len(args):]

2.哲学家吃面

先看看场景:五个外国哲学家到中国来吃饭了,因为不了解行情,每个人只拿了一双筷子,然后点了一大份的面。碍于面子,他们不想再去拿筷子了,于是就想通过脑子来解决这个问题。

每个哲学家吃面都是需要两只筷子的,这样问题就来了:(只能拿自己两手边的筷子)

  1. 如果大家都是先拿自己筷子,再去抢别人的筷子,那么就都等着饿死了(死锁
  2. 如果有一个人打破这个常规,先拿别人的筷子再拿自己的,那么肯定有一个人可以吃到面了
  3. 5个筷子,意味着最好的情况 ==> 同一时刻有2人在吃(0人,1人,2人)

把现实问题转换成代码就是:

  1. 哲学家--线程
  2. 筷子--资源(几个资源对应几把锁)
  3. 吃完一口面就放下筷子--lock的释放

有了上面基础这个就简单了,使用死锁避免机制解决哲学家就餐问题的实现:(不用再操心锁顺序了)

from contextlib import contextmanager  # 引入上下文管理器from multiprocessing.dummy import Pool as ThreadPool, Lock, current_process as current_thread# 使用简化版,便于你们理解@contextmanagerdef lock_manager(*args):    # 先排个序(按照id排序)    args = sorted(args, key=lambda x: id(x))    try:        # 依次加锁        for lock in args:            lock.acquire()        yield    finally:        # 先释放最后加的锁(倒序释放)        for lock in reversed(args):            lock.release()#########################################def eat(l_lock, r_lock):    while True:        with lock_manager(l_lock, r_lock):            # 获取当前线程的名字            print(f"{current_thread().name},正在吃面")            sleep(0.5)def main():    resource = 5  # 5个筷子,5个哲学家    locks = [Lock() for i in range(resource)]  # 几个资源几个锁    p = ThreadPool(resource) # 让线程池里面有5个线程(默认是cup核数)    for i in range(resource):        # 抢左手筷子(locks[i])和右手的筷子(locks[(i + 1) % resource])        # 举个例子更清楚:i=0 ==> 0,1;i=4 ==> 4,0        p.apply_async(eat, args=(locks[i], locks[(i + 1) % resource]))    p.close()    p.join()if __name__ == '__main__':    main()

输出图示:

自行拓展

1.银行家算法

PS:这个一般都是操作系统的算法,了解下就可以了,上面哲学家吃面用的更多一点(欢迎投稿~)

我们可以把操作系统看作是银行家,操作系统管理的资源相当于银行家管理的资金,进程向操作系统请求分配资源相当于用户向银行家贷款。 为保证资金的安全,银行家规定:

  1. 当一个顾客对资金的最大需求量不超过银行家现有的资金时就可接纳该顾客;
  2. 顾客可以分期贷款,但贷款的总数不能超过最大需求量;
  3. 当银行家现有的资金不能满足顾客尚需的贷款数额时,对顾客的贷款可推迟支付,但总能使顾客在有限的时间里得到贷款;
  4. 当顾客得到所需的全部资金后,一定能在有限的时间里归还所有的资金.

操作系统按照银行家制定的规则为进程分配资源,当进程首次申请资源时,要测试该进程对资源的最大需求量,如果系统现存的资源可以满足它的最大需求量则按当前的申请量分配资源,否则就推迟分配。当进程在执行中继续申请资源时,先测试该进程本次申请的资源数是否超过了该资源所剩余的总量。若超过则拒绝分配资源,若能满足则按当前的申请量分配资源,否则也要推迟分配。


通俗讲就是:当一个进程申请使用资源的时候,银行家算法通过先试探分配给该进程资源,然后通过安全性算法判断分配后的系统是否处于安全状态,若不安全则试探分配作废,让该进程继续等待。

参考链接:

https://www.cnblogs.com/chuxiuhong/p/6103928.htmlhttps://www.cnblogs.com/Lynn-Zhang/p/5672080.htmlhttps://blog.csdn.net/qq_33414271/article/details/80245715https://blog.csdn.net/qq_37315403/article/details/82179707

2.读写锁

Python里面没找到读写锁,这个应用场景也是有的,先简单说说这个概念,你可以结合 RLock实现读写锁(了解下,用到再研究)

读写锁(一把锁):

  1. 读共享:A加读锁,B、C想要加读锁==>成功(并行操作)
  2. 写独占:A加写锁,B、C想要读(写)==>阻塞等
  3. 读写不能同时(写优先级高):A读,B要写,C要读,D要写==>A读了,B在写,C等B写完读,D等C读完写(读写不能同时进行)

扩展参考:https://blog.csdn.net/vcbin/article/details/51181121

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 【系统设置】CentOS 修改机器名

    ken.io
  • 《动物魔法学校》儿童学编程Scratch之“外观”部分

    导读:本文通过一个案例《动物魔法学校》来学习Scratch语言的“外观”部分。之后通过一系列其他功能的综合运用对作品功能进行了扩展。

    一石匠人
  • 一张图理清《梅花易数》梗概

    学《易经》的目的不一定是为了卜卦,但是了解卜卦绝对能够让你更好地了解易学。今天用一张思维导图对《梅花易数》的主要内容进行概括,希望能够给学友们提供帮助。

    一石匠人
  • SQL中GROUP BY用法示例

    GROUP BY我们可以先从字面上来理解,GROUP表示分组,BY后面写字段名,就表示根据哪个字段进行分组,如果有用Excel比较多的话,GROUP BY比较类...

    Awesome_Tang
  • 什么样的人生才是有意义的人生——没有标准的标准答案

    【导读】其实我们可以跳出这个小圈圈去更加科客观地看一下这个世界。在夜晚的时候我们仰望天空,浩瀚的宇宙中整个地球只是一粒浮尘,何况地球上一个小小的人类?在漫长的历...

    一石匠人
  • 声音功能让儿童编程更有创造性

    导读:Scratch中声音功能非常强大,除了常规的音效,你甚至可以模拟各种乐器的各个发音、设置节拍、休止……如果你愿意,甚至可以用它创作一个交响乐。我们可以引导...

    一石匠人
  • 儿童创造力教育与编程教育的碰撞——MIT雷斯尼克教授最新理论梗概

    儿童编程教育已经在我国各一线二线城市疯狂出现,颇有“烂大街”的趋势。我们不禁要问很多很多问题:

    一石匠人
  • 复杂业务下向Mysql导入30万条数据代码优化的踩坑记录

    从毕业到现在第一次接触到超过30万条数据导入MySQL的场景(有点low),就是在顺丰公司接入我司EMM产品时需要将AD中的员工数据导入MySQL中,因此楼主负...

    haifeiWu
  • 我不是算命先生,却对占卜有了疑惑——如何论证“占卜前提”的正确与否

    事出有因,我对《周易》感兴趣了很多年。只是觉得特别有趣,断断续续学习了一些皮毛。这几天又偶然接触到了《梅花易数》,觉得很是精彩,将五行八卦天干地支都串联了起来。...

    一石匠人
  • 天干地支五行八卦的对应关系

    一石匠人

扫码关注云+社区

领取腾讯云代金券