拯救多线程混乱的 pelagia

2020-07-13 21:28:40 +08:00
 gantleman

全文地址: gameinstitute.qq.com/community/detail/133448

导致多线程混乱的依赖和死锁问题。

在介绍各种多线程开发工具前我先要介绍下多线程下最大的二个问题。

第一个问题就是任务执行的依赖关系。在不同线程间执行的任务代码会有先后次序的需求。但因为多线程的特性会导致这些代码被同时执行。那么需要一种方法来控制多线程间代码的依赖关系。违反这种依赖关系可能会导致数据的丢失。执行过程的严重错误。甚至崩溃,死机。

第二个问题就是死锁。 在多线程执行过程中,多个线程会访问同一个数据。如果其中一个线程锁住了一个数据,然后请求另一个数据的锁。而其他线程刚好锁住了他请求中的数据,并也刚好请求他拥有的数据。就会导致两个线程互相等待系统卡死的问题。

为什么多线程下这两个问题是核心问题? 因为这两个问题的出现与多线程软件规模成正比,也就是说随着多线程软件规模的增加,这两个问题出现的概率也在增加。他们像达克摩斯之剑一样悬挂在任何多线程项目的上空。

pelagia

是开源项目其地址在 github.com/surparallel/pelagia

pelagia 是由 surparallel open source 推出的开源项目,是唯一同时解决依赖和死锁的多线程工具。 在前面我简单的介绍了多线程工具的现状。我们知道依赖和死锁是多线程开发中必须要解决的两个问题。并不是简单的因为依赖和死锁会有概率的出现在多线程项目中。而是因为在多线程项目中存在指数规模效应的问题。

所谓指数规模效应是指假设一个项目有两个线程,如果你新添加一个线程,那么两个线程间发生依赖和死锁,就有 3 的 2 次方即 9 种可能。如果有 99 个线程,你同样是新添加一个线程,那么发生依赖和死锁的可能就变成了 100 的 2 次方种可能。也就是说可能发生依赖和死锁的状况,随着开发任务的增加成指数级别的增加。

这样就导致每开发一个新的线程任务就要和其余的线程任务做仔细的检查。检查是否可能出现依赖和死锁。而这种检查的规模也是程指数增加的。这样就导致我们开发的多线程项目很快就会达到团队所能承受的极限。

因为指数规模效应的存在就必然会有祖传代码拍死老师傅。为了避免老师傅们被祖传代码拍死。pelagia 结合消息通信和数据分区两个技术,同时解决依赖和死锁问题,彻底拯救了老师傅。

pelagia 目前处于准完成阶段。大部分功能已经开发完毕。包括消息通信的统计系统,支持多线程的日志系统,多线程数据分区的存储系统。其硬盘储存系统性能略快于 redis 。

11897 次点击
所在节点    程序员
39 条回复
felixlong
2020-07-31 22:55:05 +08:00
@gantleman 不知所云。有这时间打两把王者荣耀不好吗?
mind3x
2020-08-01 03:29:00 +08:00
@gantleman C 程序员需要特别小心内存越界。你的 pEvent 只在栈上分配了 4 字节(32 位)或 8 字节(64 位)的空间,然而你的 memcpy 对传进来的 valueLen 不加任何检查,调用者轻易就可以用自己构造的数据覆盖栈上不属于 pEvent 的空间。这是个很基础的问题,随手搜一下有很多文章,例如 https://www.jianshu.com/p/47d484b9227e
gantleman
2020-08-01 04:35:07 +08:00
@mind3x 谢谢您的提醒。如你所说防御编程只针对调用者或第三方。这个示例中调用者和开发者都是用户本人。并且此处的输入的长度和输出的长度都被明显指出没有隐式调用。对内存越界的恐慌和焦虑常见于有一定经验的开发者。过度的防御编程并不能解决这个问题。代码的整洁和语法的准确,对调用链层级的有效控制会更有效的解决内存溢出问题。再次感谢您对 pelagia 的关注。
mind3x
2020-08-01 05:31:47 +08:00
@gantleman
我回复你上一条是以为你真的想了解自己的代码糟糕在哪里,结果看来并不是。

我翻译一下我上面的回答和你的答复:
我:这是个基本的常识。基,本,的,常,识。
你:我自己的代码,爱怎么写就怎么写。

