日期: 2021 年 9 月 2 日

task 切分和汇总 在 cloud 环境中如何设计?

有一个需求,一个 task 会生成多个子 task,这些 task 可以并行的跑,*后需要汇总。

业务是跑在 K8S 上的,也就是说是不同 pod 会去跑子 task,*后等所有子 task 都结束之后,汇总*终的数据。

这样的需求,不知道大家有什么想法可以建议一下呢?

目前想到的是 dispatch task 用 message queue,利用 redis 来做类似引用计数的效果,生成子 task 的时候计数加 1,完成一个子 task,计数减 1,计数为 0 的时候,进行数据汇总。

task 计数 汇总 生成4 条回复 • 2021-09-02 16:07:22 +08:00
2le 1
2le 3 小时 18 分钟前 via Android ❤️ 1
在我自己负责的项目中是把分布式任务的触发、执行、进度、结果统计等由任务调度微服务完成,目前实现了两种执行器 MQ 和 HTTP 。拿 MQ 来讲,发布任务后,子任务端定期发送 MQ 消息汇报任务状态就行,任务调度服务会消费这些消息,进行任务的统一管理。
v2byy 2
v2byy 3 小时 3 分钟前
@2le 那你的任务调度服务是单点的么?如果不是,那如何知道所有子 task 已经完成?
Itoktsnhc 3
Itoktsnhc 1 小时 25 分钟前
之前做过一个多层任务树状态跟踪系统,主要用在跟踪数据采集任务的状态。
初步需求我觉得类引用计数的方式是没问题的,*简单每个父级任务作为一个 redis 内的 hashset,然后字典的 key 为生成子任务 id 什么的,如果任务状态只有两种,可以使用存在 /不存在来标记,对应的 hashset 内没有任何元素,代表外层任务结束了;如果还有一些元素 那么这些 item 就是没完成的。
Itoktsnhc 4
Itoktsnhc 1 小时 22 分钟前 ❤️ 1
@Itoktsnhc 当然*终随着需求的增加 比如每个任务有多个状态,任务树存在多级等.经历了 redis->sql server->内存数据库 *终使用类 actor 的 Orleans 框架实现

c++在公司里一般是做什么的啊

不得不说c++应用其实还是很广泛的。。。但是也不得不承认想要找到一个c++工作不容易。。。。

不像其他语言如果写业务的话几天就能上手。。。

一般 公司 啊 里97 条回复 • 2021-09-02 16:08:37 +08:00
wangxn 1
wangxn 2 天前
看不同类型的公司吧。
后台服务:就是提供普通的 HTTP 服务(逻辑用 C++ 写),RPC
基础架构:存储,数据库,区块链
各种机器学习算法的工程化:TensorFlow
游戏:用虚幻引擎制作的游戏,或者各种自研引擎。可能会有各种脚本绑定
111qqz 2
111qqz 2 天前
做基础架构,做深度学习的训练推理框架
wangxn 3
wangxn 2 天前
不过 Go 和 Rust 这种原生编译型语言一直在侵蚀 C++ 的应用领域,当然现在看来 C++ 的地位还算稳固,后面如何发展没人知道。
wobuhuicode 4
wobuhuicode 2 天前
魔改一下 ffmpeg
zxCoder 5
zxCoder 2 天前
@wangxn
@111qqz

这样看起来 c++的岗位似乎大多都需要一定的特定领域的经验啊
nieyujiang 6
nieyujiang 2 天前 via iPhone
图形渲染引擎
wyx119911 7
wyx119911 2 天前
cgi
inhzus 8
inhzus 2 天前 via iPhone
补充:高性能网关,类似于 nginx ;客户端跨平台
opengps 9
opengps 2 天前
嵌入式硬件
ipwx 10
ipwx 2 天前
@zxCoder 这些经验算不得经验吧,学起来不就四五天
agagega 11
agagega 2 天前 via iPhone
C++有些地方挺强大的,好像很少有语言支持泛型参数不是类型的,C++的模版可以把矩阵的行列数,还有物理上的量纲都做成强类型。

Cpp20 有了 Concept 、模块和协程以后又舒服多了。就是可惜在标准库演化太慢,又没有一个比较好的一键式包管理机制。
ipwx 12
ipwx 2 天前
@agagega 第三方其实还行,linux 上 conan 我用的很舒服。
lcdtyph 13
lcdtyph 2 天前
做显卡驱动的,用户态驱动是用 C++写的
echo1937 14
echo1937 2 天前
@zxCoder #5 其实不能说“c++的岗位似乎大多都需要一定的特定领域的经验”,
而是 C/C++贴近硬件,需要很强的计算机体系结构的相关知识,
毕竟你搞个 web 前后端开发也需要 HTTP 的基础知识。
wengjin456123 15
wengjin456123 2 天前 via Android
通信相关 c++非常多
ttgo 16
ttgo 2 天前 via iPhone
机器人。
justou 17
justou 2 天前
在公司编程不都是为了解决特定领域的特定问题么?
codyfeng 18
codyfeng 2 天前 via Android
做低延迟交易系统
ospider 19
ospider 2 天前 ❤️ 4
一般是写 C++ 的
YongYuan 20
YongYuan 2 天前
搜索系统、推荐系统
secondwtq 21
secondwtq 2 天前
卖萌的呗
dangyuluo 22
dangyuluo 2 天前
@ospider 似乎很有道理
liuidetmks 23
liuidetmks 2 天前 via iPhone
我在怀疑 现在还有这么苛刻的高性能需求吗?
一般是历史原因选 cpp 吧 rust go 不行不行吗
cassyfar 24
cassyfar 2 天前
云计算很多服务都是用 cpp 。
adeweb 25
adeweb 2 天前
我在工作环境用过的场景:
1. WebAssembly
2. Node 原生模块
3. Qt 桌面应用
daysv 26
daysv 2 天前
基本上低延迟的交易系统都要是 C++
Rico 27
Rico 2 天前
ue 开发
Aresrun 28
Aresrun 2 天前
@ospider 听君一席话,如听一席话
cheng6563 29
cheng6563 2 天前
cpp 和 python 很像,只会语言本身的话基本找不到工作
p2pCoder 30
p2pCoder 2 天前
推广搜,机器学习框架,这块大厂的需求还是相当大的,不过也仅集中于大厂,而且也是卷的离谱
hu8245 31
hu8245 2 天前
所有实时性要求高,资源要求苛刻的地方,都有 C++的身影
echo1937 32
echo1937 2 天前
@liuidetmks #23 有,很多,只是国内相对没有那么多的岗位。比如 编译器、虚拟机、数据库、脚本 /图形引擎、浏览器、大型工业软件、大型数学工具箱、操作系统、机器学习框架、图形 /图像 /音频处理。

Go 的高性能侧重于“高并发”,C/C++的高性能侧重于“0 开销”,Rust 在 2010 年发布的时候,C++11 都要有了,Rust 前途无量但是生态需要时间。
zuosiruan 33
zuosiruan 2 天前
游戏公司 后端用 c++的比较多。
Eagleyes 34
Eagleyes 2 天前
火星车,
月球车
shijingshijing 35
shijingshijing 2 天前 ❤️ 1
@Eagleyes 那个是用的 C,火星车上用 C++做的图像识别相关的,限制非常严格,几乎等同于带类的 C,不能用多态,不能用递归,不能用动态内存分配。
GalaDOS 36
GalaDOS 2 天前
魔改 WebRTC,优化编码器,做视频通信或者直播软件,以及配套的媒体服务器
yuhaijiang2019 37
yuhaijiang2019 2 天前
*近正在自学 C++,和虚幻引擎
3dwelcome 38
3dwelcome 2 天前
我用 C++写 wasm 网页,我自己都觉得很奇葩。
chengxy 39
chengxy 2 天前
@ospider #19 听君一席话胜听一席话
JustinMsc 40
JustinMsc 2 天前
@wobuhuicode 兄弟,了解 dash 吗?
luny 41
luny 2 天前
一般公司的基础件或者核心框架都会基于 C++来写,特点就是代码量较大,参与人数比较多。
chenpingan 42
chenpingan 2 天前
@ospider 你搁这搁这呢
ws52001 43
ws52001 2 天前
底层架构,或者需要处理效率的接口项目。
hazardous 44
hazardous 2 天前
维护老的 MFC 程序
sryanyuan 45
sryanyuan 2 天前
四七层负载 中间件
Unclev21x 46
Unclev21x 2 天前
@Aresrun 如何看待*近比较流行的废话梗?反映了怎样的社会心理??
ipwx 47
ipwx 2 天前
@shijingshijing 可是 C++ 的 template 才是 zero-abstraction 的精华啊
SIGEV13 48
SIGEV13 2 天前
transaction framework, 搜索引擎等
zxCoder 49
zxCoder 2 天前
@ipwx 。。。。哈哈 大佬的话不可信
darknoll 50
darknoll 1 天前
没卵用了现在
Cloutain 51
Cloutain 1 天前
搬砖 还能干嘛
ufan0 52
ufan0 1 天前
怎么没有人提到做游戏呢
QBugHunter 53
QBugHunter 1 天前
@liuidetmks
有的。。。我现在正在做的项目,医用便携式 B 超。里面有个控制器,内存 32KB 。。。。
shijingshijing 54
shijingshijing 1 天前
@ipwx 别说 template,连 malloc 和 new 都不能用的,所有可能潜在有 undeterministic 特性的功能都不会用的,程序运行所需的 CPU cycle,memory,communication 等资源占用都要事先明确计算好,不得超过 50%。基本上就是用做硬件的思维来写软件。
ipwx 55
ipwx 1 天前
@shijingshijing 神 tm template undeterministic 。
shijingshijing 56
shijingshijing 1 天前
@ipwx [潜在]

只能说隔行如隔山,我要是告诉你,循环嵌套还约定不超过三层嵌套,你会不会更惊奇?

这些行业的规定和经验,都是经过一条条人命累计出来的。
tianming1992 57
tianming1992 1 天前
@ipwx 航天要求时序确定。在地面上的能够完全确定机器的状态,所以内存是定死的。用动态内存会带来不确定时序。
name1991 58
name1991 1 天前
@shijingshijing 火星车用的就是 C,而且全部是静态类型的的声明,
tianming1992 59
tianming1992 1 天前
我现在做自动驾驶决策规划算法,也是 c++,实时要求很强,而且车企巴不得用更搓的处理器来省钱。
ipwx 60
ipwx 1 天前
@tianming1992 可是,template 和动态内存毫无关系啊。。。倒不如说用了 template 让 C++ 省了很多需要动态内存申请的情况。不然怎么叫 zero-cost abstraction ?

另外 template poly 可以在不加虚函数表的情况下实现一定的继承多态之类的特性。

所以你到底会不会用 C++
ipwx 61
ipwx 1 天前
@shijingshijing (正常写函数都不敢循环嵌套三层, 不然根本没法维护)
bruce0 62
bruce0 1 天前
@ipwx 我理解的 template 会有 undeterministic 原因. 是因为使用了 template 后,代码是编译器生成的,(不同的编译器还有不同的实现方式)不是程序员手写的,可以了认为是潜在不可控的。

当然,C++ 构造函数,还有深拷贝,浅拷贝等等容易出错的坑。

所有 Linus 经常喷 C++是一门辣鸡语言
ipwx 63
ipwx 1 天前
@bruce0 可是,C++ 的 zero-abstraction 是有规范的啊。

在 template 方面根本就没有 UB 。如果你认为不是手写的就是 undeterministic,我觉得你应该用机器码才行啊。
ipwx 64
ipwx 1 天前
@bruce0 构造函数、深拷贝、浅拷贝,进化到 C++17 就是 move 语法等等。这些都是有规范的,一个能通过各种测试的编译器,用这些高级特性你都知道会发生什么。包括 -O3 。写出没有 Undefined Behaviour (遵守规范所以编译器也能给你完全能控制的代码)是 C++ 程序员重要的基本功啊。
ipwx 65
ipwx 1 天前
@bruce0 哦对用机器码也不保险。

如果你用的是通用芯片,现代通用芯片在机器码的下面都有很多你“控制不了”的行为。比如英特尔的芯片微指令,一个机器指令其实是拆成若干指令,然后在芯片上有指令流水线(同一瞬间其实有多个指令的不同步骤正在执行)。这些指令“并行”会导致一些 data harzard 。当然,芯片帮我们处理好了。

但是因为是并行流水线所以会带来一些其他问题。比如遇到条件判断,现代芯片都会猜测这条语句的结果,然后提前进入某个分支执行。如果猜错了就“回滚”。对,分值预测就会导致你“执行时间不可控”。

还有比如 cacheline 、L1~L3 cache,atomic 指令,store buffer queue (在一个核上非 atomic 指令更新数据,另一个核不一定马上能看到)。这些都是你“不可控”的抽象。

