V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX  ›  kuanat  ›  全部回复第 3 页 / 共 9 页
回复总数  172
1  2  3  4  5  6  7  8  9  
19 天前
回复了 0312birdzhang 创建的主题 奇思妙想 关于打包箱子引发的思考
可以参考这个装箱问题 wiki 上面 Bin_packing_problem

放到现实世界里,搬家这个场景一般都是一个固定大箱子,往里面放各种小箱子的问题。可能还会有更多约束,比如重量,一箱子书换谁都搬不动。所以人靠直觉一般能很快找到一个可行但不一定最优的方案,换作程序里可能就是经验或者启发式算法。

另外大箱子也是可以变形的,比如 50cm 长 30cm 宽,切割一下胶带一封就可以变成 40x40 或者 60x20 来用。
19 天前
回复了 unclemcz 创建的主题 Ubuntu snap 已经在污染 apt
如果你在用 Ubuntu ,同时你又不认可 Canonical 的做法,我建议你考虑一下 Debian ,它是 Ubuntu 的上游发行版。

Debian 的全称是 Debian GNU/Linux ,如果不清楚什么是 GNU 可以看官方 FAQ https://www.debian.org/doc/manuals/debian-faq/basic-defs.en.html#whatisdebian

至于原因,我这里引用一下 Richard Stallman 于 1985 年写的 GNU Manifesto 中的一段话 https://www.gnu.org/gnu/manifesto.html

- Why All Computer Users Will Benefit
Users will no longer be at the mercy of one programmer or company which owns the sources and is in sole position to make changes.
19 天前
回复了 afxcn 创建的主题 Go 编程语言 golang 的 defer 真是个好设计
@lolizeppelin #77

如果只是单纯辩论美丑,我原本没打算回复,但既然你提到了 python 用装饰器来减少代码,那我觉得还是可以讨论一下的。

生命周期管理这个事情的困难在于:资源的分配和释放可能时机不确定,又或者在代码中的位置不同,还可能是多线程的主体不确定。原地释放几乎没有人会写错,而 scope 变得很大的时候,错写或者漏写的几率就会变大。

考虑 with...as 加入到 python 中已经有二十年了,但是装饰器版本的 contextmanager 也就十年,异步安全的版本可能最近才实现。这里有个很现实的问题,作为使用者是更愿意手写 enter/exit 还是愿意手动管理?当 contextmanager 加入的时候,项目允许升级吗?当有异步、多线程需求的时候,开发者能写出正确的版本吗?

所以抛开美丑的主观看法不谈,defer 这种万金油几乎是个 Go 开发者就会写,而 with...as 使用率就要大打折扣。先让人用起来显然是重要地多的事情。这里不抬杠开发者水平高低的问题,defer 是一种兜底机制,当开发者不确定我应该在什么时候释放资源的时候就可以用,而 with...as 即便是装饰器的版本显得繁琐,写好几行肯定比不上写一个单词,这是人性。



之前的回复里我总是反复提,不要单独拿某个关键词来讨论特性,那就以 with...as 来彻底完整做个说明,证明一下为什么这样做不合适。

C/Python/Java 由于诞生较早,都是先有的实现,然后反过来总结出来的标准。比较新的语言,都是设计先于实现,终归会从之前语言中总结经验教训,所以不仅仅是 Go ,你会发现 C#/Rust/Kotlin 这些“现代”语言往往有些共性,共享的是新的设计理念。



这里 defer/with...as 反映的其实是对异常处理这件事的哲学思考不同。

传统语言中,一方面异常是不区分错误和 Panic 的,另一方面异常又与控制流强耦合,try...catch 实质是隐式的 goto 指令。

现代语言几乎都放弃了这样的思路,Panic 要立即终止,而错误是可以处理的。代码层面也尽量避免隐式跳转。这样的改变是大趋势,原因是大家都从 C 语言中汲取了足够的教训,隐式控制流是有害的( hidden control flow considered harmful ),特别是与 C 的宏机制一结合,人脑 debug 几乎变得不可能。

反过来,显式控制流的优点是显而易见的,一方面极大提高了代码的可读性以及可靠性,所见即所得,不存在那些写在其他地方的代码被意外调用执行的情况;另一方面对于并行编程的支持变得更加容易。

如果拿 Go 来举例可能没有说服力,我简单说下 Rust 和 Kotlin 。

- Rust 没有 exception ,只有 recoverable/unrecoverable 错误,类型分别为 Result<T, E> 和 panic! 只有后者是宏实现的跳转