那我也不用那么客气了。我 1995 年开始写 C,从底层一路写上来,你还没听说过线程的时候我就在写线程调度器了。没见过写成这样还这么理直气壮的,“过度的防御编程并不能解决这个问题”。早拉黑得了,浪费自己生命。
gantleman
2020-08-01 05:50:11 +08:00
@mind3x 过度防御编程会带来两个问题。第一,c 和 c++的操作分为内存和计算两种,所有内存操作极端情况下都需要防御。那么就意味着需要额外增加 1/3 的代码来做防御。第二,防御编程的结果是提醒开发者,你的编程出错了。无论在开发阶段还是用户运行阶段这都会导致调用栈信息的丢失。以调用栈信息的丢失换来没有意义的日志输出得不偿失。所以过度防御危害更大。不要那么大火气,有些事情说清楚其实很简单。
tairan2006
2020-08-02 19:09:14 +08:00
@gantleman 你说的这个问题是可以解决的,合约化编程加注释,或者 debug 模式下运行,release 的时候不跑都行。但是口头约定是不行的,除非这代码你就没打算让别人提交 patch…
gantleman
2020-08-02 21:41:32 +08:00
@tairan2006 我想合约化编程或着人与人的约定是不能解决这个问题的。保证代码的强壮的方法是要深思熟虑,内部讨论,反复 review,交叉 review,白盒测试,黑盒测试,压力测试。没有任何开源软件会仅凭人与人的约定(口头,注释或其他)就允许提交 path 。既然选择了开源的方式,我欢迎有能力保障开源社区健康发展的人,在深入了解 pelagia 项目之后加入进来。
随着项目的发展,我对提交代码越来越谨慎。前天我在考虑使用 pelagia 处理广告集合竟价中存在的功能缺陷。通常这种情况下我会先列个功能提纲,尽可能把想到的功能都加上去。而通常两三天后我就会发现有些功能是多余的。反复思考后发现只有补充一到两个接口就可以了。有些极端情况,例如处理“相位技术”就需要创建大量的缓存镜像。可能会导致内存急速膨胀。我犹豫了半年多要不要加入进来。因为在单片机或工业机上对内存要求会比较苛刻。通常情况下不会需要大量存储换效率的情况。
no1xsyzy
2020-08-05 14:30:43 +08:00
@gantleman #23 俺还在想你这 valueLen 不管是多少都会导致破坏调用栈,结果传的是编译期常量 sizeof(void*)
这还要传个参,这叫脱裤子放屁
这还不是搜索 TaskRouting 调用方查出来,搜索到 functionPoint 就找不下去了,除了 pjob.c 之外没有任何地方包含了 functionPoint,而该文件内也只定义了它。
不是很懂怎么办到的,用数字偏移量?用数字偏移量不写注释写半天没什么意义的注释干什么?
看了下,RoutingFun 必须传一个 char* 一个 short,看来对于 C 传任意数量参数调用的技巧不是很熟。

项目如其名,佩拉吉奥斯‧塞普汀三世 —— 疯狂且自大
no1xsyzy
2020-08-05 14:31:23 +08:00
@gantleman #27 @gantleman #23 自相矛盾。
gantleman
2020-08-05 17:19:22 +08:00
@no1xsyzy 推荐你看下另一帖子 www.v2ex.com/t/695286#reply20 。我这人确实有些狂妄,因为我一直在并行技术的最前沿疯狂尝试。解决一些国内几年都解决不了的技术问题并分享给大家。开发过程中每个人使用语言的习惯都不相同。可以贴出来不好懂的代码我来慢慢讲。
no1xsyzy
2020-08-05 19:11:28 +08:00
@gantleman #30 我的调查路线是这样的,#12 说了 psimple.c@TaskRouting
就 Search in repository 搜索 TaskRouting,唯一一处使用是 psimple.c#L67,传递给 plg_JobCreateFunPtr
继续搜索,其实现在 pjob.c#L205,而传递来的函数指针被赋给 pEventPorcess->functionPoint
然后搜索 functionPoint 只找到 struct 定义里找到。也就是说这东西就这么被吃了,再也没有被用上……
xsen
2020-08-07 11:10:56 +08:00
#1/#2 两个问题,都是架构设计的问题,还有就是管理的问题
gantleman
2020-08-07 12:02:55 +08:00
@xsen 是的,软件工程的特点就在不断进化。对于纸带机所有开发和编译都是设计管理问题。我们知道后来这些都被编译器和操作系统取代了。软件工程的自动化,平台化,排斥人工的特点是诞生之日起就深入骨髓的。
gantleman
2020-08-08 23:01:56 +08:00
@no1xsyzy 很抱歉最近很忙,在准备用 pelagia 冲刺 3D 同屏 1 万人的目标。functionPoint 会在 3 个地方被使用。分别是 job 的 885 行,919 行,954 行,和用户相关的是 919 行这段。