—-

现代计算机就是在一层一层你不可控的抽象上建立起来的。C++ 的各种语法和芯片上的这些复杂机制并没有本质差别。要理解、掌握这些抽象后面的原理,利用这些抽象才对嘛。
ipwx 66
ipwx 1 天前
@bruce0 说起指令流水线和 data hazard 其实还可能会产生指令的乱序执行。比如老生常谈的

if (condition) {
with (lock) {
if (condition) { …

这种 double-check-lock 在乱序执行面前会被虐成渣渣。解决方案是加入 memory fence 。

—-

总之为啥 C++ 难,是因为太靠近硬件,需要掌握这些底层抽象才能用好。
wutiantong 67
wutiantong 1 天前
@ipwx 我觉得无知者的道听途说根本配不上这么认真的回复啊
OneMan 68
OneMan 1 天前
嵌入式,音视频,性能强相关的,android 和 iOS 的底层一些开发,也都是 c++开发好提供接口给上层用。
bruce0 69
bruce0 1 天前 ❤️ 1
@ipwx 我工作不是写 C++的,只是偶尔用一下 C++,你说的很多深入的东西我也不太懂。我说不让用 template 的原因是我的理解,不一定正确(没有任何杠的意思,只是说一下我的看法)。C++相当于 C,确实会有一些容易出错的地方(特性太多了),不是这个语言不可靠,是写 C++的人,因为对 C++理解的不够深入导致的。有几个人敢说自己精通 C++,深入理解了各种实现细节。相对来说 C 就没有这么多容易出错的地方(因为特性少)

以前,看过一篇文章,说是 在汽车上写 C 程序,指针都不让用,不知道真假
spadger 70
spadger 1 天前
https://www.amobbs.com/thread-5754354-1-1.html
关于下单助手 3.4.0 以后版本崩溃及内存异常等问题的情况说明

1 、原来为了集成各种查看实物图、SMT 贴片效果图,我们在这个小助手上集成了我们自己的一些 C++程序。
而这种 C/S 架构的方式,有 bug 的情况下,更新迭代也非常缓慢。
现在我们已经接入 EDA 团队开发的基于 WebGL 的在线图形引擎,用以替换掉原来开发的这部分 C++程序。
这样子,才能做到快速在线迭代更新。

shijingshijing 71
shijingshijing 1 天前
@ipwx
@bruce0
@tianming1992

这里的 undeterministic 不仅仅是指技术上的,需要用 template 实现的函数大部分都是基础库之类的,实际作用是为了一次书写,适用多种类型,这种情况理论上是没问题的,可是在实际使用过程中,可能会碰到各种意想不到的问题,比如,嵌入式里面有很多 8 位,16 位,32 位不同类型的处理器,这时候假如函数里面使用了 Magic Number 作为参数,比如上限值,很有可能当参数为 32bit INT 类型没问题,但 8 位,16 位就会有问题。(你可以 Argue 说这是程序员素质的问题,但写函数的和用函数的人保不齐有一个没注意的,这样就会带来问题)

template 可以看作是一种语法糖,爽了开发,但是让后面的测试,追踪,certification 乃至出了问题做 Failiure Analysis 不确定性、复杂度和人力成本剧增,可能导致整个项目成本剧增,因此即使有这种场景,一般也是用宏定义来实现的,高可靠性的项目就是从技术上,管理上等各个方面来进行限制,尽*大能力保证不出问题,*好是从根本上消除产生问题的可能性。

这样来看,不能使用指针,不能动态分配内存,循环嵌套不能超过三层,不能使用递归等等神奇的限制也就合乎情理了。
shijingshijing 72
shijingshijing 21 小时 45 分钟前
@name1991 图像处理和路径规划用的“严格裁剪版 C++”,内存管理这一块 NASA 重新造了一套轮子,上两张图吧

b00tyhunt3r 73
b00tyhunt3r 19 小时 27 分钟前 ❤️ 1
@ipwx
他上 nasa 图了 确实写着不用 template * 1 分
该你反击了

(别问我,我就一不嫌事大吃瓜的?
ipwx 74
ipwx 17 小时 10 分钟前
@shijingshijing 那行吧,只能说这是用 human labor 堆出来的重量级工程。

但凡是地球上跑的软件项目就不走这条路了,因为这要花太多人力资源。
agagega 75
agagega 16 小时 51 分钟前 via iPhone
@shijingshijing
我大概没见过哪个语言标准库 IO 部分比 C++的 iostream 更不讨人喜欢的了
boyhailong 76
boyhailong 10 小时 30 分钟前
游戏开发
shijingshijing 77
shijingshijing 7 小时 55 分钟前
@ipwx 很多代码都是自动生成的,还有很多是复用以前经过 certification 的老项目代码,测试的时候需要的人多。要想系统可靠,就必须尽量使用简单的基础模块,然后尽可能复用,宁可简单的东西复用几万遍,也不会为了省事儿弄个新轮子,NASA 自己造内存管理的轮子是迫不得已,不然也不会用的。

这些项目里面,R&D 的 effort 只占 30%,剩下的几乎全部是 Documentation,Test,Certifiction 。
leafre 78
leafre 7 小时 50 分钟前
做各种轮子
youlemei 79
youlemei 7 小时 45 分钟前
做低延迟系统
plasmetoz 80
plasmetoz 7 小时 34 分钟前
给使用的数据库加自定义函数
name1991 81
name1991 7 小时 29 分钟前
@shijingshijing intresting
koala9527 82
koala9527 7 小时 24 分钟前
在嵌入式中还是有一席之地的,例如汽车故障诊断仪中解析 CAN 、LIN 通信协议消息
ipwx 83
ipwx 7 小时 10 分钟前
@agagega 这倒是,我也不爱用 iostream 。特别傻逼。
ipwx 84
ipwx 7 小时 9 分钟前
@shijingshijing 那毕竟是 NASA,业务其实很集中。

我是觉得人类如果要征服星辰大海,这种龟速推进宇航器肯定是不行的。SpaceX 我估计不是这种写代码的风格,那种才可能快速市场迭代。。。所以你说 NASA 这种项目管理好不好?好也不好。。。
ipwx 85
ipwx 7 小时 7 分钟前
@shijingshijing 就这么说吧。按照我的理解。为什么写航天器的代码这么局限?大概是因为上天的芯片太弱,给犯错边界太窄了(包括发动机也是),所以才容易出事。

为什么上天的芯片太弱?整体科技水平还不够强的芯片不出错。
—-

这是这个时代的局限性,我觉得也挺对。但是我也盼望将来上个天和出门打个出租车一样简单,那个时候的写航天器代码一定不会是现在这样了。
wangxn 86
wangxn 6 小时 34 分钟前 via Android
@ipwx 航天试错的成本太高,虽然火星车可以升级软件,但一开始就写好写正确更合适
ipwx 87
ipwx 6 小时 32 分钟前
@wangxn 嘛,所以才需要这么搞。

如果真有一天没有多大试错成本,或者这种慢吞吞的推进速度实在太慢了,那肯定就不会这么写了。
shijingshijing 88
shijingshijing 6 小时 10 分钟前
@ipwx

SpaceX 毕竟刚刚出大气层,还没遭受火星上各种宇宙射线,各种高能粒子轰炸,Elon Musk 本身把安全性看的也不高,成本和快速迭代占了较高的优先级,所以 Falcon 初期会各种炸,直到迭代出一个稳定的版本,NASA 经过了火箭,载人,登月,火星等各种毒打,思路是稳定优先。所以才会有 SpaceX 用 x86 来搞,NASA 坚持用 Radiation Hardened CPU,同样 Tesla 的车和丰田的车也可以类比。

外太空有很多其他因素要考虑,温差大(零下几百度到零上几百度),震动大,辐射大要防 SEU 处理,还要尽可能省电,优先保证安全性,还要*对的稳定,还要做 N 多冗余设计,这么多东西要考虑,整个系统设计思路跟地球上其他系统完全不一样。

当然,随着发射成本的降低,现在也有把 HP 的高性能计算集群发射到空间站进行就地处理的实验了,好像已经有初步的结果,不知道没经过上述处理,只是简单的做 ECC 是否能保证结果的正确性。
shijingshijing 89
shijingshijing 6 小时 6 分钟前
@ipwx

芯片不是太弱,性能根本不是优先考虑的选项,首先要考虑的就是尽可能不出错,当前芯片性能完全是过剩的。但是地球上普通的芯片是不能直接拿去用的,需要定制耐高温,抗辐射等特殊设计的 CPU 。

https://en.wikipedia.org/wiki/RAD750
CRVV 90
CRVV 5 小时 50 分钟前
@ipwx
从另一个角度来看这个问题,写 Linux 内核也不让用 exception template iostream 这些东西,这不是 NASA 才有的局限。而且很显然 Linux 内核的局限更严格。

C++ 本来就有很多种用法,可以当稍微多一点功能的 C 来用,也可以当随便写 usafe 的 Rust 来用。
NASA 这么规定很可能只是因为技术负责人和 Linus 有相同的观点而已。
Caturra 91
Caturra 5 小时 23 分钟前 via Android
@CRVV 我寻思 Linux 内核也不是 c++写的,根本用不了。不过假设可以用,感觉内核社区也不屑于用这些特性
leven87 92
leven87 4 小时 13 分钟前
跑在嵌入式设备上的训练模型
ipwx 93
ipwx 3 小时 55 分钟前
@shijingshijing 我说的“太弱”也包含了“不能在太空稳定运行”这件事。

@CRVV Linux 内核不用 exception / iostream 是因为内核态上面的操作太奇妙,以至于不能用这些。可以理解。但是内核存在的意义就是让别的程序能够用 exception / iostream 这些高级玩意儿,更快开发程序不是么 233

至于 template 。那主要是 Linux 觉得 C++ 不利于这么大接近硬件的项目去兼容“傻逼程序员”。

其实软件工程从一开始就是试图用更高层的抽象去封装更复杂的显示细节。只有封装了才能让更“傻逼”的程序员更快的出活。内核就是干这事的,对于内核不能用 C++ 我觉得非常非常可以理解。要我说,用 C++ 也不过是权益之道,如果将来有一个高级抽象,可以有 C++ 的(执行时间)可靠性和 Java/C#(程序开发)可靠性,我举双手双脚赞成。
—-

@shijingshijing @CRVV 所以我的论点倒不是说 C++ 多牛逼,大家一定要用 C++。而是觉得在 C++ 和 C 之间,C++ 提供的东西更多,理论上更容易出活。这个论调肯定没问题,不然游戏引擎为啥不用 C 写而用 C++ 写是不是? C++ 开发速度 > 砍掉很多东西的 C++ > C 这是客观事实,不然 NASA 为什么不用裸 C 呢?

只是感叹这种抽象和硬件条件(包括物理学、发动机之类的制造水平)还不足以让星辰大海的征途一路通畅、高效快速前进罢了(叹息)
ipwx 94
ipwx 3 小时 53 分钟前
@shijingshijing

> SpaceX 毕竟刚刚出大气层,还没遭受火星上各种宇宙射线,各种高能粒子轰炸,Elon Musk 本身把安全性看的也不高,成本和快速迭代占了较高的优先级
—–

事实上我觉得这才是快速进入太空时代必要的。NASA 这种慢吞吞的风格才是权宜之计。
shijingshijing 95
shijingshijing 3 小时 24 分钟前
@ipwx

其实软件工程从一开始就是试图用更高层的抽象去封装更复杂的显示细节。

这一点你说的没错,事实上从抽象这个功能来讲,C++既需要做抽象,又要放不下底层,还要兼顾各种特性,于是造成了本身无比的复杂。现在普遍引入了 MBSE,基于 UML/SysML 在更高层级上建模,然后直接生成 C 代码,*终编译成机器码直接执行。这种模式已经是大的趋势了。UML/SysML 专门负责抽象,C 负责底层,就这样两层就够了。所以这样看,C++就没什么意义了,两头兼顾两头都做不好。这也注定了即使用 C++,也是在某个领域某个模块(比如图像视频处理)非用不可的情况下,才会使用,而且限制及其严格。

还有就是 NASA 并不慢,一套系统不是只要搞软件就能 run 起来的,在软件上面还有更高层的系统级的考量。基本的飞行系统架构,*顶层是飞行器系统,然后分解为推进子系统,动力控制子系统,环境控制子系统,电气子系统,电子子系统等等,这些子系统里面再进一步细分为机械模块,硬件模块,软件模块等模块,所以至少是三个层级。比如推进子系统,机械模块分为发动机喷嘴,燃料阀门,点火装置,压力检测等子模块; 硬件模块对应有发动机喷嘴控制器,燃料阀门控制器,点火装置控制器,压力检测传感器等硬件设备;然后才是运行在这些控制器里的软件模块。所以整体是一个非常庞大的系统,不止一个 CPU,不止一种 CPU,不像做个 App 什么的打开 IDE 就能写。

NASA 的系统工程和软件工程都是一流的,很多软件领域的概念和方法都是由 NASA 或者其他航空航天巨头首先提出,然后才慢慢引入到软件领域的,特别是测试领域,比如 MC/DC 。Elon Musk 纯粹是莽,没把安全性放在首位,如果一定要类比,我觉得 NASA 是传统类型的软件厂商,SpaceX 是互联网厂商。
CRVV 96
CRVV 1 小时 41 分钟前
@Caturra
这里有个因果关系的问题,因为 Linus 不喜欢 C++,所以内核才不用 C++。他不喜欢 C++ 的原因当然是 C++ 比 C 多的那些功能。
另外 Linux 内核已经开始用 Rust 了。
rainfd 97
rainfd 1 小时 18 分钟前
cuda

到底什么情况下需要使用 MySQL 事务?

众所周知,MySQL 事务具有原子性,当你需要对对张表同时进行操作的时候,为了保证要么都成功,要么都失败,必须使用事务机制。

但是仔细想想,举个例子 insert 操作,除非是 sql 语句写的有问题,正常情况下很难出错,除非是网络连接失败、数据库挂了、连接池满了等意外情况。这些都是*低概率事件。

然而如果同时操作的表不在一个库就操蛋了……所以去 tm 的事务,听天由命吧,大不了表里面多几条废数据,用什么事务!我不用了
第 1 条附言 · 21 小时 52 分钟前
纯属吐槽,我的意思是一些不太重要的业务可以不用,图个省事,特别是那种跨库的写表操作,丢了也就丢了,如果涉及订单、金额等重要业务确实使不得,分布式事务也得上
第 2 条附言 · 19 小时 59 分钟前
各位大佬说的对,然而我就是不喜欢用事务,哈哈,哪天出 bug 找我我再修复 bug,这样工作量就上来了
事务 MySQL 原子性 insert44 条回复 • 2021-09-02 16:10:04 +08:00
AngryPanda 1
AngryPanda 22 小时 41 分钟前 ❤️ 22
说的好!回去等通知。
leonme 2
leonme 22 小时 36 分钟前 via iPhone
正常情况下很难出错,这……举个例子,目前各种分布式共识算法,不就是解决非正常情况下的问题的吗? 事物本质上是简化应用层的开发工作
hqs0417 3
hqs0417 22 小时 35 分钟前
有些不一定是 SQL 语句的问题,可能是缺少必填字段,主键 /唯一键冲突。
单库上使用事务可以降低系统复杂度。

多库情况下就需要考虑*终一致了
ch2 4
ch2 22 小时 33 分钟前
我给你转 100W,转账失败了
你的钱没多,我的钱扣了
凭空消失 100W 你说事务有啥意义?
Maboroshii 5
Maboroshii 22 小时 33 分钟前
保证原子性… select update 一串操作的时候 。 当然 还要配置隔离级别这种东西。。不过我也基本没咋用过事务..
falcon05 6
falcon05 22 小时 31 分钟前 via iPhone
啊?
zhaorunze 7
zhaorunze 22 小时 28 分钟前
可以不用事务,你可以只创建一个表,其他所有场景都往这个表堆字段,行与行之间不需要共享,就不需要事务了。
yianing 8
yianing 22 小时 26 分钟前 via Android
账户的余额更改与流水创建必须在一个事务里面
Yadomin 9
Yadomin 22 小时 26 分钟前
“意外情况”,“*低概率” 就意味着不用管了?
11232as 10
11232as 22 小时 22 分钟前 ❤️ 2
事务不是个技术问题,事务是跟业务强相关得…
SlipStupig 11
SlipStupig 21 小时 26 分钟前
事务是用来保证数据库操作时的一致性和完整性啊,原子操作也是用来保证一致性啊,数据之间是有关联的一些你觉得不重要的数据可能会产生连锁反应。
比如:订单库存量,在条件竞争的情况下,可能导致库存数量为负数,在一些特定促销场景下然后导致订单爆单,这个时候公司就遭受了损失,这部分损失你愿意承担嘛?
Kilerd 12
Kilerd 21 小时 17 分钟前
您说的对!
sadfQED2 13
sadfQED2 21 小时 15 分钟前 via Android
面试的时候需要使用事物
love 14
love 21 小时 0 分钟前
too young 了
你以为就写数据库会出错?你的程序不会抛异常?抛了异常要不要回滚?就这么留着写了一半的数据集?
sy20030260 15
sy20030260 20 小时 38 分钟前
大部分垃圾代码就是「图个省事」带来的,写的人越省事,后续维护的人越蛋疼
Senorsen 16
Senorsen 20 小时 2 分钟前
1L + 10086 哈哈哈哈哈

你操作一次出现异常(比如网络错误)的概率假设是万分之一,十万次操作出现异常的概率是多少?更多次操作呢?
图省事没有事务了,除非用户量很少不到几十几百个,否则用户资源状态不一致,全乱了,导致不可用,怎么办?事后处理修数据怕是都难。
seeker 17
seeker 19 小时 29 分钟前
处理钱的时候。如果是个博客啥的,没了就没了把。
billlee 18
billlee 19 小时 21 分钟前 ❤️ 1
开事务还提升性能啊,innodb 不开事务那其实就是每条语句 auto-commit, 每行写入语句都要刷盘
zhangxiaodao 19
zhangxiaodao 18 小时 14 分钟前
大多数时候,事务是和锁一起使用用来控制并发的。非并发情况,用或不用,都是业务对编码的容忍度要求。

但是,小同志,你这个『大不了多几条废数据』的思想不能有啊,写代码还是要严谨,坏习惯不能养。
shiji 20
shiji 18 小时 0 分钟前
哈哈,这就好比研究导弹干什么,搞一架客机+几组劳动人民从飞机往下一筐一筐扔手榴弹不就完了

不过一个科技公司一旦高层技术人员都是这个思想,并对新来的 /严谨的人嗤之以鼻的话,基本离完蛋不差几年了。
EscYezi 21
EscYezi 15 小时 42 分钟前 via iPhone
“丢了就丢了”……希望出生产事故的时候楼主还能这么想
yeqizhang 22
yeqizhang 15 小时 17 分钟前 via Android
标题就起的很唬人,正文又描述的其它
dayeye2006199 23
dayeye2006199 15 小时 6 分钟前
我要是公司领导,我只想把 LZ 拖出去打屁屁
felixcode 24
felixcode 14 小时 28 分钟前
所以其它的可靠性设计在你这样的程序面前全都是个摆设,因为这样的程序在哪都是水桶的*短板。
yogogo 25
yogogo 9 小时 40 分钟前
多表操作的时候
paranoiddemon 26
paranoiddemon 9 小时 21 分钟前 via Android
加事务代码量很大?
murmur 27
murmur 8 小时 41 分钟前
这就是互联网思维,亲爱的用户我是你 X 爱用用不用拉倒,数据丢了就陪你点代金券就当无事发生
msaionyc 28
msaionyc 8 小时 4 分钟前
希望以后不会遇到你这种同事
tabris17 29
tabris17 7 小时 51 分钟前 via iPhone ❤️ 3
再过几年,你回过头来看当年发的这个帖子,你内心会有多尴尬呀

当然,那时候你可能也已经转行了
pkoukk 30
pkoukk 7 小时 48 分钟前
啧啧啧,我上大学的时候都问不出这种问题来。
cbasil 31
cbasil 7 小时 35 分钟前
例如商城里面优惠券使用,如果优惠券已经抵扣了,但是下单是因为用户账户问题下单失败,不用事务怎么处理优惠券还原的问题
sujin190 32
sujin190 6 小时 51 分钟前
恰恰多个操作不在同一个库才更需要事务,哥们,你搞反了
banmuyutian 33
banmuyutian 6 小时 41 分钟前
逆天
realpg 34
realpg 6 小时 14 分钟前 ❤️ 1
楼主,你要是入职哪个第三方支付一定告诉我啊,我多注册接
heyjei 35
heyjei 5 小时 52 分钟前 via Android
你们其实没看到楼主的问题在哪。

楼主的问题是一个 insert 语句还要用 begin transaction, commit 包起来,觉得很麻烦。楼主不知道的是单独的 sql 语句,数据库执行的时候是默认开始并在语句结束后自动提交事务的。
heyjei 36
heyjei 5 小时 44 分钟前 via Android
而我们所说的事务,一般都是指有很多个 sql 语句一起执行的时候,需要用事务包起来,要么全部执行,要么全部不执行。

在这里触发事务回滚的不一定是 SQL 写错了,或者磁盘故障,或者网络故障,更多的时候,我们触发回滚都业务上的逻辑,比如转账的时候,先 sql 减去了一个人的钱,然后通过另外一个 sql 语句发现对方账号锁了,转不进钱,于是这里只需要回滚事务就可以了,不需要重新执行 sql 一步步的恢复现场。

这只是一个简单的例子,数据库的事务远远不止这些。
heyjei 37
heyjei 5 小时 42 分钟前 via Android
善用事务,可以*大的简化和优化你的业务逻辑代码
kenvix 38
kenvix 5 小时 38 分钟前
先不说别的,姑且问一句写过存储过程吗
darkengine 39
darkengine 4 小时 40 分钟前
是否需要提桶跑路 = 调用次数 * 故障发生概率 * 故障后果

由此可知,不能因为“故障发生概率低”就不管
Felldeadbird 40
Felldeadbird 3 小时 37 分钟前
insert 语句也会出错啊。假定你某个 SQL 不稳定,导致一直 sleep 。这时候你 insert 就 wait 状态。你没事务的话,等 wait 结束后,他插入了数据。
tairan2006 41
tairan2006 3 小时 4 分钟前
、楼主脑子里没有并发的概念么
sakasaka 42
sakasaka 3 小时 3 分钟前
防止安全事故还有就是简化代码逻辑
zxcslove 43
zxcslove 3 小时 1 分钟前
就发泄情绪呗,谁还没个情绪了,哈哈哈
chendy 44
chendy 1 小时 15 分钟前
看了下楼主的历史发帖,细思*恐

如何记录知识网络?

我目前只能做到记录点和面

比如我看了几篇《秒杀系统》的文章,可以总结下有哪些手段和设计,这其实是个点
又比如我*近系统的看了 MySQL 的书,把知识点总结一下,画个思维导图,这其实是个面

但是一直感觉知识还是很零散,不成系统,没有形成所谓的知识网络

想问下,你们是怎么记录和管理知识网络的?
知识 记录点 导图 知识点14 条回复 • 2021-09-02 16:48:40 +08:00
darkengine 1
darkengine 2 小时 2 分钟前
印象笔记
korvin 2
korvin 1 小时 54 分钟前
简阅(网页转 markdown 转存坚果云)+ typora

*近看很多写 obsidian 准备看看 obsidian
xwayway 3
xwayway 1 小时 51 分钟前
想过这个问题,想有一个工具,在思维导图里面,能够做成图的结构。然后各个节点还能做超链,链接到文章,但是并没有这种工具。目前用飞书文档,按类别分类,然后每个分类下做个思维导图,再配合详细的文章来整理。
qwq11 4
qwq11 1 小时 46 分钟前
@xwayway 国外有 Notion,国内有 Wolai
musi 5
musi 1 小时 44 分钟前 ❤️ 1
Obsidian
RRRoger 6
RRRoger 1 小时 41 分钟前
用 obsidian
lasuar 7
lasuar 1 小时 32 分钟前
支持树形结构的笔记软件有很多
wangkun025 8
wangkun025 1 小时 29 分钟前
没记录。
反正感觉也带不走。
feiffy 9
feiffy 1 小时 24 分钟前
折腾过很多,roam research 在线、贵, roam edit 不稳定,丢数据,logseq 太卡了,remnote 卡,wolai 、notion 文档型不提了,mediawiki 之类,比较笨重不方便

*后我选择 obsidian + 坚果云 /onedrive/git,数据简洁纯 md,功能足够好用, 插件多、配置可以随文件一起同步
locoz 10
locoz 47 分钟前
用以块为结构存储的文档工具,一个知识点一个块,每个块之间加上关联直接出网状图就是所谓的“知识网络”了
Rever1e 11
Rever1e 44 分钟前
markdown 记录 长篇幅的
xmind 记录 树形体系
都存到 github private
palexu 12
palexu 36 分钟前
Obsidian
MoeMoesakura 13
MoeMoesakura 31 分钟前
Markdown yyds
bclerdx 14
bclerdx 30 分钟前
@lasuar 比如哪些呢?

大厂的对象存储都是基于什么实现的呢?

腾讯云 COS,阿里云 OSS,HW OBS 这些对象存储服务是独立自主实现的,还是基于什么开源项目(比如 :MinIO )实现的呢?
OSS minio 存储 对象25 条回复 • 2021-09-02 16:06:46 +08:00
Ianchen 1
Ianchen 1 天前
盲猜一个 Ceph
acbot 2
acbot 1 天前
@Ianchen *早我感觉也是 Ceph 但是后来我发现 MinIO 更像 特别是 Rest Api 和 微服务结合方面
ccde8259 3
ccde8259 22 小时 39 分钟前 via iPhone
Ceph……
自研一套 API 不用几个钱
dynastysea 4
dynastysea 22 小时 25 分钟前 ❤️ 1
明确的告诉你,这三家都是自研的,ceph 这东西当当玩具可以,真正大规模的实践坑很多。存储是云计算的根基,在这块上各家都是重金投入,而且不存在兼容性的问题,自研好处多多。
moult 5
moult 21 小时 45 分钟前 via iPhone
腾讯和阿里有历史技术沉淀在,肯定自研的。像 HW,七牛这类厂商就不好说了。。。不过,感觉 ceph 和 minio 自用还可以,拿来卖服务还差口气儿。
tongz 6
tongz 21 小时 17 分钟前
各位大佬, OSS 的话, 海量小文件存储靠谱吗, 单个文件平均在 1KB-10KB 之间, 每天产生 1 千万个左右, 还在持续增长, 写多读少
locoz 7
locoz 21 小时 15 分钟前 via Android
底层可能部分用了 ceph,但是主体肯定是自己搞的,大厂又不差那点钱。
plko345 8
plko345 20 小时 42 分钟前 via Android
@acbot 对象存储好像都是有标准的,api 和协议方面都差不太多,aws s3 也是,底层不一样,但提供给用户的接口一样,所以会有这种错觉
gstqc 9
gstqc 19 小时 55 分钟前 via Android
主要是 s3 协议已经是事实上的业界标准了,不兼容就没人用
ospider 10
ospider 19 小时 21 分钟前
ceph 确实支撑不了多大的场景,但是也不至于到玩具的地步。头条 2017 年才从 ceph 切换到自研的。
boyhailong 11
boyhailong 19 小时 6 分钟前
了解的成都好几个做云存储的都是 ceph 。。。。 而且也都商用了
dongqihong 12
dongqihong 18 小时 47 分钟前
大体都分三层,接入层( API 、各种业务逻辑),索引层(基于 LSM ),持久化存储层(分部署存储,类似 HDFS )
阿里腾讯华为都大致如此,S3 早期也一样
WebKit 13
WebKit 18 小时 44 分钟前 via Android
@moult 七牛也是 go 开发,自研的
gstqc 14
gstqc 18 小时 35 分钟前
AWS S3 这种,一个 bucket 放百亿量的文件,如何做检索的
opengps 15
opengps 18 小时 5 分钟前
参考 aws 的对象存储
crowdwei 16
crowdwei 16 小时 4 分钟前
Azure Storage:
https://azure.microsoft.com/en-us/blog/sosp-paper-windows-azure-storage-a-highly-available-cloud-storage-service-with-strong-consistency/
henvm 17
henvm 15 小时 43 分钟前 via Android
对象存储不是基于对象的吗?
imbushuo 18
imbushuo 9 小时 19 分钟前
https://dl.acm.org/doi/10.1145/2043556.2043571

scalable table as index + scalable persistent layer (stamp),跟 SSD 的内部存储组织类似但是 scale 被放大了
acbot 19
acbot 8 小时 44 分钟前
@boyhailong 曾经我看一个 私有云的商用产品存储方案也是用的 ceph
acbot 20
acbot 8 小时 43 分钟前
@moult 也就是 一个中型公司自己服务用的话 ceph 和 minio 都没有问题是不?
acbot 21
acbot 8 小时 42 分钟前
@ospider 是什么原因造成 ceph 不能支撑大的场景呢?如果自用只是存储量比较大的情况 ceph 能胜任不呢?
swulling 22
swulling 7 小时 18 分钟前 via iPhone
说底层用 ceph 的可能对数据量缺乏想象力
dynastysea 23
dynastysea 6 小时 45 分钟前
@moult 如果你是个存储行业的,你就知道 HW 的存储实力有多强了(软硬层面全栈都有自研,腾讯现在还停留在软件层面,阿里也在涉及存储硬件开发),互联网公司在华为面前就是跟玩似的,特别是腾讯,虽然是自研但基本也是跟着开源的节奏来(华为在国外都有专门的研究所,比如俄罗斯有专门的存储算法研究团队,存储部门内的博士更是一大堆,阿里也有美国研发团队,博士也不少。腾讯投入小很多,基本都是国内招本科生、研究生,存储行业好的博士基本不会选择腾讯,多数都是选择华为、阿里,很多在华为的挖都挖不动,对于那些深研技术的,目前国内差不多只有华为、阿里有更高的平台可以让他们接触到*前沿的存储技术)。华为存储做的很早,腾讯、阿里存储组里面很多华为跳槽过来的。
zmxnv123 24
zmxnv123 4 小时 56 分钟前 via iPhone
hdfs+hbase 目前对象数大概几十万亿。
armyHcz 25
armyHcz 1 小时 8 分钟前
@moult 七牛用的是阿里云包了一层

如何在 Python3 中对列表 通过比较排序(不懂就问)?

已知列表和字典,列表是需排序元素,字典指明了元素间的优先关系,譬如 S1 需在 S3 之前,而 S3 又在 S2 之前。

s = [‘S1′,’S2′,’S3’]

val = {(‘S1′,’S3’):1,(‘S3′,’S2’):1}
希望得到的结果是[‘S1′,’S3′,’S2′],请问有何好的实现方式?

自己想用 python 中的 sort 的 cmp 参数进行排序,结果竟然没排序,不知原因。

from functools import cmp_to_key

s.sort(key=cmp_to_key(lambda x,y: val.get((x,y),0)))
s3′ Python s1’ s2’11 条回复 • 2021-09-02 16:34:53 +08:00
toaruScar 1
toaruScar 5 小时 38 分钟前
可以弄成一个 class,然后用 functools.total_ordering 自定义对比函数。
MoYi123 2
MoYi123 5 小时 34 分钟前
s = [‘S1’, ‘S2’, ‘S3’]
val = {(‘S1’, ‘S3’): 1, (‘S3’, ‘S2’): 1}

class S(str):

____def __lt__(self, other):
________if (self, other) in val:
____________return val[(self, other)] == 1
________return False

s = [S(i) for i in s]
s.sort()
print([str(i) for i in s])

能用, 但是应该有更好的办法
Trim21 3
Trim21 5 小时 33 分钟前 via Android ❤️ 1
key=lambda x: {s1: 1 , s3: 2, s3: 3}.get(x, 0)
lonelinsky 4
lonelinsky 4 小时 59 分钟前
s = [‘S1′,’S2′,’S3’]
val = {(‘S1′,’S3’):-1,(‘S3′,’S2’):-1} # 这里你一开始的定义有点问题,如果你希望 S1 排在 S3 前面,则它的值应该是负的

s.sort(key=cmp_to_key(lambda x,y: val.get((x,y), -val.get((y,x), 0)))) # 这里你可能需要同时处理 (y, x) 的情况,如果你的定义是对称的。即 S1 在 S3 前面 <=> S3 在 S2 后面

注意你现在的方式里面对于未出现 val 里面的对,都会当成不需要排序的对象。如果你是像解决 AOE 网络的拓扑排序问题,建议直接看相关算法。

=============================
你一开始的排序完全没用是因为排序时候,假设按序比较
(S1, S2) 你的 val 里面没有,返回 0 表示相等,不用做任何操作
(S2, S3) 你的 val 里面还是没有,返回 0 表示相等,又不用做任何操作,所以它已经排好序了
lonelinsky 5
lonelinsky 4 小时 51 分钟前
typo
S1 在 S3 前面 <=> S3 在 S1 后面
superhxl 6
superhxl 4 小时 30 分钟前
@lonelinsky 我的定义没问题,也许有更好的方式。本身问题背景是若干个点,一次对这些点遍历,想要打印遍历的顺序。字典中的值表示相继访问的顺序,访问 S1 后访问 S3,访问 S3 后访问 S2.

关于 cmp 的应用再查查文档,这个确实是临时搜索的解决方案。

目前可以通过冒泡排序解决,但我想应该有更好办法。
“`python
for p in range(len(s)-1):
for q in range(p + 1, len(s)):
if sol[z[(s[q],s[p])]] == 1:
s[p], s[q] = s[q], s[p]
“`
@toaruScar 去学习一下具体用法。
@MoYi123 好复杂的样子。
@Trim21 没看懂。
lonelinsky 7
lonelinsky 3 小时 29 分钟前
https://docs.python.org/3/library/functools.html#functools.cmp_to_key
A comparison function is any callable that accept two arguments, compares them, and returns a negative number for less-than, zero for equality, or a positive number for greater-than.

你定义的 1 会被认为是 greater-than,而默认排序是从小到大,所以你的定义和你的预期是反的,这是我说你定义有问题的地方,如果执意用 1 做定义的话,可以在 function 里面取个反就行。
你现在的定义里面不能保证任意两个元素的 cmp 结果都包含,比如你开始的定义里面 S1 和 S2 的大小关系没有显式定义,在冒泡这种会完全比较的排序方法中可能没问题,但是对于类似快排这种,排序中间可能会出现错误的结果。(这个地方是直觉,没有严格证明)

> 本身问题背景是若干个点,一次对这些点遍历,想要打印遍历的顺序。
如果是这个问题的话,建议用 OrderedDict https://docs.python.org/3/library/collections.html#collections.OrderedDict,直接打印就好了。
lonelinsky 8
lonelinsky 3 小时 25 分钟前
> 字典中的值表示相继访问的顺序
我好像理解错了,所以字典中的值会是 1,2,3 这样?是的话对字典按 value 排序,然后对 key 做个 reduce 就好了?
BBrother 9
BBrother 3 小时 24 分钟前
没看到你的问题,你是不是需要拓扑排序?
princelai 10
princelai 55 分钟前 ❤️ 2
from networkx import DiGraph, draw_networkx, topological_sort

s = [‘S1’, ‘S2’, ‘S3’, ‘S4’, ‘S5’]
val = {(‘S1’, ‘S3’): 1, (‘S3’, ‘S2’): 1, (‘S5’, ‘S1’): 1, (‘S2’, ‘S4’): 1}
g = DiGraph()
g.add_edges_from(val.keys())
s = list(topological_sort(g))
ipwx 11
ipwx 41 分钟前
@superhxl python 默认的不是两两比较排序,而是快速排序,只比 N log N 次。

所以你期待的 S1 < S3 + S3 < S2 imply S1 < S2 不存在。

你这个需求可以建图然后用拓扑排序。

Android内存优化8 内存检测工具2 LeakCanary——直白的展现Android中的内存泄露

Android内存优化8 内存检测工具2 LeakCanary——直白的展现Android中的内存泄露

之前碰到的OOM问题,终于很直白的呈现在我的眼前:我尝试了MAT,但是发现不怎么会用。直到今天终于发现了这个新工具:

当我们的App中存在内存泄露时会在通知栏弹出通知:

这里写图片描述

当点击该通知时,会跳转到具体的页面,展示出Leak的引用路径,如下图所示:

这里写图片描述

LeakCanary 可以用更加直白的方式将内存泄露展现在我们的面前。

 

工程包括:

  1. LeakCanary库代码
  2. LeakCanaryDemo示例代码

使用步骤:

  1. 将LeakCanary import 入自己的工程
  2. 添加依赖:

    compile project(':leakcanary')

  3. 在Application中进行配置
    public class ExampleApplication extends Application {
    
      ......
      //在自己的Application中添加如下代码
    public static RefWatcher getRefWatcher(Context context) {
        ExampleApplication application = (ExampleApplication) context
                .getApplicationContext();
        return application.refWatcher;
    }
    
      //在自己的Application中添加如下代码
    private RefWatcher refWatcher;
    
    @Override
    public void onCreate() {
        super.onCreate();
        ......
            //在自己的Application中添加如下代码
        refWatcher = LeakCanary.install(this);
        ......
    }
    
    .....
    }
    
  4. 在Activity中进行配置
public class MainActivity extends AppCompatActivity {

    ......
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
            //在自己的应用初始Activity中加入如下两行代码
        RefWatcher refWatcher = ExampleApplication.getRefWatcher(this);
        refWatcher.watch(this);

        textView = (TextView) findViewById(R.id.tv);
        textView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startAsyncTask();
            }
        });

    }

    private void async() {

        startAsyncTask();
    }

    private void startAsyncTask() {
        // This async task is an anonymous class and therefore has a hidden reference to the outer
        // class MainActivity. If the activity gets destroyed before the task finishes (e.g. rotation),
        // the activity instance will leak.
        new AsyncTask<Void, Void, Void>() {
            @Override
            protected Void doInBackground(Void... params) {
                // Do some slow work in background
                SystemClock.sleep(20000);
                return null;
            }
        }.execute();
    }


}

 

  1. 在AndroidMainfest.xml 中进行配置,添加如下代码
        <service
            android:name="com.squareup.leakcanary.internal.HeapAnalyzerService"
            android:enabled="false"
            android:process=":leakcanary" />
        <service
            android:name="com.squareup.leakcanary.DisplayLeakService"
            android:enabled="false" />

        <activity
            android:name="com.squareup.leakcanary.internal.DisplayLeakActivity"
            android:enabled="false"
            android:icon="@drawable/__leak_canary_icon"
            android:label="@string/__leak_canary_display_activity_label"
            android:taskAffinity="com.squareup.leakcanary"
            android:theme="@style/__LeakCanary.Base" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