- Kotlin 保留了 try 关键词,但 try 是表达式而非(控制流)语句,也就是说 try 可以返回(错误)值

在减轻开发者心智负担方面,新的设计哲学可谓是殊途同归。这个进步主要体现在工程化层面,当语言不区分错误和 Panic 的时候,开发者会主动选择最无脑的方式,也就是随意抛出集中处理。现代语言中砍掉了这样的机制,反过来倒逼开发者思考异常处理的本质,从而间接提高代码的可复用性,以及程序的可靠性。



回到 with...as 的问题上,Python 的错误处理还是传统的控制流模式。换句话说,就算语法糖写出花来,它的核心仍旧是基于 enter/exit 的对于 RAII 的模仿。由于 Python 没有 RAII ,这种模仿的结果就是一定要手动处理 scope 的问题。

先说一个比较接近最终方案的提案版本:

with EXPR:
____BLOCK

这个版本看上去相当美好,可是没多久就被枪毙了。原因有两个,一是 with 只能是 statement 而不能是表达式(需要同时支持 with VAR = EXPR: 这样的语法,VAR = EXPR 赋值本身也是合法的 EXPR );二是 EXPR 自身有可能再次抛出异常,或者包含 break/continue 等其他控制流指令,结果会导致 scope 失效。

第二条麻烦一下解释器也不是不可以,多维护一个基于栈的 trackback 机制。但第一条的影响是致命的:

with VAR = EXPR:
____BLOCK

想要对以上形式写装饰器,__exit__ 一定要在 VAR 上有定义,这就失去了装饰器省代码的意义。

为了不让 VAR 获得 EXPR 的赋值,最终实现的版本用 with EXPR [as VAR]: 这样的形式。相关实现细节 PEP 都能搜到,这里就不继续展开了。

即便是用上装饰器,代码大概是这样的:

@contextmanager
def open(file):
____f = open(file)
____try:
________yield f
____finally:
____f.close()

写这么多只是为了实现:可以在 with scope 里,不用单独写 f.close()。

装饰器的实现晚于 with...as 很久才进标准库,核心原因就是前面提到的隐式控制流。装饰器本身

客观地说,Python 本来就是试错型快速迭代偏好的语言,with...as 加上迭代器的语法糖的作用非常有限。它起到的主要作用是,当你知道什么时候该写 f.close() 的时候( scope 边界清晰),不用你再写了(自定义类型还是要写装饰器)。但是解决不了辅助开发者判断,什么时候该写的问题,你无法依赖解释器帮你兜底。



尽管在站队好或者不好的事情上,网友可能会支持你也可能会反对你,但不代表大家都是出于相同的推理逻辑。在语言设计这么关键的问题上,一半是妥协一半是取舍,单拿出来一个点做比较最终只会变成屁股之争。
19 天前
回复了 afxcn 创建的主题 Go 编程语言 golang 的 defer 真是个好设计
@lolizeppelin #75

确实我说 Go 好不好对 Go 和其他编程语言没有什么意义,但不代表我在这里的回帖没有意义。

很多人觉得我是狂热的吹捧者,我想还是有所误解。这里面有我表述方式的原因,也有读者立场的原因。我在之前的某个帖子里表达过,我大多数回复的目的是“创造价值”,因为我从 V2EX 学到了很多,我也想把我学到的反馈给社区。Go 的主题比较多更多是因为它有一定流行度却又不那么流行,在我日常工作里,主流的语言应用比例里 Go 反倒没有那么高。

关于 python 的 with 语法,我有个问题,你怎么看自定义对象需要手写 __enter__ __exit__ 的?

单独拿 with 出来,配合 python 的缩进,起到了看上去很美的效果。但是没有 RAII ,为了实现 scope ,又要回到手写析构的路上。

因此我一直都强调,单独拿一个关键词一个特性出来比较是不合适的。
19 天前
回复了 KinsleyNg 创建的主题 Linux 这个 Linux 的 chrome 硬解就这么难处理吗
@KinsleyNg #9

我是说不推荐 fedora ,ubuntu 什么情况不清楚。非官方打包的只能叫 chromium ,试一下官方源里的吧。
21 天前
回复了 KinsleyNg 创建的主题 Linux 这个 Linux 的 chrome 硬解就这么难处理吗
@KinsleyNg #7

实测 xwayland 有效,wayland 无效。楼上说 24.1 相关补丁才合并,目前 f40/rawhide 都只有 24.0.6 的包。

即便补丁合并了,我还是不建议以 chrome 为主力的时候选择 fedora 。由于版权的缘故,RH 系只能把解码器托管在 rpmfusion ,然后 chromium build 和原版经常不一致。反正 gtk4 在我印象里是没有生效过的。

然后 chromium 只实现了 text-input-v1 协议,这个协议除了 KDE/KWin 根本没人支持,就连 KWin 的支持还是 fcitx5 作者加回去的。所以在 Fedora 上既要 wayland 又想输入法正常,只能是 KDE 版本。而其他发行版可以 gnome/gtk4 曲线救国。

另外我只是用 fedora 但是不用 gnome ,日常主力是 sway ,sway 不支持 text-input-v1 。hyprland 那边是支持的,fedora/hyprland 这个组合可以考虑。

至于 firefox 虽然我常年支持,但是在性能方面弱不少,硬件解码支持大家都一样,但其他方面差距很大。我在另一个帖子里刚好提到了,B 站现在那个弹幕功能,firefox 的硬件加速比较原始,纯 3D overlay 。Chrome 就好很多,把 css3 用 video enhance 单元来加速。
@mayli #25

现代操作系统的显示逻辑是 C/S 架构的,应用程序也就是客户端会申请一块显示区域作为 client area 用于显示(相对应的 host area 一般是标题栏、边框这些)。

没有硬件加速的情况下,客户端区域就是各种界面库 API 或者文字 API 绘制,直接把绘制结果以像素的形式填充到客户端区域。操作系统的窗口合成器( compositor )把桌面上所有应用的显示区域做叠加合成,交给显卡完成最终显示。

有硬件加速的情况下,比如窗口中部分区域播放视频,客户端依旧会声明一块显示区域,但中间挖出一部分,并表示这部分不由客户端负责。窗口合成器也会忽略这一部分(但是还会判断遮挡关系),此时显卡的 framebuffer 就是两部分,一部分是窗口合成器给的去掉视频区域的显示数据,另一部分是视频解码单元给的视频数据。

上个回复里说 3D 其实不准确,因为所有最终的渲染都是走的 3D 单元。在显卡看来,3D 单元就是渲染单元。这个语境下的 2D 一般是指无加速,就是客户端把结果绘制完了,合成器直接拿像素数据,而 3D 有加速就是绘制 API 直接在显示区域上进行操作。这里细节我不是很确定,但大致思路就是浏览器把所有绘制 API 后端都用 3D 指令来实现了,整个浏览器内部就是一个巨大的 3D 显示,skia 文字渲染引擎也是 3D 指令在页面上显示文字。

我上个回复里描述的 3D 渲染行为,是根据风扇狂转推测的浏览器的行为。因为我是 Linux 用户,所以对硬件加速感知很深。每个浏览器都有自己如何用显卡硬件单元完成加速的实现逻辑,最终效率和性能也差别很大。目前 bili 的弹幕功能是基于 css3 的,之前有过基于 canvas 的版本但可能是因为效率问题弃用了。

目前 Firefox/Chrome/Chromium 三个浏览器的表现都是不一样的:

- Firefox 就是我上个帖子里说的模式,会调用视频解码单元,但对于 css3 的加速就很弱。从显卡资源占用上说,比较大可能就是没有优化,直接在透明层上叠加。

- Chrome 官方打包版本对于硬件加速支持很不稳定,有些版本正常有些版本就没了。

- Chromium 主要看发行版适配,官方源会打针对自己发行版的补丁。在解码器和补丁都正常的情况下,效果是最好的。首先视频本身还会走硬件解码,但是 css3 特效并非 3D 透明层,而是调用 video enhance 硬件单元。这个硬件单元的作用原本是用作视频播放去噪点、锐化等功能的,Chromium 的实现里 css3 相当于实时调整原视频,所以大幅降低了 3D 渲染单元的负载。
21 天前
回复了 KinsleyNg 创建的主题 Linux 这个 Linux 的 chrome 硬解就这么难处理吗
@KinsleyNg #5

我刚刚又确认了一下,Google 官方打包的那个版本,确实是没有视频解码硬件加速的。原因不清楚,我是拿 bili 的 av1 做的测试。

我用的 Fedora 官方源的 chromium 测试,是有视频解码硬件加速的。
22 天前
回复了 afxcn 创建的主题 Go 编程语言 golang 的 defer 真是个好设计
@Jirajine #62

我的观点一直都是,在合适的场景使用恰当的工具。在说 Go 好的帖子里这样说,在说 Go 不好的帖子里也这样说。就连这个帖子里,我的观点也是一致的,defer 不能滥用,而 error 处理要换思路。

关于 sum type 我从来没谈过,因为我想不出这样做的原因,我也没有从 Go 的官方文章找到头绪。Null safety/checked initialization 都是用来佐证,语言特性是取舍这一观点的。既然选择了简洁,就要付出代价,Go 没有特别多的选择。

既然你能准确使用这些术语,说明对这些话题有足够的认知,那完全可以阐述你对于语言设计的认识和观点,没有必要归结于个人偏好。
@sakujo #13

不开弹幕的话,就是单纯的扣一块区域,调用硬件解码器。浏览器本身是不负责播放区域的,系统窗口合成器把浏览器渲染的其他部分与视频解码的播放部分进行叠加。这个过程里,显卡的 Render/3D 单元的占用取决于视频是否在播放。暂停的话占用率会立即下降。

如果开启弹幕,弹幕区域等价于在跑一个 3D 应用,这个应用就是在播放区域之上渲染一个透明层,然后把弹幕显示上去,一般这个 3D 应用的帧率会和屏幕刷新率一致。即使视频暂停,这个透明层依旧不会停止运行。(视频暂停会看到旧弹幕飘过,只是不加载新弹幕了。)

CPU 要负责生成弹幕的帧,同时一些类似于避开视频中的人物会加大计算量,因为这个功能实际上要抓取画面,然后计算分析生成绕开人物的蒙版。

风扇转是因为 cpu gpu 都在高负荷工作。手机这个功耗水平都能完成的事情,只要硬件解码正常工作,没有理由电脑还很吃力。
22 天前
回复了 t41372 创建的主题 操作系统 很讨厌桌面端系统的权限管理模式
追加提一句作为我 #9 回复的补充。

systemd 提供了一个 sudo 的替代品叫 run0 ,今天刚发布,某种成都上是解决 sudo 是为传统多用户设计,不适合现代个人 PC 场景问题的新思路。(当然要实现沙盒化的权限机制,软件适配还是无法避免的。)

技术上 sudo 是 SUID 实现的,除去安全问题之外(任何人都可以执行 SUID 应用,所以额外需要 sudoers 配置文件),这套权限体系是不区分人和应用程序的。这就造成人控制硬件设备还要 sudo 非常麻烦,而应用获得权限反倒可以为所欲为的现状。

run0 不依赖 SUID ,而是通过 PID 1 来 fork 出一个隔离的 pty 执行相关命令和应用,权限部分由 polkit 控制。

好处就是大部分之前依赖 sudo 的硬件控制功能,都可以在已登录用户 session 的情况下无感授权。尽管还做不到像手机上那样基于隐私的权限体系,但已经可以把相对危险的操作隔离开了。

只是目前看它还是 systemd 的一部分,想要完全替代 sudo 还需要时间来验证。
22 天前
回复了 afxcn 创建的主题 Go 编程语言 golang 的 defer 真是个好设计
我看到楼上有人提多返回值的类型问题,这里也多解释一下。当然我觉得有必要再重复一次:

- 不要拿 Java/C++ 的思路去套 Go 的实现
- 不要单独讨论一个特性点的好坏

异常处理并不是只有 try...catch 这一种范式。Go 之所以设计成这个样子就是为了干掉 try...catch 。Go 这样做单独和其他语言放到一起比显得很笨,但却是非常符合 Go 需求的方式。


现实世界里“逐级汇报”和“越级处理”都是普遍存在的。习惯了随意抛出,集中处理,要是异常的类型不确定,肯定会让人抓狂,而且还会觉得,有事说事,明明没问题还汇报个屁。这样想下来,肯定觉得 Go 哪哪都是毛病。

现在换一种眼光,如果用逐级汇报的想法。Go 就明说了,我的机制比较死板,只能逐级汇报,而且模式都是没问题就过,有问题就汇报问题。这样能处理的原地就处理了,不能处理的考虑继续交给上级,直到有人能处理为止。

对 Go 来说还有个问题,要是不止一级出问题怎么办?总不能无限追加返回值的数量吧。为了解决这个问题,Go 把 error 定义成了接口:type error interface { Error() string }。

这里有两层用意,一是中间层级可以追加或修改错误内容,二是指明了 Errors are values 。第一点映射到现实就是,如果中间层级也出错,那就追加进去,甚至可以做请示备注。(这里不抬杠,C++/Java 也可以逐级处理)


Go 真正创新的地方在与第二点:错误是个值,而不是个类型。一旦明白错误是个值,你就会发现异常处理不再强依赖 goto 类控制流程。同时错误状态和错误本身也完成了解耦,控制逻辑只需要判断有没有错误,至于错误,是否处理、由谁处理甚至什么时候处理都有完全的自由。

所有纠结 Go 返回值错误类型的,有没有思考过,凭什么错误一定要是一个类型,这种先入为主的概念是从哪里来的?这就是我一直反复强调的,千万不要手里拿把锤子,就看什么都是钉子。
22 天前
回复了 afxcn 创建的主题 Go 编程语言 golang 的 defer 真是个好设计
最后补充一下我理解的 Go 为什么不选择 RAII 路线的理由,runtime 原因之外的理由。

Go Proverbs 里面有一句 Clearer is better than clever ,说得更直白一些就是 explicit over implicit 即显式优于隐式。

Go 的设计目标“合作”还有工程上的意义,鼓励程序员之间以“合作”的思维来写代码。这一点我在某个讨论包管理的话题里简单提到过一次。

不夸张地说,Go 是我接触过的所有编程语言里,读代码最容易的,没有之一。不管这个 Go 代码是 Java 味道或是什么别的。

原因我猜测是在于 Go 没有隐式的调用。但是基于 RAII 的实现就会存在问题,构造和解析会触发预期之外的代码执行,作为代码或者项目的使用者就要主动去注意相关的问题。

于是 Defer/Panic/Recover 这一类影响控制流的功能都在语言层面上做了限制,这类隐式 goto 的行为也是可预期的,代码也通常不会间隔太远。基于同样的理由,Go 不推荐在无必要的时候在包层面上使用 Init 方法。

与之相关的是,Go 对于零值的执着。一方面它是作为弥补没有构造机制导致表达力缺失的手段,另一方面它是让开发者重新思考构造、默认值和 sane defaults 这些问题。

尽管有人会吐槽 Go 的 pkg.dev.go 被低质量的包污染(也许说得是好名字被提前占了?),但可能没意识到 Go 的简洁带来的好处,一个包的质量好不好,看下代码几分钟就能弄清楚。

PS

说到底,机器是没有办法完美 GC 的,如果人人都能写出生命周期管理完美的代码,也用不到机器了。在这个意义上,defer 提供了非常简单的机制辅助开发者,我认为是语言设计层面上非常大的思路进步,这个机制只有足够简单才能让人有欲望去用。
22 天前
回复了 afxcn 创建的主题 Go 编程语言 golang 的 defer 真是个好设计
回复比较长,简单分了几节方便阅读。

A.
虽然我很对于 Go 设计层面的评价是比较高的,但讨论这种话题还是先表明立场比较好……

1. 语言的单一特性不适合拿出来单独对比,每种语言都是在各种特性中做取舍,单一特性有可能是被偏爱的那个,也有可能是被牺牲的那个,但都是服务于语言整体的。

2. 语言提供的功能特性有适用范围的,有不代表一定要用,好不好用看场景。千万不要手里拿着锤子,看什么都是钉子。

3. 不要用一种语言的思维去套另一种语言的设计。也就是常说的 XXX 味道的 YYY 代码。


B.
回到 defer 设计的问题上,要知道 Go 在关键词上是异常吝啬的,那为什么要专门设计 defer 这样一个看似作用不大的语句?假如没有 defer 语句,对现在的 Go 影响大吗?

一旦你意识到这个问题,越往下深挖就越能理解“妥协”在语言设计里的意义。以下的部分都是我根据记忆总结的,大多数来源是官方文档和一些开发者的个人 blog ,然后形成了我自己的理解,所以不一定正确,大家谨慎参考。


我不止一次在站里各种 Golang 相关的帖子里提到过,Go 语言设计核心的要素是合作,换句话就是妥协。有些事情程序员做很麻烦,那就让语言或者编译器多工作一点,有些事情编译器很难优化,那就让程序员多担待一点。

合作体现最明显的地方是 goroutine 。过去 Linux 在非常长的时间 IPC 信号机制尽管有类似的尝试,但缺乏合作意识导致真正其作用的只有接收方无感知的 SIGKILL/SIGSTOP ,而 SIGTERM 之类的 graceful 操作应用很少。而 Golang channal 机制是内存模型层面唯一保证线程安全的手段,无形中迫使开发者尽可能写出合作式的多线程调度代码。

