V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX  ›  kuanat  ›  全部回复第 4 页 / 共 9 页
回复总数  164
1  2  3  4  5  6  7  8  9  
40 天前
回复了 rockyliang 创建的主题 Go 编程语言 关于 golang 官网一段代码的疑惑
@rockyliang #20

这部分我也不太清楚,猜测影响的因素会有很多,编译器实现方式影响可能大一些。

如果仅从语言层面上说,Golang 的内存模型里面随机结果的种类会少一点,官方编译器倾向于减少这些不确定性。C 的话编译器很多,基本很难预期 UB 的结果。
40 天前
回复了 rockyliang 创建的主题 Go 编程语言 关于 golang 官网一段代码的疑惑
@doraemonki #16

“[2]一定执行了,那么可以推出[1]也执行了“ => 这句话不正确。

参考"happens-before 和指令执行的先后顺序无关,不是时间上的先后概念"这一部分。这句话解释起来就比较麻烦了,建议去看内存模型相关的文档,一般都是有示例来辅助理解的。happens-before 和时间上执行先后顺序无关。

[1]<=[2] 和 [3]<=[4] 这两个条件不能通过传递性获得任何 [1] 与 [4] 的关系。具体到这个例子,时间顺序上按 [2],[3],[4],[1] 的先后执行,恰好编译器使得 [2] 的结果对 [3]可见,这样是不违反规范的。

参考“编译器的工作从来都不是对指令执行流程进行排序,只要能保证可见性,指令顺序是不影响预期结果的。”

尽管 [1]<=[2] 但 [1] 和 [2] 的时间先后对二者都没有影响,[3] 和 [4] 相互也没有影响,他们都没有操作相同的内存变量。换句话说,由代码书写先后推导出的 [1]<=[2] 和 [3]<=[4] 是没有用的额外条件。这四条指令可以按任意时间顺序执行,都满足规范。
40 天前
回复了 rockyliang 创建的主题 Go 编程语言 关于 golang 官网一段代码的疑惑
我来尝试用最直白的方式解释一下吧,

原文 The Go Memory Model 是严谨准确的,只是涉及到专业术语,没有相应的理论背景很难理解。

第一个容易误解的概念是 guaranteed 。编程语言定义规范( spec ),编译器负责在目标平台上完成实现。保证( guaranteed )的意思就是,在满足特定条件的情况下,无论编译器如何完成,都要确保最终的结果符预期。

看上去和什么都没说一样对吧?结合相反的概念就明白了,这个相反的概念叫 undefined 。也就是 C 时代大名鼎鼎的 undefined behavior 那个 undefined 。因为编程语言没明确要求,编译器想怎么干就怎么干,也可以什么都不干。

所以 guaranteed/undefined 的实质区别就是,前者以确定的方式干了点什么,从而保证了结果和预期的一致性。至于具体干了什么后面再说。

接下来的概念叫内存模型( memory model ),这是一组对规范( spec )的抽象而形成的规则。毕竟代码写起来可以五花八门,但内在逻辑是相似且有限的。但是如何用形式化( formal )的方式来精准定义这些逻辑呢?

从最简单的例子开始,假如只有单线程,那么执行顺序就是代码的书写顺序。如果是多线程呢?(这里还是取最简单的情形来简化分析)

思考一下就会发现,不确定性的来源主要是两类:一类是对于同一(共享)变量(内存)的读/写并行操作,另一类是写/写操作。这些的潜在冲突被叫做竞态( race )。

设想最简单的读写冲突场景,读和写操作分别位于两个独立的并行执行序列中。如果预期的结果是先读后写,那什么都不用做,这两个指令不会相互影响;但是反过来,预期先写后读,且要保证( guaranteed )读到写的结果,就需要编译器做点什么了。

这里“读到写的结果”有个专门的名词叫可见性( visibility ),编译器具体怎么做不重要,重要的是编译器做了一件事,在写操作完成读操作发生之前,使得写操作的结果对于读操作可见。(要么共享一份副本,要么两份独立的副本但同步成为相同的结果)

最后的任务就是,编程语言在规范( spec )中定义,在什么情况下编译器需要保证可见性( visibility )。即判断开发者按照直觉写出的代码,它的预期执行逻辑应该是什么样的。

这背后的代码逻辑关系被定义成了一个特殊的术语 happens-before ,满足 w(rite) happens-before r(ead) 的情形下,就要保证 w 的结果对 r 可见。这里其实是个反向的过程,既然某些代码和逻辑按照开发者的预期应该满足可见性,那就定义它要满足 happens-before 关系。(这里为了方便理解,对定义进行了简化)

