前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Python3 与 C# 并发编程之~ 线程篇3

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

原创
作者头像
逸鹏
发布于 2018-09-07 00:39:22
发布于 2018-09-07 00:39:22
48400
代码可运行
举报
文章被收录于专栏:逸鹏说道逸鹏说道
运行总次数:0
代码可运行

锁专题扩展

1.加锁机制

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

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# 装饰器方法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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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(...)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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()

输出:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[互刷之前]小明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元给小明

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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()

输出:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[互刷之前]小明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
上下文管理器进一步完善
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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的释放

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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. 当顾客得到所需的全部资金后,一定能在有限的时间里归还所有的资金.

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


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

参考链接:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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

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

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

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

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

评论
登录后参与评论
暂无评论
推荐阅读
Circos图神器--circlize包
circlize包是由德国癌症中心的华人博士Zuguang Gu开发,这个R包包含两个文件,一个是介绍绘制简单圈图的方法,另一个专门介绍基因组数据绘制圈图。
作图丫
2022/03/29
5.4K0
Circos图神器--circlize包
肿瘤免疫细胞浸润与临床相关性分析
Profiles of immune infiltration in colorectal cancer and theirclinical significant: A gene expression- based study
DoubleHelix
2020/04/07
6.9K0
R代码-九象限图绘制
在多组学联合分析中,需要用得到九象限图来对两个组学获得得基因结果进行可视化,例如下面这样得,因此这两天主要是对这个内容进行整理。
小胡子刺猬的生信学习123
2022/09/08
2.2K1
R代码-九象限图绘制
使用corrplot包绘制相关性图及美化!
R语言的corrplot包提供了一种可视化相关矩阵的探索性工具,支持自动变量重排序以帮助发现变量间的隐藏模式。
生信医道
2025/04/08
2800
使用corrplot包绘制相关性图及美化!
如果你觉得相关性热图不好看,或者太简陋
就有粉丝提问,把单细胞亚群使用 AverageExpression 函数做成为了亚群矩阵,是不是忽略了单细胞亚群的异质性呢?毕竟每个单细胞亚群背后都是成百上千个具体的细胞啊。代码如下所示:
生信菜鸟团
2021/07/29
4730
如果你觉得相关性热图不好看,或者太简陋
R- 组合图(折线+条形图)绘制
就是下面这张图,在途中用条形图展示了不同季节样本浮游动物的组成情况,同时使用带误差棒的折线图来表示浮游动物生物量的变化,相当于在一幅图中同时展示了群落的相对丰度和绝对丰度。
DataCharm
2021/02/22
3.5K0
R- 组合图(折线+条形图)绘制
【画图】如何批量展现基因表达相关性?
现在已经有明确的实验证明,跟SARS病毒一样,新冠状病毒2019-nCoV与宿主细胞的ACE2受体结合[1]。上次教程已经给大家演示了,GTEx数据库有人各组织中基因表达谱数据,下载整理这个数据可以绘制出ACE2受体在人体组织中的表达量情况以及可能的功能有哪些。
Chris生命科学小站
2023/02/28
4890
【画图】如何批量展现基因表达相关性?
R语言绘制圈图、环形热图可视化基因组实战:展示基因数据比较
heatmap()的输入应该是一个矩阵(或者一个将被转换为单列矩阵的向量)。如果矩阵被分割成组,必须用split参数指定一个分类变量。注意spilt的值应该是一个字符向量或一个因子。如果它是一个数字向量,它将被转换为字符。
拓端
2021/09/29
5.4K0
【工具】NPM用最接近配对校正组学数据的潜在批效应
批效应(BEs)是组学数据中的主要噪声源,经常掩盖真实的生物信号。BEs在现有数据集中仍然很常见。目前的BE校正方法大多依赖于特定的假设或复杂的模型,可能无法充分检测和调整BE,从而影响下游分析和发现能力。为了解决这些挑战,我们开发了NPM,这是一种基于最近邻匹配的方法,可以调整BEs,并且在广泛的数据集中可能优于其他方法。
生信学习者
2025/04/02
900
基于直方图和散点图延伸出来的其他绘图细节
图形是一个有效传递分析结果的呈现方式。R是一个非常优秀的图形构建平台,它可以在生成基本图形后,调整包括标题、坐标轴、标签、颜色、线条、符号和文本标注等在内的所有图形特征。本章将带大家领略一下R在图形构建中的强大之处,也为后续更为高阶图形构建铺垫基础。
1480
2019/07/01
6570
基于直方图和散点图延伸出来的其他绘图细节
R语言画图
R自带的画图工具,R绘图基础图形系统的核心,plot()函数是一个泛型函数,使用plot时真正被调用的时函数依赖于对象所属的类。
靓且有猫
2024/07/21
1822
R语言入门之折线图
在R语言中可以使用基本绘图函数lines(x, y, type=)来绘制线条,这里参数x和y分别是数值型向量,代表着横坐标和纵坐标的数据,参数type=主要是用来控制线条的类型。
生信与临床
2020/08/06
2.2K0
R语言入门之折线图
R in action读书笔记(15)第十一章 中级绘图 之二 折线图 相关图 马赛克图
> plot(t1$age,t1$circumference,xlab="Age(days)",ylab="circumference (mm)",main="orange tree 1growth")
Ai学习的老章
2019/04/10
6990
R in action读书笔记(15)第十一章 中级绘图 之二 折线图 相关图 马赛克图
Shapes and line types for R
Solution Note that with bitmap output, the filled symbols 15-18 may render without proper anti-alia
学到老
2018/03/16
1.5K0
Shapes and line types for R
「R」自己动手进行R基础绘图
基本绘图和R本身一样古老,但对大多数用户来说,它仍然是神秘的。他们可能使用plot(),甚至知道其参数的完整列表,但大多数人从未完全理解它。本文试图通过为外行提供友好的介绍来揭开基础图形的神秘面纱。
王诗翔呀
2022/12/30
1K0
「R」自己动手进行R基础绘图
R-三维散点图绘制绘制
上期我们说了气泡图。如果我们将气泡图的三维数据绘制到三维坐标系[1]中,通常称其为三维散点图,即用在三维X-Y-Z图上针对一个或多个数据序列绘出三个度量的一种图表。
DataCharm
2021/02/22
2.4K0
R-三维散点图绘制绘制
R语言画图par() 函数参数详解
R有着非常强大的绘图功能,我们可以利用简单的几行代码绘制出各种图形来,但是有时候默认的图形设置没法满足我们的需要,甚至会碰到各种各样的小问题:如坐标轴或者标题出界了,或者图例说明的大小或者位置遮挡住了图形,甚至有时候默认的颜色也不能满足我们的需求。如何进行调整呢?这就用到了“强大”的函数par()。我们可以通过设定函数par()的各个参数来调整我们的图形,这篇博文就是对函数par()的各个参数进行一下总结。
孙小北
2024/01/30
4090
生存资料校准曲线的绘制
前面我们已经讲过logistic模型的校准曲线的画法,这次我们学习生存资料的校准曲线画法。
医学和生信笔记
2022/11/15
8860
生存资料校准曲线的绘制
R语言︱画图
point加点;axis右边坐标轴,mtext右边坐标轴的名称,text给出本文。
悟乙己
2019/05/26
1.3K0
R语言入门系列之二
在进行正式的数据分析之前,通常要对数据进行处理。而读取数据仅仅是最简单的,之后还要进行数据的筛选、排序、转换等。数据框是最方便的数据存储、管理对象。R有很多内置的示例数据集包括向量、矩阵数据框等,可以使用data()进行查看,接下来我们以R内置数据mtcars(32辆汽车在11个指标上的数据)为例进行分析,如下所示:
SYSU星空
2022/05/05
4.2K0
R语言入门系列之二
推荐阅读
相关推荐
Circos图神器--circlize包
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档