这段表示表示如果是 C 函数指针类型直接执行调用执行,如果失败将回滚数据库事物。
if (pEventPorcess->scriptType == ST_PTR) {

if (0 == pEventPorcess->functionPoint(pOrderPacket->value, plg_sdsLen(pOrderPacket->value))) {
job_Rollback(pJobHandle);
}
}
no1xsyzy
2020-08-09 00:16:19 +08:00
@gantleman #34 哦是我没仔细看 Github Repo 内搜索的前提,同一文件只会显示前两处匹配……
不过在尝试写一个双链表 insert 写得一塌糊涂后明白自己写 C 确实太少,尤其指针的表记都得想半天。还是算了。
在我经历过的学习路线图上 C 只是 ASM 的伪代码(
swulling
2020-08-10 09:04:52 +08:00
@gantleman
> 我想合约化编程或着人与人的约定是不能解决这个问题的。保证代码的强壮的方法是要深思熟虑,内部讨论,反复 review,交叉 review,白盒测试,黑盒测试,压力测试。没有任何开源软件会仅凭人与人的约定(口头,注释或其他)就允许提交 path 。

良好且严格的代码规范,合理的注释和文档。
这两者的重要性在某些人眼里比 Review 、Test 要低,其实并不然。

此外,从你的代码风格和上面的某些回复合理猜测主要在做个人编程或者小团队开发,否则不会养成这些习惯。
gantleman
2020-08-10 10:41:08 +08:00
@swulling 在讨论任何事情的时候都要设置一个合理的前提,pelagia 就是一个小团队项目。为了最大可能兼容单片机和任何其他种类的客户端所以不会引入网络功能。预计最终代码不会超过 5 万行。随着项目的稳定也必然会添加防御相关的代码。工程开发有其过程,核心功能都不完备的情况就添加各种防御,哪么核心功能发生变化后防御代码怎么办?这些防御的代码文或档在大团队下对团队协作是必须。我带团队也天天强调文档和防御的重要性。强调文档的重要并不意味真的就比核心功能还重要。核心功能都没有的话,这个帖子就不存在了。以上内容都是我猜测的,因为你没有说出为什么文档比核心功能更重要的原因。希望能坦诚告诉我。至于开发习惯,是不是因为我一直写个人项目或则小团队项目,没有大公司成熟代码的习惯。还是因为 pelagia 本身是开源项目而没有把大公司病带入这个项目。或则大公司的代码是不是因为文档就更加强壮。这个命题真的很大,涉及到多个软件管理的领域。当然我也有兴趣和你聊聊开发管理的问题。小团队,大团队,公司角度的管理都不同。侧重点也各有不同。但无论什么时候软件按时,按质,按要求做出来。对公司,对团队,对团队领导才是最重要。无论小项目,大项目,团队项目。都要认真对待做出成绩。我能力也有不足的地方,对于细枝末节把空的并不好。也欢迎有兴趣,志同道合的开发者加入进来。
danc
2020-08-10 13:58:17 +08:00
rust 了解一下?
gantleman
2020-08-10 14:37:51 +08:00
@danc 你提出了一个有趣的问题,我没有理解错的话是想问 rust 和 go 在并行上和 pelagia 有什么区别。简单说 go 和 rust 会死锁,pelagia 不会。深入的说 go 和 rust 通过放弃对线程间共享数据的管理。来减少工程的复杂程度。也许开发者仅仅不想处理这么复杂的问题,或则没想好怎么处理这么复杂的问题。去掉共享数据的管理在技术水平上就相当于自降一级。假设有一天软件工程师在做开发的时候,不需要考虑语言错误问题,存储错误问题,网络错误问题。那不表示这些问题天生不存在。哪仅仅是因为所使用系统语言或工具将这些问题处理掉了。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/689717

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX