Eric 发布的文章

给小朋友解释三门问题

有天晚上, 上三年级的小朋友很认真地说, 要给我出一个数学问题.

他描述的问题是这样的:
有三扇门, 一扇门后面有一辆车, 另外两扇门后面各有一只山羊. 我先选一扇门, 然后主持人会打开另外两扇门中的一扇, 这扇门后面是一只山羊. 现在我有两个选择:

  1. 坚持选择我最初选的那扇门.
  2. 换到另外一扇还没打开的门.
    问题是: 我应该坚持选择, 还是换门, 哪个选择赢得车的概率更大?

听完这个问题, 我感觉以前听过类似的问题, 但当时没太在意. 于是我思考了一下, 说到: “换不换门, 赢车的概率其实是一样的, 都是1/2”.

小朋友听了, 觉得我说得不对, 他说: “你错了, 换门赢车的概率是2/3, 坚持选择赢车的概率是1/3”.

我觉得这个问题挺有趣的, 感觉应该没有那么简单, 但是我觉我的推理分析也没有差错. 于是我问他, 这个问题你是从哪里听的? 如果是老师讲的, 或者从某些科普书上看到的, 那么这个问题应该是有标准答案的.

他说可能是从某个音频节目上听到的, 但他也不太确定.

接着他又进一步给我解释说: "假设有100个门, 其中99个门后面是山羊, 只有1个门后面是车. 你先选一个门, 你选中的门有1/100的概率是车, 有99/100的概率是山羊. 然后主持人会打开另外99个门中的98个, 这些门后面都是山羊. 现在只剩下你最初选的那个门和另外一个没打开的门. 如果你坚持选择你最初选的门, 你赢车的概率还是1/100. 但是如果你换到另外一个没打开的门, 你赢车的概率就是99/100. 所以换门赢车的概率更大."

其实听完他的这段解释, 我还是没太明白.

于是我去问了一下大模型, 才发现他描述的问题里面没有突出主持人的角色. 其实主持人是知道哪扇门后面是车还是山羊的, 他完全知道每扇门后面是什么, 并且当我选择一扇门后, 他必须打开一扇有山羊的门. 这样的行为影响了最终的概率分布.

为了解释这个问题, 我决定重新梳理一下这个问题的逻辑.

假设主持人并不知道门后的情况

在我选择之后, 他从我没选的2扇门里面随机选择了一扇门, 并且结果里面是山羊,这种情况下, 不论从我的角度还是从主持人的角度去看, 我坚持选择和换门的概率都是1/2.

假设主持人知道门后的情况

我们先从主持人的行为角度来分析:
当我选择了一扇门后, 主持人会根据我选择的门后面的情况来决定他打开哪扇门.

  1. 如果我选择的门后面是车, 那么主持人可以从另外两扇门中任选一扇有山羊的门打开.
  2. 如果我选择的门后面是山羊, 那么主持人只能打开另外唯一的一扇有山羊的门.

从主持人的行为可以看出, 主持人的选择是有条件的, 因为他知道门后的情况, 所以他的选择不是随机的, 而是受限于我最初选择的门后面的情况.

从我的角度来看, 我仍然看到2扇门, 其中一个有车, 另一个有山羊. 单纯的分析, 我觉得坚持选择和换门的概率都是1/2.

但是考虑到主持人的行为是有条件的, 他的选择实际上影响了剩下两扇门的概率分布.

具体分析

假设3扇门分别是A, B, C. 具体车在哪个门后面是不影响整体分析的, 因为他们是对称的.
假设车在A门后面, 山羊在B和C门后面, 从事前各种可能的情况来纯理论推演分析:

  1. 假设我选择A门(车), 主持人可以打开B或C(山羊), 如果我坚持选择A, 赢车; 换门到C或B, 输.
  2. 假设我选择B门(山羊), 主持人只能打开C(山羊), 如果我坚持选择B, 输; 换门到A, 赢车.
  3. 假设我选择C门(山羊), 主持人只能打开B(山羊), 如果我坚持选择C, 输; 换门到A, 赢车.

总结以上3种情况:

  • 坚持选择赢车的情况: 1次(选择A)
  • 换门赢车的情况: 2次(选择B或C)