5、测试结果

a、Toast显示(大概10秒左右显示)

%title插图%num

b、通知显示

%title插图%num

c、桌面自动添加的图表

%title插图%num

d、内存泄露列表

%title插图%num

e、内存泄露详细

%title插图%num

LogCat可以看到日志日下(hprof文件可以用MAT打开进行分析):

  1. 01-04 11:49:41.815 12967-13004/com.micky.leakcanarysamples I/dalvikvm: hprof: dumping heap strings to “/storage/emulated/0/Download/leakcanary/suspected_leak_heapdump.hprof”.
  2. 01-04 11:49:42.020 12967-13004/com.micky.leakcanarysamples I/dalvikvm: hprof: heap dump completed (28850KB)

查看自动生成的AndroidManifest文件,LeakCanarySamples/app/build/intermediates/manifests/full/debug/AndroidManifest.xml

  1. <?xml version=“1.0” encoding=”utf-8″?>  
  2. <manifest xmlns:android=“http://schemas.android.com/apk/res/android”  
  3.     package=“com.micky.leakcanarysamples”  
  4.     android:versionCode=“1”  
  5.     android:versionName=“1.0” >  
  6.     <uses-sdk  
  7.         android:minSdkVersion=“10”  
  8.         android:targetSdkVersion=“23” />  
  9.     <!– To store the heap dumps and leak analysis results. –>  
  10.     <uses-permission android:name=“android.permission.WRITE_EXTERNAL_STORAGE” />  
  11.     <android:uses-permission android:name=“android.permission.READ_EXTERNAL_STORAGE” />  
  12.     <application  
  13.         android:name=“com.micky.leakcanarysamples.BaseApplication”  
  14.         android:allowBackup=“true”  
  15.         android:icon=“@mipmap/ic_launcher”  
  16.         android:label=“@string/app_name”  
  17.         android:supportsRtl=“true”  
  18.         android:theme=“@style/AppTheme” >  
  19.         <activity  
  20.             android:name=“com.micky.leakcanarysamples.MainActivity”  
  21.             android:label=“@string/app_name”  
  22.             android:theme=“@style/AppTheme.NoActionBar” >  
  23.             <intent-filter>  
  24.                 <action android:name=“android.intent.action.MAIN” />  
  25.                 <category android:name=“android.intent.category.LAUNCHER” />  
  26.             </intent-filter>  
  27.         </activity>  
  28.         <activity android:name=“com.micky.leakcanarysamples.TestActivity” />  
  29.         <service  
  30.             android:name=“com.squareup.leakcanary.internal.HeapAnalyzerService”  
  31.             android:enabled=“false”  
  32.             android:process=“:leakcanary” />  
  33.         <service  
  34.             android:name=“com.squareup.leakcanary.DisplayLeakService”  
  35.             android:enabled=“false” />  
  36.         <activity  
  37.             android:name=“com.squareup.leakcanary.internal.DisplayLeakActivity”  
  38.             android:enabled=“false”  
  39.             android:icon=“@drawable/__leak_canary_icon”  
  40.             android:label=“@string/__leak_canary_display_activity_label”  
  41.             android:taskAffinity=“com.squareup.leakcanary”  
  42.             android:theme=“@style/__LeakCanary.Base” >  
  43.             <intent-filter>  
  44.                 <action android:name=“android.intent.action.MAIN” />  
  45.                 <category android:name=“android.intent.category.LAUNCHER” />  
  46.             </intent-filter>  
  47.         </activity>  
  48.     </application>  
  49. </manifest>  