这是最容易被误解的术语,它和指令执行的先后顺序无关,不是时间上的先后概念。(关于这一点如果不理解,建议从基础开始学习内存模型理论。)编译器的工作从来都不是对指令执行流程进行排序,只要能保证可见性,指令顺序是不影响预期结果的。

这个逻辑关系比较重要的特性是它具有传递性。我这里用 A <= B 代表 A happens-before B ,那么 A <= B 且 B <= C 可以推导出 A <= C 。另一点比较重要的是,happens-before 强制要求编译器保证可见性,但是可见性并不能反推出 happens-before 关系。

最终的结果就是,Golang 的设计者希望,当开发者用 XXX 的方式写代码的时候,编译器能产生符合预期的结果。于是就定义 XXX 这些写法,就是 happens-before 关系,编译器必须对其可见性结果做出保证。Golang 规范中定义了几种 happens-before 关系,这里就不一一列举了。

--------手动分割线--------

如果重新表述一下示例代码的问题,就是判断 [1] `a = "hello, world"` 和 [4] `print(a)` 是否满足 happens-before 关系,[2] `done = true` 和 [3] `for !done {}` 是否满足 happens-before 关系,从而判定可见性。

很遗憾根据 Golang 的规范,这里仅有 [1] <= [2] 和 [3] <= [4],不存在其他 happens-before 关系。(读 The Go Memory Model 原文可以看到采用 channel 通信后,比如把 [2] 和 [3] 分别替换为 `c <- 0` 和 `<-c`,由于语言规范层面上建立了两个 goroutine 相关指令间的 happens-before 关系,有 [2] <= [3],从而依靠传递性保证了 [1] <= [4],于是保证了不会有死循环,赋值也总能得到正确的输出)

既然不存在 happens-before 关系,那么编译器就没有必要保证 `done = true` 的结果对于 `for !done {}` 可见。缺少了这一层保证,那编译器的某种实现使得 `for !done {}` 一直读到 `done = true` 可见之前的结果也就不奇怪了。因为 `done = true` 对于 `done` 的操作可能完全不会让 `for !done {}` 知道。

--------手动分割线--------

看到这里你肯定会感到疑惑,为什么 Golang 不对前面这种情况做精确定义?

我的理解是,这个思路 C 当年用过了,然后发现太复杂且不符合直觉的情形太多,于是抽象了诸如 mutex/semaphore 等等原语来降低开发者的心智负担,帮助写出正确的代码。结果并没有那么理想,理解这个背景对于大多数非科班的开发者要求还是太高了。Golang 走相同的路不会有更好的结果。

Golang 的设计目标中很重要的一点是简单。希望即便开发者完全不懂内存模型,也能凭直觉写出正确的并行代码。原文的示例非常明显了,不要用 C 的思路去写并行代码,而是用 Golang 提供的 channel 机制,新机制有着严格且准确的 happens-before 逻辑,同时符合直觉。
用 rust 重新实现一遍很不错,手动点赞。

只是 https://github.com/mitmproxy/mitmproxy 已经很成熟了,用的是 python 开发所以很方便直接用 python 脚本接管请求处理。不清楚 rust 要怎么支持一个脚本引擎来做类似的工作比较合适。
春节期间我把开源可以部署的本地 LLM 都试了一下,没有量化单纯体感,7B 这个参数尺度上,DeepSeek Coder 效果是最好的。
能做成网络请求的方式是最好的,即使是内网也可以临时用穿透方式来测试。

如果因为实时性或者与其他模块集成没办法,那就编译成 pyd/so ,如果是 so 的话记得移除符号表,pyd 应该是默认移除的。

做一点简单的限时逻辑,不要有明显的特征,尽量以 silent crash 的形式来处理。这样一般防老板是够用了。有应对措施的大概率就能自己做了。
既然有选择的空间,那就肯定不是类似于嵌入式环境定制这样的场景了。

这个问题不如反过来思考,什么时候会考虑换内核?

因为绝大多数情况下,选择内核版本是在选择发行版之后的事情,也就是说几乎不会主动去换内核。

- 某个版本内核爆出严重安全漏洞

- 需要调节某个编译期配置参数(开启功能支持或性能优化)

- 闭源驱动模块限制内核版本

一旦限制了内核版本可选范围,那么就尽量选择改范围内的 lts 版本,然后就基本保持不变了。版本本质上是内核功能集合的数值化表述,不需要新功能(或者性能优化)就不需要更新。

目前还在维护的 lts 版本有 4.19/5.4/5.10/... 等版本,基本上应该满足各种需求了。使用 lts 版本的原因是,出现严重漏洞的时候,相关补丁也会 backport 到所有 lts 版本上。