同时 Golang 设计目标中优先级很高的一点是概念和语法层面的简洁。但是一般来说,写起来越简单,编译器就越复杂。核心设计者在早期就一边写编译器,然后他们发现,try...catch 会使得编译器产生大量难以优化的 goto ,而 RAII 的方式又会使得 runtime 变得复杂。

所以最终的妥协是:支持精简的 runtime ,放弃 RAII 的析构;使用基于错误返回的异常处理,放弃 try...catch 语法。同时还包括使用 GC 降低开发者心智负担,而不是 Rust 那样为了安全增加大量程序员的工作。这里还是需要强调一遍,这些设计抉择是为语言整体服务的,不能单独拿出来比较好坏。


这样做的结果是:

- Golang 中对于 OO 的支持是基于接口的 duck typing 模式,并没有 RAII 支持。
- Golang 没有复杂的控制流结构,只能通过序列式返回错误。程序员被迫要写大量 if err!= nil {}。

相应的好处也显而易见,Go 在很多时候有着媲美无 GC 语言的运行效率,以及非常高的开发效率。

其实在 Go 设计者眼里,语言表达能力弱是可以接受的妥协,而且对于程序员来说付出的代价能够接受。反过来为了开发者习惯做妥协,牺牲编译器效率和 runtime 简洁性就是无法承受之痛了。


C.
在聊 defer 之前,还有点铺垫要做。不清楚开发者们有没有意识到,官方文档对于 defer 讲解的文章标题是 Defer, Panic, and Recover 。

Go Proverbs 里专门有一条 Don't Panic 。意思是不要用 try..catch 的思路来同时处理异常和错误,因为 Go 没有 RAII 。具有 C++/Java 背景的开发者会非常习惯用相同的控制流去一并理异常和错误,而且处理的位置是调用栈的最外层,但 Golang 要求开发者转变思路了。

于是官方文档里 Golang 要求开发者重新审视:什么是错误( error ),什么是 Panic ?一般意义上,可以恢复(即能够继续运行)的异常叫错误,而无法恢复的异常叫 Panic 。

为了弥补缺少了 try...catch 结构导致的表达能力弱的问题,即区分错误类型的问题,Golang 顺利成章地借鉴了多返回值的设计思路。但是依旧不那么便利,毕竟 try...catch 结构里的错误类型是可以在其他地方定义然后使用的。于是 Go 更进一步,将 error 的类型定义为接口。尽管不是那么完美,至少能用了不是。

这里我想再重复一遍,当了解清楚 Golang 设计思路之后,你认为还能单独拿 if err != nil {} 来评价 Golang 吗?


D.
回到上个问题 Defer/Panic/Recover 放在一起是因为这三者的作用域都是当前 function ,这是和传统 RAII 控制流不同的。

官方怕被误用,给出了相关的实现逻辑:Panic/Recover 都是内置函数,defer 是基于栈的 LIFO 队列。所以需要使用者注意循环嵌套的问题。

本质上,defer 作为语法糖的用途只是个副作用,它的设计用途其实是为 Recover 服务的。Recover 只有在 defer 调用时才有意义。我这里引用一下原文:Recover is a built-in function that regains control of a panicking goroutine. Recover is only useful inside deferred functions. During normal execution, a call to recover will return nil and have no other effect. If the current goroutine is panicking, a call to recover will capture the value given to panic and resume normal execution.

说得再简洁一些,Panic 是个隐式 goto ,通过 defer recover 可以利用 defer 的栈结构简单回到之前的执行位置。

这也是官方说 Don't Panic 的原因,没有必要就不需要用。从哲学层面区分错误和 Panic 可以简化逻辑模型。


E.
最后回到更加原始的问题上:编程语言设计大 runtime 和 GC 的目的是什么?还不是因为手动内存、对象管理对开发者造成的心智负担太重了。那让开发者养成“谁污染谁治理”的习惯不好吗?这可太难了,随着控制结构变得复杂,创建和释放资源的位置可能隔着十万八千里呢……

所以回头看 defer 作为语法糖的副作用,栈实现和作用域限制了它的应用范围,了解清楚这一点就更容易明白,defer 不是银弹,该手动的时候还是要手动。

就以楼上的例子:循环里加锁、defer 解锁就是非常不合适的用例。不仅仅是因为 defer 的作用域在锁上会扩大 critical section ,更重要的是违反了创建者负责释放的原则。

我这里有个非常简单的原则:在 function 作用域内即可完成的资源创建和释放,不用 defer ;创建资源之后需要给其他 function 调用,在创建之后 defer 释放。