如上所示LeakCanary给我们自动添加了两个Service和一个Activity,并添加了对SD卡的读写权限





It 's so simple.

注:

1、如果在Release模式下请使用RefWatcher.DISABLED

2、在Activity或Fragment 的 Destroy方法中添加检测(很好理解,就是判断一个Activity或Fragment想要被销毁的时候,是否还有其他对象持有其引用导致Activity或Fragment不能被回收,从而导致内存泄露)

 

LeakCanary,30分钟从入门到精通

LeakCanary,30分钟从入门到精通

简述

在性能优化中,内存是一个不得不聊的话题;然而内存泄漏,显示已经成为内存优化的一个重量级的方向。当前流行的内存泄漏分析工具中,不得不提的就是LeakCanary框架;这是一个集成方便, 使用便捷,配置超级简单的框架,实现的功能却是*为强大的。


不骗你,真的,使用就是这么简单 ?!

1. 你需要添加到配置的只有这个

dependencies {

debugCompile ‘com.squareup.leakcanary:leakcanary-android:1.3’

releaseCompile ‘com.squareup.leakcanary:leakcanary-android-no-op:1.3’

}

2. 你肯定需要初始化一下,当然, 推荐在Application中

public class MyApplicationextends Application {

@Override

public void onCreate() {

super.onCreate();

LeakCanary.install(this);

}

}