另外发行版一般不会直接使用某版本 mainline 内核,而是有自己的一套补丁,用以配合自家发行版的默认功能。但是发行版的版本基本上会匹配同版本号的 mainline 版本,所以还是建议尽量选择严肃的发行版,以便尽快获得安全补丁。


------手动分割线------

尽管 CentOS 已经停止维护很久了,但还是有人会因为各种原因选择它。对于这种情况,不仅仅要关注内核问题,还要关注 glib 等版本的问题。

相应的开发、测试或者 staging 环境几乎都要退化到比较低的版本。比较新的版本就不存在这些问题。
我提一个可能会被遗漏的细节,微服务环境里做日志,一定要有全链路的 trace 信息,不然出了问题也很难还原出错的场景。

前面有人讲了读写分离,写基本都是本地,然后通过后台 daemon 定期汇总到日志服务器上。

原始日志汇总存储之后,建议再定期根据 trace 重新组织一下(比如数据库视图,或者直接用文档类型存储)以方便查询。一般来说,日志信息往往会比生产数据的容量还大,如果用到了再去查可能会很慢。
43 天前
回复了 onichandame 创建的主题 程序员 local first 应用前景咋样
某种程度上,现在软件商业化形式是和 local first 理念相悖的,所以做这个的很少。

从另一个角度,我个人认为开发 local first 应用的技术难度也很大,笼统地说实现 local first 就面临分布式系统的同步问题。这个问题没有最优解,只有妥协,当你追求用户体验的时候,留给你的技术选择就非常少了。
paddleocr 内存分配管理有问题是个很长时间的问题了。我印象官方 github 有几个 issues 就是讨论这个的,而且跨了多个版本,你可以去查一下。

之前有说是因为框架缓存的原因,有人说不是。我之前遇到这个问题也是通过重启来解决,尝试读了一下 c++ 代码部分还是太复杂了,没有解决的精力和能力。
48 天前
回复了 sofukwird 创建的主题 程序员 浏览器爬虫再进化
如果我没理解错的话,这个实现的是挺厉害的,只是应用场景非常受限。

从功能上说,相当于被注入的页面向外提供 http 代理,该代理会复用浏览器环境,也就继承了相关 cookies 。

使用这个方案,相比基于 webdriver/cdp 的方式,可以省去提取 cookies 、模拟登录的过程,通过人工在图形界面上操作一次即可。

比较容易想到的局限在于:

- 网站有多处或者基于访问频率的人机检测,就需要大量人工介入(甚至难以介入,因为通过代理触发的人机检测并不会显示在浏览器界面上)

- 目标接口需要额外访问参数,一般反爬措施都会利用 vmp 混淆 js 代码来生成可以被服务器后端验证的动态参数

如果我说错了还请 OP 指正一下,因为我确实想不到更好的应用场景了。
字体渲染这个话题一两句话说不清楚,我自己的笔记大概有五六千字,如果有空闲我会考虑专门发个帖子。

这里回复主要是想表达一个观点:苹果的渲染效果是可以在 Linux 环境近似复制的。

我这里举两个例子,需要配合 #23 @agagega 的截图对比来看。

由于我这里没有苹方字体,不过看过效果之后就知道不影响了。我这里的环境是 Gnome+Firefox 200% 缩放。

第一张是我日常的效果

https://i.imgur.com/E7QDq7p.png

第二张是贴近苹果的效果(通过调整参数还可以更像)

https://i.imgur.com/3FNIPYO.png



----------手动分割线----------

Windows 那个就不做对比了,我说下 #23 macOS/KDE 和我这里的区别。

首先忽略标题里粗体,那个是字重的关系,其次忽略英文和数字,因为它们 fallback 到了不同的字体上。

如果你把图片保存下来放大看,那么你会发现,KDE 的字体渲染是有彩色边缘的,说明它使用了 subpixel 抗锯齿。而 macOS 实际上是用的 grayscale 抗锯齿。

接下来关注标题下面“XX 小时 XX 分钟”的那个“时”字,苹果系统那个“时”和日常里写法是不太一致的,右边“寸”的横非常靠下,而 KDE 版本就比较正常。这是因为 macOS 的渲染引擎会无视 hinting ,而 KDE 依赖的底层 FreeType 在 KDE 的默认值是 Slight Hinting ,当苹方字体本身没有 hinting 信息时,会按照算法在垂直方向上做自动 hinting 修正。

所以 Linux 环境里,如果通过配置 fontconfig ,设置 hinting=false hintstyle=hintnone ,基本上可以获得接近 90% 的苹果效果。也就是我这里图一的例子。