结论

通过以上分析可以看出, 坚持选择赢车的概率是1/3, 换门赢车的概率是2/3. 所以小朋友说的是对的, 换门赢车的概率更大.

记录小米路由器刷 openWRT 并配置代理等操作

自从感觉到 chatGPT 基本能覆盖技术的方方面面, 就不太愿意写博客了. 但是记录一下路由器的设置和操作, 还是很有必要的, 主要方便自己以后重新设置方便.

一开始搬到这边后, 只有一个移动的光猫. 但是在另外一个房间, 总是信号不好. 于是买了一个华为的穿墙路由器, 通过网线连接到移动光猫. 过了半年, 感觉离路由器较远的一个房间还是信号不好, 小孩上网课会卡顿. 于是又买了一个小米的路由器, 改成小米路由器通过网线连移动光猫, 然后华为路由器通过桥接连小米路由器. 这样终于信号不错了. 再后来, 公司的 Proxy 不能用了, 即便在公司电脑上. 于是想在路由器上假设透明代理. 查了下, 发现小米路由器可以ssh, 于是折腾开始了.

给小米路由器装openWRT

有人做了全自动的破解安装ssh, 和安装 openWRT: https://github.com/openwrt-xiaomi/xmir-patcher. 一个 run.sh 或者 run.bat 完全搞定.

openWRT 的基本设置

修改网段

openWRT 的默认网段是 192.168.1.x. 移动路由器也是 192.168.1.x, 虽然一个是在lan, 一个是在wan口, 理论上没影响, 但还是把openWRT上的 192.168.1.x 改成了 192.168.31.x. 这也是小米最初的设置.

修改时区和NTP server

System -> System
时区 -> ntp.aliyun.com, ntp.tencent.com 等.

修改wifi通道

Network -> Wireless -> 选择 SSID -> Edit -> Operating frequency & Country Code

修改 DNS

安装完之后, 刷小红书视频有时候会卡住, 不知道为什么, 猜测可能是openWRT 的默认英文版本里面的 DNS 是国外的, 于是修改 DNS 配置. 在 /tmp/resolv.conf.d/resolv.conf.auto 里面添加国内的 DNS servers

公共 DNS 服务

服务商主要地址特点适合场景
阿里云 DNS223.5.5.5
223.6.6.6
速度快、稳定性好综合最佳
腾讯云 DNS119.29.29.29
182.254.116.116
响应快、干净无劫持日常使用
百度云 DNS180.76.76.76智能解析、抗污染技术用户
114 DNS114.114.114.114
114.114.115.115
老牌稳定、覆盖广基础使用

运营商 DNS

运营商DNS 地址特点
中国电信218.85.152.99
218.85.157.99
本地化最优
中国联通123.123.123.123
210.21.4.130
延迟最低
中国移动211.136.192.6
211.136.192.7
移动网络专用

恢复系统

很多时候, 发现安装某个软件之后, 网络就慢,或者有问题, 可以从头再来. System -> Backup / Flash Firmware -> Flash new firmware image. 这个image 可以从 https://openwrt.org/inbox/toh/xiaomi/ax3000t 安装部分(Installation) -> Firmware OpenWrt Upgrade URL 下载.

诊断 Python 占用一个 CPU 的问题

早上刚打开 MAC 笔记本 5分钟, 就提示我电池快没电了, 于是感觉给它供电. 紧接着, 就听到风扇呼呼作响, 于是查看 Activity Monitor, 发现有个 Python 进程几乎占用一个CPU, 感觉有 bug, 也许是项目的那个代码没写好. hmm, 生产环境不会也是这样吧?

Activity_Monitor.png

先确定进程信息

有了进程号, 进程很容易确定.

% ps aux  | grep 24315
xiatian          24315  99.3  0.0 36938856   9804   ??  R    Mon10PM 1044:13.73 /usr/local/Cellar/python@3.11/3.11.11/Frameworks/Python.framework/Versions/3.11/Resources/Python.app/Contents/MacOS/Python /Users/supra/work/projects/agent-ui/main.py

再做 CPU profiling