希望我这一大长篇能够说清楚这个问题。考虑到以上都是我个人的理解,难免会有误解或者错误,欢迎各位斧正。
24 天前
回复了 kuanat 创建的主题 V2EX V2EX 是否会考虑增加专栏功能?
@shendaowu #4

试了一下确实稍微有点麻烦,我思考了一下,可能站长说的 IPFS 分发的方案也可行。或者等主题多了做个汇总主题。
如果 Windows 有这个功能,大概率可以 Linux 实现。难易程度主要看硬件实现的方式,我简单描述下方法,你可以先尝试一下。



一般来说,电池都会通过 ACPI 提供像电量、充电状态信息等等接口,然后通过 sysfs 抽象供用户空间的应用使用。

所以第一步是看 /sys/class/power_supply 的设备,笔记本上 power_supply 类型一般有电源和电池。

能找到对应的电池的话,可以顺着 /sys/class/power_supply/XXX 链接去找对应的电池设备,看看有没有类似 charge_control_end_threshold 这类名字的抽象,这个名字都是厂家自己定义的,但是看字面意义一般就能猜出来。

如果存在的话,可以直接尝试写入。通常电池还会有另一个属性 status ,取值大概是 full/unknown/charging/discharging/not charging ,这个属性几乎都是一样的。not charging 就代表外接电源通电但电池未充电,可以据此判断充电限制是否生效。

还有一种可能是并没有一个 threshold 之类的属性可供设置,但有个类似 limit/switch 命名的属性,代表是否开启充电。通过写 0/1 来测试是否生效。



上面说的都是比较理想的情况。不太理想的情况是,这个充电控制是 EC 完成的,并没有暴露在 sysfs 里面。

那就需要你去 Windows 里,使用 https://github.com/hirschmann/nbfc 这个项目里的 ec_probe 工具,用来检测 EC 寄存器的变化。通过切换软件的充电控制开关,观察 EC 寄存器的变化,用控制变量的方法最终确定相应的地址,然后尝试改写寄存器的值,判断充电控制是否生效。