如果仔细看还是会发现,苹果的渲染标题下面“XX 小时 XX 分钟前”里面的汉字,明显要颜色偏深一些。实际上,如果设置了暗色模式,这个差别会更明显。

这里涉及到一个叫 stem darkening 的概念,在“正确”渲染的前提下,额外对灰度进行加深,以匹配人类视觉的非线性感知。

所谓“正确”是指,通过 alpha composing 将字符前景与背景合成,这个计算是在线性空间里完成,然后通过 gamma 矫正转换到输出空间。这个过程在 Win/macOS 上都是默认支持的,然而 Linux 缺少 gamma 矫正这一步。

可以通过 FreeType 的参数来强行启用 stem darkening ,就如我第二张图的效果。实际效果是轻微的加粗,对于灰度显示的部分影响比较明显。只是从“反映字体设计意图”的角度上说,这个渲染逻辑是不正确的。但考虑到追求渲染效果往往是因为设备 DPI 不够,所以正确与否反倒不那么重要了。
`Run or raise`
https://github.com/CZ-NIC/run-or-raise
https://extensions.gnome.org/extension/1336/run-or-raise/

PS
不依赖扩展的方式就是楼上说的 Super+数字,上面的插件里也提到了。

再 PS
tiling wm 环境里这个功能非常好实现,可以借鉴一下思路。绑定快捷键就用 Gnome 自带的 Keyboard shortcuts ,执行一个 bash -c "command" 命令。这个命令脚本用来判定运行程序和判定焦点,然后通过某个接口去操作窗口管理器。X11 的话,wmctrl 就可以。Wayland 需要通过 D-Bus 调用。
60 天前
回复了 flx413 创建的主题 程序员 QUIC 实践疑问
基于你这个描述,我提供一个方向,可能和我之前遇到的丢包问题是一样的。

并发加载资源的时候,NWListener 用单个 NWConection 连接会丢包,用多个 NWConnection 来处理,有可能就正常了。

一般化的测试方法是用 quic-interop-runner 来跑一下,看看是不是协议不同实现的问题。估计你用的服务端实现肯定在列表里,就是需要额外写个 ios 的客户端。

如果要定量测的话,可以配合 wireshark/mitmproxy ,这俩新版本都可以简单处理 quic 协议了。
Go 的 context 是 1.7 版本引入给 net/http 服务的,用来解决信号和取消问题,传 value 只是顺带的,同时特别强调了线程安全的问题。名字用了 context 但是语义上确实只有上文。所以当你真正需要上下文的时候 context 包是不够的。

一般中间件解决这个问题的思路是自定义 context ,其实我不太喜欢 gin 的方式,我个人的偏好是类似

```
type MyContext struct {
ctx context.Context
// custom field
key string
}
```
这样的方式。然后实现 Context 的接口方法,写几个 wrapper 就可以完成对 context.Context 的兼容,不影响原本 net/http 的信号取消机制。

剩下的就是语法层面的封装了,需要实现一组方法,比如从 context.Context 衍生出子 MyContext:
```
func DeriveMyContext(ctx context.Context) *MyContext {
myCtx, _ := ctx.Value(MyCtxKey).(*MyContext)
return myCtx
}
```
此处用接口断言是根据 context 的设计,value 通过自定义类型模拟命名空间,防止 key 冲突。

结合起来就是 `context.WithValue(context.Background(), key, value)` 中的 kv 对,实际上就是通过 context.Value 传递了一个特定的 key ,这个 key 等价于指向 MyContext 的指针,和你的思路是一致的。

这样中间件所有涉及的 context 都通过一个 MyContext 的结构共享上下文,如果涉及到多线程可以加 Mutex 锁。

反正 Go 在传递 context 这件事上已经一条道走到黑了,比如 1.21 标准化的 slog 日志库也可以接受 context ,稍微封装下也可以直接用。
125 天前
回复了 bocchi1amos 创建的主题 Python 为什么 Python 会有.venv 虚拟环境的概念?
@cnt2ex #63

我的描述是基于自己的记忆和理解,不一定就是正确的。

我原本是想要通过 __all__ 这个例子来体现,基于文件系统的 import path 对今天包管理机制造成的困扰。

在当时 windows/dos/Macintosh 的文件系统上,import * 遍历出来的文件名是大小写不固定的,也就导致了引入的符号表大小写不固定。于是设计了 __all__ 机制,因为 __all__ 定义的大小写是可控的。开发者可以选择 opt in 。