3. 什么?你还在下一步?已经结束了!通过以上配置,你就可以轻松使用LeakCanary检测内存泄漏了

    关于LeakCanary的详细使用教程,建议去看:LeakCanary中文使用说明


闲聊结束,我想你们来肯定不是看我这些废话的,那么现在进入正题!本篇我们所想的,就是LeakCanary为什么可以这么神奇,它是怎么检测内存泄漏的?下面我们解开谜题!


《LeakCanary原理》  核心类分析

01    LeakCanary                                        源码解析

02    LeakCanary                                        SDK提供类

03    DisplayLeakActivity                           内存泄漏的查看页面

04    HeapAnalyzerService                         内存堆分析服务, 为了保证App进程不会因此受影响变慢&内存溢出,运行于独立的进程

05    HeapAnalyzer                                     分析由RefWatcher生成的堆转储信息, 验证内存泄漏是否真实存在

06    HeapDump                                          堆转储信息类,存储堆转储的相关信息

07    ServiceHeapDumpListener                一个监听,包含了开启分析的方法

08    RefWatcher                                           核心类, 翻译自官方: 检测不可达引用(可能地),当发现不可达引用时,它会触发                                                                         HeapDumper(堆信息转储)

09    ActivityRefWatcher                               Activity引用检测, 包含了Activity生命周期的监听执行与停止

通过以上列表,让大家对LeakCanary框架的主要类有个大体的了解,并基于以上列表,对这个框架的大体功能有一个模糊的猜测。


漫无目的的看源码,很容易迷失在茫茫的Code Sea中,无论是看源码,还是接手别人的项目,都是如此;因此,带着问题与目的性来看这些复杂的东西是很有必要的,也使得我们阅读效率大大提高;想要了解LeakCanary,我们*大的疑惑是什么,我列出来,看看是与你不约而同。

Question1:    在Application中初始化之后,它是如何检测所有的Activity页面的 ?

Question2:    内存泄漏的判定条件是什么 ? 检测内存泄漏的机制原理是什么?

Question3:    检测出内存泄漏后,它又是如何生成泄漏信息的? 内存泄漏的输出轨迹是怎么得到的?

回顾一下这个框架,其实我们想了解的机制不外乎三:

1. 内存泄漏的检测机制

2. 内存泄漏的判定机制

3. 内存泄漏的轨迹生成机制

我们会在源码分析*后,依次回答以上的三个问题,可能在阅读源码之前,我们先要对内存泄漏做一些基础概念与原理的理解。


什么是内存泄漏(MemoryLeak)?

大家对这个概念应该不陌生吧,当我们使用一个Bitmap,使用完成后,没有recycle回收;当我们使用Handler, 在Activity销毁时没有处理;当我们使用Cursor,*后没有close并置空;以上这些都会导致一定程度上的内存泄漏问题。那么,什么是内存泄漏?

内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

以上是百度百科的解释,总结下为:内存泄漏是不使用或用完的内存,因为某些原因无法回收,造成的一种内存浪费;内存泄漏的本质是内存浪费。以个人理解来解释,通俗一点就是

1. GC回收的对象必须是当前没有任何引用的对象

2.当对象在使用完成后(对我们而言已经是垃圾对象了), 我们没有释放该对象的引用,导致GC不能回收该对象而继续占用内存

3.垃圾对象依旧占用内存,这块内存空间便浪费了

内存泄漏与内存溢出的区别是什么?

从名称来看,一个泄漏,一个溢出,其实很好理解。

内存泄漏: 垃圾对象依旧占据内存,如水龙头的泄漏,水本来是属于水源的, 但是水龙头没关紧,那么泄漏到了水池;再来看内存,内存本来应                     该被回收,但是依旧在内存堆中;总结一下就是内存存在于不该存在的地方(没用的地方)

内存溢出: 内存占用达到*大值,当需要分配内存时,已经没有内存可以分配了,就是溢出;依旧以水池为例, 水池的水如果满了,那么如果继                      续需要从水龙头流水的话,水就会溢出。总结一下就是,内存的分配超出*大阀值,导致了一种异常

明白了两者的概念,那么两者有什么关系呢?

内存的溢出是内存分配达到了*大值,而内存泄漏是无用内存充斥了内存堆;因此内存泄漏是导致内存溢出的元凶之一,而且是很大的元凶;因为内存分配完后,哪怕占用再大,也会回收,而泄漏的内存则不然;当清理掉无用内存后,内存溢出的阀值也会相应降低。


JVM如何判定一个对象是垃圾对象?

该问题也即垃圾对象搜索算法,JVM采用图论的可达遍历算法来判定一个对象是否是垃圾对象, 如果对象A是可达的,则认为该对象是被引用的,GC不会回收;如果对象A或者块B(多个对象引用组成的对象块)是不可达的,那么该对象或者块则判定是不可达的垃圾对象,GC会回收。

 

%title插图%num

 


以上科普的两个小知识:1) 内存泄漏  2) JVM搜索算法 是阅读LeakCanary源码的基本功,有助于源码的理解与记忆。好了,下面来看一下LeakCanary的源码,看看LeakCanary是怎么工作的吧!

既然LeakCanary的初始化是从install()开始的,那么从init开始看

%title插图%num
%title插图%num

回顾一下核心类模块可知,内存分析模块是在独立进程中执行的,这么设计是为了保证内存分析过程不会对App进程造成消*的影响,如使App进程变慢或导致out of Memory问题等。因此

*步: 判断APP进程与内存分析进程是否属于同一进程;如果是, 则返回空的RefWatcher DISABLED;如果不是,往下走,看第二步

%title插图%num
是否与分析进程是同一个, isInAnalyzerProcess
%title插图%num
与分析进程一致,返回一个空的DISABLED

第二步: enableDisplayLeakActivity 开启显示内存泄漏信息的页面

 

%title插图%num
调用了setEnabled,记住参数DisplayLeakActivity就是查看泄漏的页面,enable传true,开启Activity组件

 

%title插图%num
这个方法的作用是设置四大组件开启或禁用,从上图传的参数看,是开启了查看内存泄漏的页面

第三步:初始化一个ServiceHeapDumpListener,这是一个开启分析的接口实现类,类中定义了analyze方法,用于开启一个DisplayLeakService服务,从名字就可以看出,这是一个显示内存泄漏的辅助服务

%title插图%num
看注释,这个服务的作用是分析HeapDump,写入一个记录文件,并弹出一个Notification

第四步:初始化两个Watcher, RefWatcher和ActivityRefWatcher. 这两个Watcher的作用分别为分析内存泄漏与监听Activity生命周期

%title插图%num
ActivityRefWatcher监听Activity生命周期,在初始化时开始监听Activity生命周期(watchActivities)
%title插图%num
watchActivities中注册了所有Activity的生命周期统一监听;onActiityDestroy会在onDestroy时执行,执行watch,检测内存泄漏

 

通过以上代码分析,我们可以得出*个问题的答案。LeakCanary通过ApplicationContext统一注册监听的方式,来监察所有的Activity生命周期,并在Activity的onDestroy时,执行RefWatcher的watch方法,该方法的作用就是检测本页面内是否存在内存泄漏问题。