如果能找到对应的地址和值,用 ec_probe 的 Linux 版本做相应操作即可。(如果开启 SafeBoot 的话,Linux 会进入 Kernel Lockdown 模式,不允许修改 MSR 的值,这就需要 https://github.com/musikid/acpi_ec 的内核模块来修改)



如果 EC 接口也没用,那大概率这个功能是 WMI 驱动完成的,这就需要把 Windows 版的控制程序拿来逆向,然后改写成 Linux 可用的内核驱动模块。这个方法比较复杂就不多说了,如果真遇到了可以再聊。
24 天前
回复了 kkocdko 创建的主题 Linux Linux 笔电的所谓省电技巧
@Donduck #59

我用 Windows 很少,不确定是不是因为 AMD 给 Windows 的调度驱动更完善一些。论方便的话 Linux 发挥一下想象力,有各种实现方式。

基于焦点的切换(不仅仅是游戏模式)我之前在 Linux 尝试过,个人感觉不太好用,因为我一般是后台编译 rust 的时候就切走了。实现也比较简单,sway IPC 监控一下焦点切换消息就好了。

现在用得最多的是快捷键切换,然后状态栏上有个图标响应当前状态。

这个功能比较“标准”的做法是模仿 inhibitor 的模式,写个 loader 脚本,用 loader 启动想要高性能运行的应用,应用结束了 loader 退出也就结束高性能状态。
楼上说得很对,ffmpeg 几乎大部分 filter 都有附带的 scene detection ,这是一个效率优先的实现,需要根据目标视频反复测试参数阈值。

原理是以 YUV 信号提取帧的 luma 亮度信息,速度虽然快,但主要是适应动、静态的场景转换。对于比如讲座类视频效果就不好。

达到目标还有很多思路,比如相似性 hash ,点云等等,主要看目标视频的特性。
24 天前
回复了 t41372 创建的主题 操作系统 很讨厌桌面端系统的权限管理模式
这事还真的是历史包袱,而且不推翻重来的话,看不到什么改善的空间。

以 Linux 为例,权限系统设计的时候是沿袭自 mainframe/terminal 的多用户单主机模式,权限服务的是隔离设备的所有者(巨型机的所有者或者管理员,赋予 root 权限)和用户(终端登录,普通权限)。所以凡是涉及到对于硬件的操作,都要高权限。

但是当今环境里,电脑几乎都是“个人”的,安全威胁模型也逐渐转变成为,隔离不同的应用程序,于是有了沙盒等配套机制。要让这套系统良好运作,需要的是所有应用程序主动按照权限机制重写。
24 天前
回复了 vx7298 创建的主题 Ubuntu ubuntu 一如既往的优秀!
@ityspace #47
@424778940 #65
@Jirajine #51

作为发行版来说,我认为现阶段 NixOS 是不够严肃的,定位大致可以接近于弱化版的 Arch 。作为语言的 Nix 和作为包管理的 Nixpkgs 也不能直接拿来对比的,但是这三个经常被混在一起。比如 NixOS 官网提到的三个特性关键词 Reproducible/Declarative/Reliable 实际上分别是描述 Build ( Nixpkgs )/Language ( Nix )/OS ( NixOS )的。(另外官方的正式名称 Nix 既是语言,又是包管理器,Nixpkgs 指的是软件仓库)

按时间来说,我印象是先有的 Nixpkgs 的想法,为此设计了 Nix 语言,最后才有的 NixOS 。早期(至少十年前了) Nixpkgs/Nix 的文档实在是太简陋了,大部分时间我都是看着代码去猜的……当然现在易用性好了很多,只是我个人的评价是,Nix 系的设计目标是有可借鉴之处的,但是实现方式和哲学我不是很认可。

当年我被 Nixpkgs 吸引的原因是它希望用新的方式(在当时是很超前的思路)*同时*解决包管理器常年存在的几个问题:一是多版本共存,二是依赖解析,在此基础上顺便提供两个新特性:支持原子操作和回滚且无需依赖特定文件系统,再就是 Reproducible 构建。实现方式上,用的是现在被很多包管理借鉴的叫做 Content-addressable storage 的存储后端,以及类似沙盒化的构建过程(主要是记录依赖树而非权限限制)。

换个简单的说法,这个包管理的思路就是:记录依赖树上每个包的版本,同名包不同的版本会各存一份,相同版本也只存一份,而目标应用的执行环境(沙盒)中只有用得到的特定版本的依赖。这是个很理想化的设计,也存在很现实的问题。根据 Debian 过去十多年的构建经验,符合 Reproducible 构建标准(二进制一致)的包的占比一直在 85% 左右(构建这个行为会引入包依赖之外的变量)。Nixpkgs 意义上的 Reproducible 更接近于容器镜像可移植 Portable 的概念,即每次构建可能不同,但可以相互替换。

对于开发者来说,一般不关心打包的事情,但多数都会提供构建脚本。对于打包者来说,针对特定系统的打包希望尽量标准化,配置尽可能简单,原作者的构建脚本能拿过来用最好。对于包管理器来说,依赖最好是清楚写明的而不要是推断的。在 Nixpkgs 的设计者来说,当时没有合适的工具,所以想要创建一门新的语言,功能需求至少有几个:有脚本的灵活,声明式的写法以及模板化的生成能力。

至少在 Nixpkgs 层面,我是很欣赏设计思路的。到了 Nix 这门语言的层面,我的能力不足以做出准确的论断。仅从用户的角度上说,学习成本和易用性门槛都是相对比较高的。我个人认为 Nixpkgs 可以和 Gentoo portage 相媲美,都可以作为非常好的构建平台。

当包管理器想要往发行版方向发展时,限制就显现出来了。我一直说 RedHat/Debian/Suse 系是相对严肃的发行版,主要是因为它们都有足够的维护者。Nix 目前官方仓库的维护水平差距还是比较大的,而且可以遇见由于用户基数和门槛的限制,这个改善过程会很艰难。

另一方面我认为 NixOS 在发行版或者操作系统的理解定位上有点走偏了,就好比 Nixpkgs 是很好的锤子,但是 NixOS 太想把发行版塑造成钉子的形状。而且无论是 Windows/Linux/macOS 经过这么多年的发展,比较明显的共识是操作系统应该追求 Immutable 而不是 Reproducible 。这方面 NixOS 的实现方式永远比不上基于文件系统( layered )的效果,所以 NixOS 只能宣传自己 Reliable 却无法说自己是 Immutable 。

再就是用户数据理应和系统分离,应用程序理应和配置分离。NixOS 推崇的把用户、配置纳入进来的思想在我看来耦合有点过深了。不是说这样做不好,只是不太符合我个人的 Linux 哲学认知。
1  2  3  4  5  6  7  8  9  
关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   4916 人在线   最高记录 6679   ·     Select Language
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.8.5 · 28ms · UTC 06:48 · PVG 14:48 · LAX 23:48 · JFK 02:48
Developed with CodeLauncher
♥ Do have faith in what you're doing.