py-spy 是一个非常流行的采样 profiler,可以在不修改代码的情况下分析任何正在运行的 Python 程序。

pip install py-spy
py-spy record -o profile.svg --pid 24315

火焰图如下:
profile_svg.png

前面三行都是 Python 自带 python3.11/threading.py 里面的代码. 最后一行是 concurrent/futures/thread.py) 的代码, 代码如下:

    try:
        while True:
            work_item = work_queue.get(block=True)
            if work_item is not None:
                work_item.run()
                # Delete references to object. See issue16284
                del work_item

                # attempt to increment idle count
                executor = executor_reference()
                if executor is not None:
                    executor._idle_semaphore.release()
                del executor
                continue

            executor = executor_reference()
            if _shutdown or executor is None or executor._shutdown:
                # Flag the executor as shutting down as early as possible if it
                # is not gc-ed yet.
                if executor is not None:
                    executor._shutdown = True
                # Notice other workers
                work_queue.put(None)
                return
            del executor
    except BaseException:
        _base.LOGGER.critical('Exception in worker', exc_info=True)

看上去从 work_queue.get(block=True) 拿 item , 每次拿到的都是 None. 那么下一个问题就是这个 work_queue 从哪里来的, 为什么会以极快的速度返回一个 None?

交叉验证

要回答上面的问题, 就要深入内存去看这个 work_queue 从哪里来的, 它引用了那些对象, 哪些对象还在引用它. 这样才能确定这个 work_queue 有什么问题. 但是, 在此之前, 我们可以去production 看一下, 看看在prod 的 Linux 上是不是有类似的问题, 结果发现完全没问题, 并且它运行的时间更长.

于是猜测, 这个问题难道只是个 MAC 有关? 或者和 MAC 休眠有关?

github 使用 personal access token 认证

最近又因为这件事情折腾半小时, 索性记录下来.

  1. 因为公司电脑有防火墙或者公司电脑访问策略的问题, 不能使用 git 协议访问 github.com 的 push/pull, 所以只能使用 https 协议, 比如: https://github.com/int0x03/samples.git
  2. github.com 已经禁止使用账户加密码来操作 git push/pull, 看这里: https://docs.github.com/en/get-started/git-basics/about-remote-repositories#cloning-with-https-urls
  3. 如果使用用户名 + 密码会出现下面的错误:

    git push origin master
    Username for 'https://github.com': int0x03
    Password for 'https://int0x03@github.com':
    remote: Support for password authentication was removed on August 13, 2021.
    remote: Please see https://docs.github.com/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
    fatal: Authentication failed for 'https://github.com/int0x03/samples.git/'
  4. 所以要先添加 personal access token 然后把 token 做密码来访问. 具体路径是: github.com 右上角点头像, 进入 Settints -> Developer setttings -> Personal access tokens -> token classic 添加.
  5. 添加完成, 选择生产 token, 这个token 可以设置终止日期的.
  6. 可选把 token 放到 iTerm 的password 里面.

python Async Sync 函数

这篇是比较好的一篇文章: https://docs.chainlit.io/guides/sync-async

  1. sync -> sync -> ok
  2. sync -> async -> asyncio.run() | asyncio.get_event_loop().run_until_complete(async_func())
  3. async -> sync -> ok | await asyncio.to_thread(sync_task)
  4. async -> async -> await
  5. Python 的 asyncio 库是标准库中用于编写异步 I/O 代码的核心框架,自 Python 3.4 引入后成为构建高性能网络应用和并发程序的重要工具. asyncio 是 Python 异步编程的基石,适合需要高效处理 I/O 并发的场景。通过事件循环和协程机制,开发者能以同步风格编写异步代码,显著提升程序性能.
  6. Asyncer 是一个用于简化 Python 异步编程的第三方库,它通过更优雅的语法和高级功能让异步代码更易编写和维护. 相比标准库 asyncio,Asyncer 通过更高层次的抽象降低了异步编程门槛,适合需要快速开发高并发应用的场景.
  7. Python 的 anyio 库是一个​​跨异步框架的统一接口库​​,旨在简化异步编程的复杂性,同时兼容主流异步模型(如 asyncio 和 trio)。