下面我们继续来分析核心类RefWatcher中的源码,检测机制的核心逻辑便在RefWatcher中;相信阅读完这个类后,第二个问题的答案便呼之欲出了。

既然想弄明白RefWatcher做了什么,那么先来看一下官方的解释

%title插图%num
监听可能不可达的引用,当RefWatcher判定一个引用可能不可达后,会触发HeapDumper(堆转储)

从上面图可以看出官方的解释。 RefWatcher是一个引用检测类,它会监听可能会出现泄漏(不可达)的对象引用,如果发现该引用可能是泄漏,那么会将它的信息收集起来(HeapDumper).

从RefWatcher源码来看,核心方法主要有两个: watch() 和 ensureGone()。如果我们想单独监听某块代码,如fragment或View等,我们需要手动去调用watch()来检测;因为上面讲过,默认的watch()仅执行于Activity的Destroy时。watch()是我们直接调用的方法,ensureGone()则是具体如何处理了,下面我们来看一下

%title插图%num
watch 检测核心方法

上图为watch()的源码, 我们先来看一下官方的注释

监听提供的引用,检查该引用是否可以被回收。这个方法是非阻塞的,因为检测功能是在Executor中的异步线程执行的

 

从上述源码可以看出,watch里面只是执行了一定的准备工作,如判空(checkNotNull), 为每个引用生成一个唯一的key, 初始化KeyedWeakReference;关键代码还是在watchExecutor中异步执行。引用检测是在异步执行的,因此这个过程不会阻塞线程。

 

%title插图%num
检测核心代码 gone()判定WeakReference中是否包含当前引用

以上是检测的核心代码实现,从源码可以看出,检测的流程:

1) 移除不可达引用,如果当前引用不存在了,则不继续执行

2) 手动触发GC操作,gcTrigger中封装了gc操作的代码

3) 再次移除不可达引用,如果引用不存在了,则不继续执行

4) 如果两次判定都没有被回收,则开始分析这个引用,*终生成HeapDump信息

总结一下原理:

1. 弱引用与ReferenceQueue联合使用,如果弱引用关联的对象被回收,则会把这个弱引用加入到ReferenceQueue中;通过这个原理,可以看出removeWeaklyReachableReferences()执行后,会对应删除KeyedWeakReference的数据。如果这个引用继续存在,那么就说明没有被回收。

2. 为了确保*大保险的判定是否被回收,一共执行了两次回收判定,包括一次手动GC后的回收判定。两次都没有被回收,很大程度上说明了这个对象的内存被泄漏了,但并不能100%保证;因此LeakCanary是存在*小程度的误差的。

 

上面的代码,总结下流程就是

判定是否回收(KeyedWeakReference是否存在该引用), Y -> 退出, N -> 向下执行

手动触发GC

判定是否回收, Y -> 退出, N-> 向下执行

两次未被回收,则分析引用情况:

1) humpHeap :  这个方法是生成一个文件,来保存内存分析信息 

2) analyze: 执行分析

通过以上的代码分析,第二个问题的答案已经浮出水面了吧!


接下来分析内存泄漏轨迹的生成~

*终的调用,是在RefWatcher中的ensureGone()中的*后,如图

%title插图%num
分析*终调用,在ensureGone()中

很明显,走的是heapdumpListener中的analyze方法,继续追踪heapdumpListener是在LeakCanary初始化的时候初始化并传入RefWatcher的,如图

%title插图%num
在install中初始化并传入RefWatcher

打开进入ServiceHeapDumpListener,看里面实现,如图

%title插图%num
ServiceHeapDumpListener中的analyze

调用了HeapAnalyzerService,在单独的进程中进行分析,如图

%title插图%num
HeapAnalyzerService分析进程

HeapAnalyzerService中通过HeapAnalyzer来进行具体的分析,查看HeapAnalyzer源码,如图

%title插图%num
HeapAnalyzer

进行分析时,调用了openSnapshot方法,里面用到了SnapshotFactory

%title插图%num
org.eclipse.mat

从上图可以看出,这个版本的LeakCanary采用了MAT对内存信息进行分析,并生成结果。其中在分析时,分为findLeakingReference与findLeakTrace来查找泄漏的引用与轨迹,根据GCRoot开始按树形结构依次建议当前引用的轨迹信息。


通过上述分析,*终得出的结果为:

1. Activity检测机制是什么?

答: 通过application.registerActivityLifecycleCallbacks来绑定Activity生命周期的监听,从而监控所有Activity; 在Activity执行onDestroy时,开始检测当前页面是否存在内存泄漏,并分析结果。因此,如果想要在不同的地方都需要检测是否存在内存泄漏,需要手动添加。

2. 内存泄漏检测机制是什么?

答: KeyedWeakReference与ReferenceQueue联合使用,在弱引用关联的对象被回收后,会将引用添加到ReferenceQueue;清空后,可以根据是否继续含有该引用来判定是否被回收;判定回收, 手动GC, 再次判定回收,采用双重判定来确保当前引用是否被回收的状态正确性;如果两次都未回收,则确定为泄漏对象。

3. 内存泄漏轨迹的生成过程 ?

答: 该版本采用eclipse.Mat来分析泄漏详细,从GCRoot开始逐步生成引用轨迹。

 

通过整篇文章分析,你还在疑惑么?

LeakCanary 原理浅析

LeakCanary 原理浅析

前言

提到Java语言的特点,无论是教科书还是程序员一般都会罗列出面向对象、可移植性及安全等特点。但如果你是一位刚从C/C++转到Java的程序员,对Java语言的特性除了面向对象之外,*外直接的应当是在Java虚拟机(JVM)在内存管理方面给我们变成带来的便利。JVM的这一大特性使Java程序员从繁琐的内存管理工作中得到了一定解放,但是JVM的这个特点的实现也是有代价的,并且它也并非万能。因此如果一个编程习惯不好的Java程序员如果完全将内存回收寄希望于JVM,那么OOM(Out Of Memory)就已经悄悄潜伏在了他的程序之中。

Android应用基于Java实现,因此它也将Java的优缺点继承了过来。相对来说,移动设备对于内存问题更为敏感,程序在申请一定的内存但又没有及时得到释放后就很容易发生OOM而导致crash。因此Android程序员开发过程中一般都会定时排查自己程序中可能出现的这些雷点,尽可能地避免因为crash问题而影响用户体验。
简介

目前Java程序*常用的内存分析工具应该是MAT(Memory Analyzer Tool),它是一个Eclipse插件,同时也有单独的RCP客户端,也可以通过官网的SVN下载到它的源码并编译成jar包。LeakCanary本质上就是一个基于MAT进行Android应用程序内存泄漏自动化检测的的开源工具,通过集成这个工具代码到自己的Android工程当中就能够在程序调试开发过程中通过一个漂亮的界面(如下图)随时发现和定位内存泄漏问题,而不用每次在开发流程中都抽出专人来进行内存泄漏问题检测,*大地方便了Android应用程序的开发。源码地址

这里写图片描述

假如你现在想集成LeakCanary到自己的工程中,那么你只需要做以下工作:

在gradle文件中引入依赖:

dependencies {
debugImplementation ‘com.squareup.leakcanary:leakcanary-android:1.6.1’
releaseImplementation ‘com.squareup.leakcanary:leakcanary-android-no-op:1.6.1’
// Optional, if you use support library fragments:
debugImplementation ‘com.squareup.leakcanary:leakcanary-support-fragment:1.6.1’


debugCompile ‘com.squareup.leakcanary:leakcanary-android:1.6.1’
releaseCompile ‘com.squareup.leakcanary:leakcanary-android-no-op:1.6.1’
}

在项目的Application 类中添加

public class ExampleApplication extends Application {

@Override public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
LeakCanary.install(this);
// Normal app init code…
}
}

图解

此章先自顶向下,用图简化,不落代码陷阱,不入细节迷障。理清思路之后,我们再踏入源码的海洋,这样理解会更加清晰。

问:有没有一种简单直接且能有效定位内存泄漏位置的方法呢?

答:有,那就是 LeakCanary 。我们可以简单地人为:将一个 App 作为输入,通过 LeakCanary 检测后,就会得到内存泄漏位置结果(如果存在的话)。

这里写图片描述

知其然知其所以然,LeakCanary 如此强大实用,那么:LeakCanary 是怎么实现的?

Android 应用的整个生命周期由其组件的生命周期组成,如下图中所示。用户使用应用的过程中,在不同界面之间跳转,每个界面都经历着”生死“的转换,可在此建立检测点。Activity/Fragment 都有 onDestory() 回调方法, 进入此方法后,Activity/Fragment生命周期结束,应该被回收。

这里写图片描述

然后我们需要解决:如何得到未被回收的对象。ReferenceQueue+WeakReference+手动调用 GC可实现这个需求。

WeakReference 创建时,传入一个 ReferenceQueue 对象。
当被WeakReference 引用的对象的生命周期结束,一旦被 GC 检查到,GC 将会把该对象添加到 ReferenceQueue 中,待ReferenceQueue处理。
当 GC 过后对象一直不被加入 ReferenceQueue,它可能存在内存泄漏。

找到了未被回收的对象,如何确认是否真的内存泄漏?这里可以将问题转换为:未被回收的对象,是否被其他对象引用?找出其*短引用链。VMDebug + HAHA 完成需求。

VM 会有堆内各个对象的引用情况,并能以hprof文件导出。
HAHA 是一个由 square 开源的 Android 堆分析库,分析 hprof 文件生成Snapshot对象。Snapshot用以查询对象的*短引用链。

这里写图片描述

找到*短引用链后,定位问题,排查代码将会事半功倍。
如下泳道图分析, LeakCanary 各个模块如何配合达到检测目的。

这里写图片描述
深入源码分析
检测原理
监听

在Android中,当一个Activity走完onDestroy生命周期后,说明该页面已经被销毁了,应该被系统GC回收。通过Application.registerActivityLifecycleCallbacks()方法注册Activity生命周期的监听,每当一个Activity页面销毁时候,获取到这个Activity去检测这个Activity是否真的被系统GC。
检测

当获取了待分析的对象后,需要确定这个对象是否产生了内存泄漏。

通过WeakReference + ReferenceQueue来判断对象是否被系统GC回收,WeakReference 创建时,可以传入一个 ReferenceQueue 对象。当被 WeakReference 引用的对象的生命周期结束,一旦被 GC 检查到,GC 将会把该对象添加到 ReferenceQueue 中,待ReferenceQueue处理。当 GC 过后对象一直不被加入 ReferenceQueue,它可能存在内存泄漏。

当我们初步确定待分析对象未被GC回收时候,手动触发GC,二次确认。
分析

分析这块使用了Square的另一个开源库haha,利用它获取当前内存中的heap堆信息的快照snapshot,然后通过待分析对象去snapshot里面去查找强引用关系。
源码
监听

直接从LeakCanary.install()方法开始看。

/**
* Creates a {@link RefWatcher} that works out of the box, and starts watching activity
* references (on ICS+).
*/
public static RefWatcher install(Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}

/**
* Builder to create a customized {@link RefWatcher} with appropriate Android defaults.
*/
public static AndroidRefWatcherBuilder refWatcher(Context context) {
return new AndroidRefWatcherBuilder(context);
}

install()方法中首先实例化了一个AndroidRefWatcherBuilder类。
然后使用listenerServiceClass()方法设置了DisplayLeakService类,这个类用于分析内存泄漏结果信息,然后发送通知给用户。

public final class AndroidRefWatcherBuilder extends RefWatcherBuilder<AndroidRefWatcherBuilder> {

/**
* Sets a custom {@link AbstractAnalysisResultService} to listen to analysis results. This
* overrides any call to {@link #heapDumpListener(HeapDump.Listener)}.
*/
public AndroidRefWatcherBuilder listenerServiceClass(
Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
}

}

public class RefWatcherBuilder<T extends RefWatcherBuilder<T>> {

public final T heapDumpListener(HeapDump.Listener heapDumpListener) {
this.heapDumpListener = heapDumpListener;
return self();
}

}

然后调用excludedRefs()方法设置添加一些白名单,通俗来讲就是不要误伤了友军。在AndroidExcludedRefs类中以枚举的形式定义了忽略列表信息,如果这些列表中的类发生了内存泄漏,并不会显示出来,同时HeapAnalyzer在计算到GC roots的强引用路径,也会忽略这些类。如果你想自己的某个类泄漏了,LeakCanary不提示,就加到这个类中。