__all__ 机制是需要手动维护的,也不能阻止用户显示地手动引入。如果今天重新设计一门语言,__all__ 是完全不必要的,可以通过 public/private 关键词,也可以通过某种 name convention 来实现。

并不是说基于文件系统的 Package 组织形式不好,而是说这是一个选择。Python 选择了简洁,但是要在别的地方(包管理)付出代价。__all__ 机制就是这个代价之一。


回到虚拟环境的问题上,现在假想一下 Python 依旧使用基于文件系统的 Package 组织形式,但是在路径名中包含版本号。

现在 Python 就面临几种选择,要么以 import xyz==1.1.1 的形式使用,要么 Python 自己实现一个版本选择机制然后维持 import xyz 的向后兼容。

第一种方案几乎没有人选的。因为一旦要升级依赖,需要显示地在源代码里面做修改。但是这个方案的改版非常多见,比如 npm 的 package.json 就是集中把 xyz==1.1.1 放到了一起。

(看起来很完美对吧,但是当时没有人意识到这个 NP 问题会成为日后的麻烦。这种组织方式从机制上就不允许引入同一个包的不同版本,所以 NP 问题退化为 P 问题的途径就少了一个。)

第二种方案,考虑到时间节点,不是一个好的选择。但这个路线是近十年来以软件工程为导向的语言的首选。

这里需要明确区分 Package 的组织形式和 import path 的实现,前者是一种 specification 规范,后者是一个实现方式。Python 完全可以从规范层面要求所有的包名都带版本号,然后 import hook 的实现无视它,固定使用版本号最大或者最小,甚至文件最后修改信息最新或者最旧的版本。


虚拟环境解决的是:在 Package 组织形式是基于文件系统、且 Package 规范不包含版本号的前提下,提供一种在同一个系统中,安装同一个包多个不同版本的可能性。

如果 Package 规范要求包名带版本号,那么“安装”这个行为是不受限制的,也就不需要虚拟环境了。至于安装好了之后,使用时“选择“哪个版本,这是另一个事情。

技术层面上,我猜 Python 转向 npm 类似的包管理方式是没有太大难度的,PEP 621 规范了 pyproject.toml ,也就是前面说的方案一,楼上 frostming 开发的 PDM 就是这么做的。现在的问题是 Python 的 import hook 本身并不支持路径名带版本号的,所以还是需要再套一层虚拟环境,让 Python 只能访问到特定的包。

说得再具体一点,包管理器在文件系统里,以包名+版本号的形式,集中管理所有的包。虚拟环境指定了 $PATH ,在当前环境中的包软链接到实际的带版本号的包,然后软连接的名字不包含版本号。包管理器额外维护一个 pyproject.toml 记录当前所用的包,同时负责计算依赖。在此环境内的 Python 解释器和源码都不需要做任何改变,完全向后兼容。
127 天前
回复了 bocchi1amos 创建的主题 Python 为什么 Python 会有.venv 虚拟环境的概念?
@flyingghost 60

我没有听说过类似的方案,如果让我评论的话,我认为两个方向都不合适,一个是结合了版本号之后命中率很

另一个方面,一个包 A 如果同时依赖 B 和 C ,我猜测不存在一个多项式算法,可以从 B 和 C 依赖计算出 A 的依赖。(需要 backtrack 到所有依赖重新计算)导致这样的缓存中根本上不可用。
我的映射是 capslock 短按是 esc 长按是 ctrl 。
127 天前
回复了 bocchi1amos 创建的主题 Python 为什么 Python 会有.venv 虚拟环境的概念?
@cnt2ex #57

这里我说得太简略了。

在设计这个 import 机制的 1.x 版本,要实现 import * 需要去文件系统里遍历有哪些模块。底层文件系统可能会对某个名为 xyz 的模块,返回 XYZ/Xyz/xyz 几个不同的结果。当时对于这个问题的解决方案是设计 __all__ 让维护者自己声明是哪一个。

之后的 PEP 才明确了模块名应该( should )全小写,import 的所有符号都是小写。再之后 __all__ 才成为一种工程上的控制机制。
128 天前
回复了 Goooooos 创建的主题 Java 吐槽下 Google 开源的组件
@Knife42 #8

自我定位是 long time lurker 所以没有什么社交账号。

v2ex 发帖的初衷是想在 AI 生成内容的时代留下一点有价值的东西。
1  2  3  4  5  6  7  8  9  
关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5057 人在线   最高记录 6679   ·     Select Language
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.8.5 · 37ms · UTC 07:43 · PVG 15:43 · LAX 00:43 · JFK 03:43
Developed with CodeLauncher
♥ Do have faith in what you're doing.