public enum AndroidExcludedRefs {
ACTIVITY_CLIENT_RECORD__NEXT_IDLE(VERSION.SDK_INT >= 19 && VERSION.SDK_INT <= 21) {
void add(Builder excluded) {
excluded.instanceField(“android.app.ActivityThread$ActivityClientRecord”, “nextIdle”).reason(“Android AOSP sometimes keeps a reference to a destroyed activity as a nextIdle client record in the android.app.ActivityThread.mActivities map. Not sure what\’s going on there, input welcome.”);
}
},
SPAN_CONTROLLER(VERSION.SDK_INT <= 19) {
void add(Builder excluded) {
String reason = “Editor inserts a special span, which has a reference to the EditText. That span is a NoCopySpan, which makes sure it gets dropped when creating a new SpannableStringBuilder from a given CharSequence. TextView.onSaveInstanceState() does a copy of its mText before saving it in the bundle. Prior to KitKat, that copy was done using the SpannableString constructor, instead of SpannableStringBuilder. The SpannableString constructor does not drop NoCopySpan spans. So we end up with a saved state that holds a reference to the textview and therefore the entire view hierarchy & activity context. Fix: https://github.com/android/platform_frameworks_base/commit/af7dcdf35a37d7a7dbaad7d9869c1c91bce2272b . To fix this, you could override TextView.onSaveInstanceState(), and then use reflection to access TextView.SavedState.mText and clear the NoCopySpan spans.”;
excluded.instanceField(“android.widget.Editor$EasyEditSpanController”, “this$0”).reason(reason);
excluded.instanceField(“android.widget.Editor$SpanController”, “this$0″).reason(reason);
}
},

}

*后调用了buildAndInstall()方法,创建了一个RefWatcher对象并返回了,这个对象用于检测是否有对象未被回收导致内存泄漏,后续会详细讲解这块。

/**
* Creates a {@link RefWatcher} instance and starts watching activity references (on ICS+).
*/
public RefWatcher buildAndInstall() {
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
LeakCanary.enableDisplayLeakActivity(context);
ActivityRefWatcher.install((Application) context, refWatcher);
}
return refWatcher;
}

因为分析泄漏信息是在另一个进程,如果判断出当前Application启动是在分析泄漏信息的进程中,就返回DISABLED,不去执行后续的初始化操作。在这里LeakCanary提供了一个很好的方式去区分启动是否在应用主进程,可以作为参考。

如果发现是在应用主进程中,就会进行一些初始化操作。

LeakCanary.enableDisplayLeakActivity(context);这个是调用PackageManager将DisplayLeakActivity设置为可用。

public static void enableDisplayLeakActivity(Context context) {
setEnabled(context, DisplayLeakActivity.class, true);
}

从配置文件中可以看见LeakCanary的这几个服务都是在新的进程中运行的,DisplayLeakActivity默认是不可用android:enabled=”false”,这样才能在一开始未存在内存泄漏时候,隐藏LeakCanary图标的。

<application>
<service
android:name=”.internal.HeapAnalyzerService”
android:process=”:leakcanary”
android:enabled=”false”/>
<service
android:name=”.DisplayLeakService”
android:process=”:leakcanary”
android:enabled=”false”/>
<activity
android:theme=”@style/leak_canary_LeakCanary.Base”
android:name=”.internal.DisplayLeakActivity”
android:process=”:leakcanary”
android:enabled=”false”
android:label=”@string/leak_canary_display_activity_label”
android:icon=”@drawable/leak_canary_icon”
android:taskAffinity=”com.squareup.leakcanary.${applicationId}”>
<intent-filter>
<action android:name=”android.intent.action.MAIN”/>
<category android:name=”android.intent.category.LAUNCHER”/>
</intent-filter>
</activity>
<activity
android:theme=”@style/leak_canary_Theme.Transparent”
android:name=”.internal.RequestStoragePermissionActivity”
android:process=”:leakcanary”
android:taskAffinity=”com.squareup.leakcanary.${applicationId}”
android:enabled=”false”
android:excludeFromRecents=”true”
android:icon=”@drawable/leak_canary_icon”
android:label=”@string/leak_canary_storage_permission_activity_label”/>
</application>

紧接着执行了ActivityRefWatcher.install((Application) context, refWatcher);,这里将实例化的RefWatcher当做入参传入,同时对Activity的生命周期进行了监听。

public static void install(Application application, RefWatcher refWatcher) {
(new ActivityRefWatcher(application, refWatcher)).watchActivities();
}

public void watchActivities() {
// Make sure you don’t get installed twice.
stopWatchingActivities();
application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
}

public void stopWatchingActivities() {
application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks);
}

void onActivityDestroyed(Activity activity) {
this.refWatcher.watch(activity);
}

private final ActivityLifecycleCallbacks lifecycleCallbacks = new ActivityLifecycleCallbacks() {
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}

public void onActivityStarted(Activity activity) {
}

public void onActivityResumed(Activity activity) {
}

public void onActivityPaused(Activity activity) {
}

public void onActivityStopped(Activity activity) {
}

public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}

public void onActivityDestroyed(Activity activity) {
ActivityRefWatcher.this.onActivityDestroyed(activity);
}
};

可以看到为了保证不初始化两次监听,先移除了一次,然后再次添加了监听。每次当Activity执行完onDestroy生命周期,LeakCanary就会获取到这个Activity,然后对它进行分析,查看是否存在内存泄漏。

RefWatcher类中有一些成员变量,解释一下它们的作用。

watchExecutor 确保分析任务操作是在主线程进行的,同时默认延迟5秒执行分析任务,留时间给系统GC
debuggerControl debug控制中心
gcTrigger 内部调用Runtime.getRuntime().gc(),手动触发系统GC
heapDumper 用于创建.hprof文件,用于储存heap堆的快照,可以获知程序的哪些部分正在使用大部分的内存
heapDumpListener 分析结果完成后,会告诉这个监听者
excludedRefs 白名单,分析内存泄漏忽略的名单

public final class RefWatcher {
public static final RefWatcher DISABLED = (new RefWatcherBuilder()).build();
private final WatchExecutor watchExecutor;
private final DebuggerControl debuggerControl;
private final GcTrigger gcTrigger;
private final HeapDumper heapDumper;
private final Set<String> retainedKeys;
private final ReferenceQueue<Object> queue;
private final Listener heapdumpListener;
private final ExcludedRefs excludedRefs;

}

判断是否存在内存泄漏

从上面Activity生命周期监听回调可以看见,每次当Activity执行完onDestroy生命周期,会调用RefWatcher去分析是否存在内存泄漏。

/**
* Identical to {@link #watch(Object, String)} with an empty string reference name.
*
* @see #watch(Object, String)
*/
public void watch(Object watchedReference) {
watch(watchedReference, “”);
}

/**
* Watches the provided references and checks if it can be GCed. This method is non blocking,
* the check is done on the {@link WatchExecutor} this {@link RefWatcher} has been constructed
* with.
*
* @param referenceName An logical identifier for the watched object.
*/
public void watch(Object watchedReference, String referenceName) {
if (this == DISABLED) {
return;
}
checkNotNull(watchedReference, “watchedReference”);
checkNotNull(referenceName, “referenceName”);
final long watchStartNanoTime = System.nanoTime();
String key = UUID.randomUUID().toString();
retainedKeys.add(key);
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);

ensureGoneAsync(watchStartNanoTime, reference);
}

checkNotNull()方法可以不用管,用来判断对象是否为null。在这里声明了一个弱引用,将Activity放入,然后调用了ensureGoneAsync()方法。

private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override
public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}

先生成一个随机数用作key放在retainedKeys容器里面,用来区分待分析对象是否被回收,然后使用watchExecutor去调度分析任务,主要有两点,一保证分析是在主线程进行,二延迟五秒分析内存泄漏,给系统GC时间。这部分有兴趣可以深入去看一下,与分析的主流程关系不大,我们接下继续看。

Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime – watchStartNanoTime);

removeWeaklyReachableReferences();

if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
if (gone(reference)) {
return DONE;
}
gcTrigger.runGc();
removeWeaklyReachableReferences();
if (!gone(reference)) {
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap – gcStartNanoTime);

File heapDumpFile = heapDumper.dumpHeap();
if (heapDumpFile == RETRY_LATER) {
// Could not dump the heap.
return RETRY;
}
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() – startDumpHeap);
heapdumpListener.analyze(
new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
gcDurationMs, heapDumpDurationMs));
}
return DONE;
}

private void removeWeaklyReachableReferences() {
// WeakReferences are enqueued as soon as the object to which they point to becomes weakly
// reachable. This is before finalization or garbage collection has actually happened.
KeyedWeakReference ref;
while ((ref = (KeyedWeakReference) queue.poll()) != null) {
retainedKeys.remove(ref.key);
}
}

private boolean gone(KeyedWeakReference reference) {
return !this.retainedKeys.contains(reference.key);
}

在这里首先执行removeWeaklyReachableReferences()尝试着从弱引用的队列中获取待分析对象,如果不为空,那么说明已经被系统回收了,就将retainedKeys中对应的key去掉。如果被系统回收了,直接就返回DONE;如果没有被系统回收,可能存在内存泄漏,为了保证结果的准确性,调用gcTrigger.runGc();,手动触发系统GC,然后再尝试移除待分析对象,如果还存在,说明存在内存泄漏。

public void runGc() {
Runtime.getRuntime().gc();
this.enqueueReferences();
System.runFinalization();
}

private void enqueueReferences() {
try {
Thread.sleep(100L);
} catch (InterruptedException var2) {
throw new AssertionError();
}
}

手动触发系统GC后,enqueueReference()方法通过沉睡100毫秒给系统GC时间,System.runFinalization()是强制调用已经失去引用的对象的finalize方法。

确定有内存泄漏后,调用heapDumper.dumpHeap()生成.hprof文件目录,然后回调heapdumpListener监听,这个监听者具体实现是ServiceHeapDumpListener类,会回调到analyze()方法。

public void analyze(HeapDump heapDump) {
Preconditions.checkNotNull(heapDump, “heapDump”);
HeapAnalyzerService.runAnalysis(this.context, heapDump, this.listenerServiceClass);
}

HeapDump是个model类,里面用于储存一些分析类强引用路径的需要的信息。HeapAnalyzerService.runAnalysis方法是发送了一个Intent,启动了HeapAnalyzerService服务,这个服务是IntentService。

public static void runAnalysis(Context context, HeapDump heapDump, Class<? extends AbstractAnalysisResultService> listenerServiceClass){
Intent intent = new Intent(context, HeapAnalyzerService.class);
intent.putExtra(“listener_class_extra”, listenerServiceClass.getName());
intent.putExtra(“heapdump_extra”, heapDump);
context.startService(intent);
}

启动Service后,会在onHandleIntent分析,找到内存泄漏对象的引用关系。

protected void onHandleIntent(Intent intent) {
if(intent == null) {
CanaryLog.d(“HeapAnalyzerService received a null intent, ignoring.”, new Object[0]);
} else {
String listenerClassName = intent.getStringExtra(“listener_class_extra”);
HeapDump heapDump = (HeapDump)intent.getSerializableExtra(“heapdump_extra”);
HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs);
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}
}

找到引用关系

启动分析泄漏的服务后,会实例化一个HeapAnalyzer对象,然后调用它的checkForLeak()方法来分析*终得到结果。

public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) {
long analysisStartNanoTime = System.nanoTime();
if(!heapDumpFile.exists()) {
IllegalArgumentException e1 = new IllegalArgumentException(“File does not exist: ” + heapDumpFile);
return AnalysisResult.failure(e1, this.since(analysisStartNanoTime));
} else {
try {
MemoryMappedFileBuffer e = new MemoryMappedFileBuffer(heapDumpFile);
HprofParser parser = new HprofParser(e);
Snapshot snapshot = parser.parse();
this.deduplicateGcRoots(snapshot);
Instance leakingRef = this.findLeakingReference(referenceKey, snapshot);
return leakingRef == null?AnalysisResult.noLeak(this.since(analysisStartNanoTime)):this.findLeakTrace(analysisStartNanoTime, snapshot, leakingRef);
} catch (Throwable var9) {
return AnalysisResult.failure(var9, this.since(analysisStartNanoTime));
}
}
}

这里用到了Square的另一个库haha,哈哈哈哈哈,名字真的就是叫这个,开源地址

首先用HprofParser类获取到内存中的heap堆快照,然后对调用deduplicateGcRoots()对快照做了去重处理,去除一些重复的强引用关系。findLeakingReference()方法就是拿到待分析的类,去快照里面找引用关系,并将结果返回。

HeapAnalyzerService服务拿到分析结果后,调用了AbstractAnalysisResultService.sendResultToListener()方法,这个方法启动了另一个服务。

public static void sendResultToListener(Context context, String listenerServiceClassName, HeapDump heapDump, AnalysisResult result) {
Class listenerServiceClass;
try {
listenerServiceClass = Class.forName(listenerServiceClassName);
} catch (ClassNotFoundException var6) {
throw new RuntimeException(var6);
}

Intent intent = new Intent(context, listenerServiceClass);
intent.putExtra(“heap_dump_extra”, heapDump);
intent.putExtra(“result_extra”, result);
context.startService(intent);
}

listenerServiceClassName就是开始LeakCanary.install方法传入的DisplayLeakService类,它本身也是个IntentService服务。

protected final void onHandleIntent(Intent intent) {
HeapDump heapDump = (HeapDump)intent.getSerializableExtra(“heap_dump_extra”);
AnalysisResult result = (AnalysisResult)intent.getSerializableExtra(“result_extra”);

try {
this.onHeapAnalyzed(heapDump, result);
} finally {
heapDump.heapDumpFile.delete();
}

}

服务启动后,会调用自身的onHeapAnalyzed方法

protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) {
String leakInfo = LeakCanary.leakInfo(this, heapDump, result, true);
CanaryLog.d(“%s”, new Object[]{leakInfo});
boolean resultSaved = false;
boolean shouldSaveResult = result.leakFound || result.failure != null;
if(shouldSaveResult) {
heapDump = this.renameHeapdump(heapDump);
resultSaved = this.saveResult(heapDump, result);
}

PendingIntent pendingIntent;
String contentTitle;
String contentText;

// 设置消息通知的 pendingIntent/contentTitle/contentText

int notificationId1 = (int)(SystemClock.uptimeMillis() / 1000L);
LeakCanaryInternals.showNotification(this, contentTitle, contentText, pendingIntent, notificationId1);
this.afterDefaultHandling(heapDump, result, leakInfo);
}

在这个方法中对判断是否需要将内存泄漏信息存到本地,如果需要就存到本地,然后设置消息通知的基本信息,通过LeakCanaryInternals.showNotification方法调用系统自身的通知栏通知,告诉用户应用有内存泄漏。来个图:

这里写图片描述

至此所有LeakCanary的检测过程通过源码的形式都梳理了一遍。
总结

它的基本工作原理如下:

RefWatcher.watch() 创建一个 KeyedWeakReference 到要被监控的对象。
然后在后台线程检查引用是否被清除,如果没有,调用GC。
如果引用还是未被清除,把 heap 内存 dump 到 APP 对应的文件系统中的一个 .hprof 文件中。
在另外一个进程中的 HeapAnalyzerService 有一个 HeapAnalyzer 使用HAHA 解析这个文件。
得益于唯一的 reference key, HeapAnalyzer 找到 KeyedWeakReference,定位内存泄漏。
HeapAnalyzer 计算 到 GC roots 的*短强引用路径,并确定是否是泄漏。如果是的话,建立导致泄漏的引用链。
引用链传递到 APP 进程中的 DisplayLeakService, 并以通知的形式展示出来。

总的来说,LeakCanary有如下几个明显优点:

针对Android Activity组件完全自动化的内存泄漏检查。
可定制一些行为(dump文件和leaktrace对象的数量、自定义例外、分析结果的自定义处理等)。
集成到自己工程并使用的成本很低。
友好的界面展示和通知

并行编程之条件变量(posix condition variables)

在整理Java LockSupport.park()的东东,看到了个”Spurious wakeup”,重新梳理下。

首先来个《UNIX环境高级编程》里的例子:

#include <pthread.h>
struct msg {
struct msg *m_next;
/* … more stuff here … */
};
struct msg *workq;
pthread_cond_t qready = PTHREAD_COND_INITIALIZER;
pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER;
void process_msg(void) {
struct msg *mp;
for (;;) {
pthread_mutex_lock(&qlock);
while (workq == NULL)
pthread_cond_wait(&qready, &qlock);
mp = workq;
workq = mp->m_next;
pthread_mutex_unlock(&qlock);
/* now process the message mp */
}
}
void enqueue_msg(struct msg *mp) {
pthread_mutex_lock(&qlock);
mp->m_next = workq;
workq = mp;
pthread_mutex_unlock(&qlock);
pthread_cond_signal(&qready);
}

一个简单的消息生产者和消费者的代码。它们之间用condition同步。
这个代码*容易让人搞混的是process_msg函数里的pthread_mutex_lock 和 pthread_mutex_unlock 是一对函数调用,前面加锁,后面解锁。的确,是加锁解锁,但是它们两不是一对的。它们的另一半在pthread_cond_wait函数里。

pthread_cond_wait函数可以认为它做了三件事:
把自身线程放到condition的等待队列里,把mutex解锁;
等待被唤醒(当其它线程调用pthread_cond_signal或者pthread_cond_broadcast时);
被唤醒之后,对metex加锁,再返回。
mutex和condition实际上是绑定在一起的,一个condition只能对应一个mutex。在Java的代码里,Condition对象只能通过lock.newCondition()的函数来获取。

Spurious wakeup
所谓的spurious wakeup,指的是一个线程调用pthread_cond_signal(),却有可能不止一个线程被唤醒。为什么会出现这种情况?wiki和其它的一些文档都只是说在多核的情况下,简化实现允许出现这种spurious wakeup。。

在man文档里给出了一个可能的实现,然后解析为什么会出现。

假定有三个线程,线程A正在执行pthread_cond_wait,线程B正在执行pthread_cond_signal,线程C正准备执行pthread_cond_wait函数。

pthread_cond_wait(mutex, cond):
value = cond->value; /* 1 */
pthread_mutex_unlock(mutex); /* 2 */
pthread_mutex_lock(cond->mutex); /* 10 */
if (value == cond->value) { /* 11 */
me->next_cond = cond->waiter;
cond->waiter = me;
pthread_mutex_unlock(cond->mutex);
unable_to_run(me);
} else
pthread_mutex_unlock(cond->mutex); /* 12 */
pthread_mutex_lock(mutex); /* 13 */

pthread_cond_signal(cond):
pthread_mutex_lock(cond->mutex); /* 3 */
cond->value++; /* 4 */
if (cond->waiter) { /* 5 */
sleeper = cond->waiter; /* 6 */
cond->waiter = sleeper->next_cond; /* 7 */
able_to_run(sleeper); /* 8 */
}
pthread_mutex_unlock(cond->mutex); /* 9 */

线程A执行了第1,2步,这时它释放了mutex,然后线程B拿到了这个mutext,并且pthread_cond_signal函数时执行并返回了。于是线程B就是一个所谓的“spurious wakeup”。
为什么pthread_cond_wait函数里一进入,就释放了mutex?没有找到什么解析。。

查看了glibc的源代码,大概可以看出上面的一些影子,但是太复杂了,也没有搞明白为什么。。

/build/buildd/eglibc-2.19/nptl/pthread_cond_wait.c
/build/buildd/eglibc-2.19/nptl/pthread_cond_signal.c
不过从上面的解析,可以发现《UNIX高级编程》里的说明是错误的(可能是因为太久了)。

The caller passes it locked to the function, which then atomically places the calling thread on the list of threads waiting for the condition and unlocks the mutex.

上面的伪代码,一进入pthread_cond_wait函数就释放了mutex,明显和书里的不一样。

wait morphing优化
在《UNIX环境高级编程》的示例代码里,是先调用pthread_mutex_unlock,再调用pthread_cond_signal。
void enqueue_msg(struct msg *mp) {
pthread_mutex_lock(&qlock);
mp->m_next = workq;
workq = mp;
pthread_mutex_unlock(&qlock);
pthread_cond_signal(&qready);
}
有的地方给出的是先调用pthread_cond_signal,再调用pthread_mutex_unlock:
void enqueue_msg(struct msg *mp) {
pthread_mutex_lock(&qlock);
mp->m_next = workq;
workq = mp;
pthread_cond_signal(&qready);
pthread_mutex_unlock(&qlock);
}
先unlock再signal,这有个好处,就是调用enqueue_msg的线程可以再次参与mutex的竞争中,这样意味着可以连续放入多个消息,这个可能会提高效率。类似Java里ReentrantLock的非公平模式。
网上有些文章说,先singal再unlock,有可能会出现一种情况是被singal唤醒的线程会因为不能马上拿到mutex(还没被释放),从而会再次休眠,这样影响了效率。从而会有一个叫“wait morphing”优化,就是如果线程被唤醒但是不能获取到mutex,则线程被转移(morphing)到mutex的等待队列里。

但是我查看了下glibc的源代码,貌似没有发现有这种“wait morphing”优化。

man文档里提到:

The pthread_cond_broadcast() or pthread_cond_signal() functions may be called by a thread whether or not it currently owns the mutex that threads calling pthread_cond_wait() or pthread_cond_timedwait() have associated with the condition variable during their waits; however, if predictable scheduling behavior is required, then that mutex shall be locked by the thread calling pthread_cond_broadcast() or pthread_cond_signal().

可见在调用singal之前,可以不持有mutex,除非是“predictable scheduling”,可预测的调度行为。这种可能是实时系统才有这种严格的要求。
为什么要用while循环来判断条件是否成立?
while (workq == NULL)
pthread_cond_wait(&qready, &qlock);
而不用if来判断?

if (workq == NULL)
pthread_cond_wait(&qready, &qlock);
一个原因是spurious wakeup,但即使没有spurious wakeup,也是要用While来判断的。
比如线程A,线程B在pthread_cond_wait函数中等待,然后线程C把消息放到队列里,再调用pthread_cond_broadcast,然后线程A先获取到mutex,处理完消息完后,这时workq就变成NULL了。这时线程B才获取到mutex,那么这时实际上是没有资源供线程B使用的。所以从pthread_cond_wait函数返回之后,还是要判断条件是否成功,如果成立,再进行处理。

pthread_cond_signal和pthread_cond_broadcast
在这篇文章里,http://www.cppblog.com/Solstice/archive/2013/09/09/203094.html

给出的示例代码7里,认为调用pthread_cond_broadcast来唤醒所有的线程是比较好的写法。但是我认为pthread_cond_signal和pthread_cond_broadcast是两个不同东东,不能简单合并在同一个函数调用。只唤醒一个效率和唤醒全部等待线程的效率显然不能等同。典型的condition是用CLH或者MCS来实现的,要通知所有的线程,则要历遍链表,显然效率降低。另外,C++11里的condition_variable也提供了notify_one函数。

http://en.cppreference.com/w/cpp/thread/condition_variable/notify_one

mutex,condition是不是公平(fair)的?
这个在参考文档里没有说明,在网上找了些资料,也没有什么明确的答案。

我写了个代码测试,发现mutex是公平的。condition的测试结果也是差不多。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

volatile int mutexCount = 0;
void mutexFairTest(){
int localCount = 0;
while(1){
pthread_mutex_lock(&lock);
__sync_fetch_and_add(&mutexCount, 1);
localCount += 1;
if(mutexCount > 100000000){
break;
}
pthread_mutex_unlock(&lock);
}
pthread_mutex_unlock(&lock);
printf(“localCount:%d\n”, localCount);
}

int main() {
pthread_mutex_lock(&lock);
pthread_create(new pthread_t, NULL, (void * (*)(void *))&mutexFairTest, NULL);
pthread_create(new pthread_t, NULL, (void * (*)(void *))&mutexFairTest, NULL);
pthread_create(new pthread_t, NULL, (void * (*)(void *))&mutexFairTest, NULL);
pthread_create(new pthread_t, NULL, (void * (*)(void *))&mutexFairTest, NULL);
pthread_create(new pthread_t, NULL, (void * (*)(void *))&mutexFairTest, NULL);
pthread_create(new pthread_t, NULL, (void * (*)(void *))&mutexFairTest, NULL);
pthread_mutex_unlock(&lock);

sleep(100);
}
输出结果是:
localCount:16930422
localCount:16525616
localCount:16850294
localCount:16129844
localCount:17329693
localCount:16234137
还特意在一个单CPU的虚拟机上测试了下。输出的结果差不多。操作系统是ububtu14.04。

连续调用pthread_cond_signal,会唤醒多少次/多少个线程?
比如线程a,b 在调用pthread_cond_wait之后等待,然后线程c, d同时调用pthread_cond_signal,那么a, b线程是否都能被唤醒?

会不会出现c, d, a 这种调用顺序,然后b一直在等待,然后死锁了?

根据文档:

The pthread_cond_signal() function shall unblock at least one of the threads that are blocked on the specified condition variable cond (if any threads are blocked on cond).
因此,如果有线程已经在调用pthread_cond_wait等待的情况下,pthread_cond_signal调用至少会唤醒等待中的一个线程。

所以不会出现上面的线程b一直等待的情况。

但是,我们再仔细考虑下:

如何确认线程a, b 调用pthread_cond_wait完成了?还是只是刚切换到内核态?显然是没有办法知道的。

所以,我们平时编程肯定不会写这样的代码,应该是共享变量,在获取到锁之后,再修改变量。这样子来做同步。参考上面《UNIX环境高级编程》的例子。

不过,这个问题也是挺有意思的。

友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速