2026-07-22 21:38:36
去年夏天,我们从剑桥出发,自驾前往科茨沃尔德,当天往返,前后开车约八个小时。原本计划住上一晚,却因行程和天气变化改成了一日游。我们在当地乡村酒吧吃过午饭后,陆续游览了附近三个小镇,欣赏河流、石桥、古老石屋和宁静的乡村风景。虽然行程有些匆忙,但阳光下的科茨沃尔德依然给我们留下了美好而难忘的回忆。 一日往返科茨沃尔德:八小时车程,换一场英国乡村的夏日漫游 盛夏漫游科茨沃尔德:一场匆忙却美好的乡村之旅 从剑桥到科茨沃尔德:一天走过三个英国小镇 科茨沃尔德一日游:石屋、小河与英伦夏日 八小时自驾,只为看一眼科茨沃尔德的夏天 匆匆一天,慢慢回味:我们的科茨沃尔德之行 从照片里的河流、石桥、广场和石屋判断,当天去的三个地方最可能是: Bourton-on-the-Water 中文常译为“水上伯顿”。照片中有小河、石桥和很多水鸟的地方,基本可以确定是这里。 Stow-on-the-Wold 中文常译为“斯托昂泽沃尔德”。照片中的开阔广场、停车区和蜂蜜色石屋街道,很像这里的 Market Square。 Lower Slaughter 中文常译为“下斯劳特村”或“下斯劳特”。照片中的林荫小路、安静的石屋和溪流,很可能是这里;当地的小河叫 River Eye。不过也不排除其中部分照片拍摄于附近的 Upper Slaughter。 Bourton-on-the-Water:小河、低矮石桥、河边草地和水鸟,都是水上伯顿最典型的景观。 River Windrush:穿过水上伯顿中心的小河。 The Model Village:照片中成片的微型石屋和红褐色屋顶,是水上伯顿著名的九分之一比例模型村,位于 The Old New Inn 后面。 The Cotswold Pottery:照片里的橱窗上直接写着这个名字,地址位于 Bourton-on-the-Water 的 Clapton Row。 The Chestnut Tree:模型村照片中的建筑招牌,在水上伯顿也确实有这家茶室和餐厅。去年夏天,2025年7月26日,我们从剑桥出发,开车前往科茨沃尔德(Cotswolds)。
当天我们游览了科茨沃尔德附近的三个小镇,其中印象最深的是有“小威尼斯”之称的水上伯顿(Bourton-on-the-Water)。清澈的温德拉什河(River Windrush)穿过小镇,河上分布着几座低矮的石桥。我们还参观了当地著名的模型村(The Model Village),并经过了科茨沃尔德陶艺店(The Cotswold Pottery)。原本的计划是在那里住一晚,安排一个相对轻松的周末旅行。不过后来行程临时有了变化,印象中好像是因为天气预报不太稳定,最后决定只选天气晴朗的那一天过去,当天往返。 现在回想起来,这个决定多少有点“勇猛”:从剑桥开到科茨沃尔德,再在几个小镇之间辗转,晚上还要原路返回,一天下来足足开了八个小时。真正留给我们吃饭、散步和看风景的时间,反而没有在车上的时间长。
下面按截图中图片的顺序,从左到右、从上到下编号,共 52 张: 第一排 在科茨沃尔德的乡村酒吧翻看午餐菜单。 长途驾驶后,先用两杯冰凉的啤酒稍作休息。 乡村酒吧里分量十足的英式午餐。 色彩丰富、摆盘精致的一道主菜。 简单而精致的乡村酒吧料理。 午餐桌上的清爽沙拉与时蔬。 从剑桥一路开往科茨沃尔德,漫长旅程仍在继续。 沿着绿篱环绕的小路,走进宁静的英国乡村。 树荫覆盖的乡间步道,为盛夏带来一丝清凉。 坐在户外餐桌旁享用午餐的孩子们。 长途旅行之后,终于可以安心吃饭了。 蜂蜜色石屋排列在古老的村庄街道两旁。 绿树掩映下,一条清澈的小河静静流过。 穿过树林与石屋之间的宁静乡间公路。 河边展示的科茨沃尔德乡村风景画。 蓝天白云下,古老石屋构成了典型的英伦乡村画面。 第二排 水鸟在清澈的河水中悠闲游动。 一家人坐在树荫下休息,享受难得的悠闲时光。 盛夏阳光下,科茨沃尔德乡村中的蓝裙倩影。 坐在乡村餐厅里,记录旅途中的轻松一刻。 漫步在安静的村庄街道上。 巨大的向日葵,为夏日旅程增添了明亮色彩。 古老石屋与乡村花园之间的夏日留影。 在户外茶座享用下午茶,感受英式乡村生活。 遮阳伞下的悠闲时光。 古老石墙和木门前的旅行留影。 蓝裙与蜂蜜色石屋,构成温柔的英伦夏日画面。 古老石窗与爬藤植物旁的乡村人像。 一组记录科茨沃尔德夏日之旅的照片。 安静坐落在路边的传统英式乡村住宅。 阳光透过茂密树冠,洒向林间小路。 绿树与灌木环绕的科茨沃尔德乡间公路。 第三排 白色小屋与蜂蜜色石墙相依而立。 游客穿行在古老而热闹的村庄街道上。 清澈的小河穿过茂密的树林。 绿意环绕的河流,静静倒映着岸边树木。 孩子们坐在河边长椅上休息。 乡村酒吧门前的黑板菜单与古老石墙。 石桥横跨温德拉什河,连接着水上伯顿的两岸。 水鸟在温德拉什河中悠闲觅食。 河水清澈见底,水鸟在浅滩间缓缓游动。 树影下的河面安静而幽深。 河中的水鸟与斑驳倒影。 阳光照在流动的河水上,泛起细碎波光。 水上伯顿模型村中的微缩石屋建筑。 盛开的向日葵,为旅程带来明亮的夏日气息。 The Cotswold Pottery 橱窗里陈列的手工陶器。 蜂蜜色石屋构成了科茨沃尔德最经典的村庄景色。 第四排 阳光下泛着金黄色光泽的树叶。 夏日的水上伯顿,河岸边坐满了游客。 绿树与藤蔓环绕的传统英式庄园住宅。 树荫深处的古老石屋,安静地守在水边。
中午到了在小酒馆歇息/媳妇[/caption]
[caption id="attachment_72206" align="alignnone" width="2048"]
河中的水鸟与斑驳倒影。[/caption]
[caption id="attachment_72205" align="alignnone" width="2048"]
阳光照在流动的河水上,泛起细碎波光。[/caption]
[caption id="attachment_72204" align="alignnone" width="2048"]
水上伯顿模型村中的微缩石屋建筑。[/caption]
[caption id="attachment_72203" align="alignnone" width="1365"]
乡村酒吧门前的黑板菜单与古老石墙。[/caption]
[caption id="attachment_72202" align="alignnone" width="2048"]
娃走累了就坐在石头上歇会儿。[/caption]
[caption id="attachment_72201" align="alignnone" width="2048"]
绿树与灌木环绕的科茨沃尔德乡间公路。[/caption]
[caption id="attachment_72200" align="alignnone" width="2048"]
阳光透过茂密树冠,洒向林间小路。[/caption]
[caption id="attachment_72199" align="alignnone" width="2048"]
开了三个多小时终于到了小镇,在镇中心的小广场上。[/caption]
[caption id="attachment_72198" align="alignnone" width="2048"]
蓝裙与蜂蜜色石屋,构成温柔的英伦夏日画面。[/caption]
[caption id="attachment_72197" align="alignnone" width="2048"]
古老石墙和木门前的旅行留影。[/caption]
[caption id="attachment_72196" align="alignnone" width="2048"]
小树林鸟儿叽叽喳喳[/caption]
[caption id="attachment_72195" align="alignnone" width="1001"]
在户外茶座享用下午茶,感受英式乡村生活。在酒巴的后院里,吃着简易三明制[/caption]
[caption id="attachment_72194" align="alignnone" width="1024"]
古老石屋与乡村花园之间的夏日留影。[/caption]
[caption id="attachment_72193" align="alignnone" width="1024"]
巨大的向日葵,为夏日旅程增添了明亮色彩。[/caption]
[caption id="attachment_72192" align="alignnone" width="1024"]
漫步在安静的村庄街道上。[/caption]
[caption id="attachment_72191" align="alignnone" width="1024"]
坐在乡村餐厅里,记录旅途中的轻松一刻。[/caption]
[caption id="attachment_72190" align="alignnone" width="1024"]
盛夏阳光下,科茨沃尔德乡村中的蓝裙倩影。[/caption]
[caption id="attachment_72189" align="alignnone" width="2048"]
一家人坐在树荫下休息,享受难得的悠闲时光。[/caption]
[caption id="attachment_72188" align="alignnone" width="2048"]
水鸟/鸭子在清澈的河水中悠闲游动。[/caption]
[caption id="attachment_72187" align="alignnone" width="2048"]
蓝天白云下,古老石屋构成了典型的英伦乡村画面。[/caption]
[caption id="attachment_72186" align="alignnone" width="2048"]
河边展示的科茨沃尔德乡村风景画。[/caption]
[caption id="attachment_72185" align="alignnone" width="1365"]
穿过树林与石屋之间的宁静乡间公路。[/caption]
[caption id="attachment_72184" align="alignnone" width="2048"]
绿树掩映下,一条清澈的小河静静流过。[/caption]
[caption id="attachment_72183" align="alignnone" width="1365"]
蜂蜜色石屋排列在古老的村庄街道两旁。[/caption]
[caption id="attachment_72182" align="alignnone" width="1365"]
长途旅行之后,终于可以安心吃饭了。[/caption]
[caption id="attachment_72181" align="alignnone" width="2048"]
坐在户外餐桌旁享用午餐的孩子们。[/caption]
[caption id="attachment_72180" align="alignnone" width="2048"]
树荫覆盖的乡间步道,为盛夏带来一丝清凉。[/caption]
[caption id="attachment_72179" align="alignnone" width="2048"]
沿着绿篱环绕的小路,走进宁静的英国乡村。[/caption]
[caption id="attachment_72178" align="alignnone" width="2048"]
从剑桥一路开往科茨沃尔德,漫长旅程仍在继续。开车8小时 338英理,刚到家,累死了[/caption]
[caption id="attachment_72177" align="alignnone" width="2048"]
午餐桌上的清爽沙拉与时蔬。[/caption]
[caption id="attachment_72176" align="alignnone" width="2048"]
简单而精致的乡村酒吧料理。[/caption]
[caption id="attachment_72175" align="alignnone" width="2048"]
色彩丰富、摆盘精致的一道主菜。[/caption]
[caption id="attachment_72174" align="alignnone" width="2048"]
乡村酒吧里分量十足的英式午餐。[/caption]
[caption id="attachment_72173" align="alignnone" width="2048"]
长途驾驶后,先用两杯冰凉的啤酒稍作休息。[/caption]
[caption id="attachment_72172" align="alignnone" width="864"]
在科茨沃尔德的乡村酒吧翻看午餐菜单。[/caption]
[caption id="attachment_72171" align="alignnone" width="1536"]
媳妇朋友圈/九宫格/摄影[/caption]
[caption id="attachment_72170" align="alignnone" width="2048"]
媳妇在小石屋前留影[/caption]
[caption id="attachment_72169" align="alignnone" width="1024"]
古老石窗与爬藤植物旁的乡村人像。[/caption]
[caption id="attachment_72168" align="alignnone" width="1024"]
媳妇/古老石墙和木门前的旅行留影。[/caption]
[caption id="attachment_72218" align="alignnone" width="2048"]
树荫深处的古老石屋,安静地守在水边。[/caption]
[caption id="attachment_72217" align="alignnone" width="1365"]
绿树与藤蔓环绕的传统英式庄园住宅。[/caption]
[caption id="attachment_72216" align="alignnone" width="2048"]
树荫深处的古老石屋,安静地守在水边。[/caption]
[caption id="attachment_72215" align="alignnone" width="2048"]
夏日的水上伯顿,河岸边坐满了游客。[/caption]
[caption id="attachment_72214" align="alignnone" width="1365"]
阳光下泛着金黄色光泽的树叶。[/caption]
[caption id="attachment_72213" align="alignnone" width="2048"]
古老的小城镇[/caption]
[caption id="attachment_72212" align="alignnone" width="1365"]
Cotswold Pottery 科茨沃尔德陶器/街上看到的 The Cotswold Pottery 橱窗里陈列的手工陶器。[/caption]
[caption id="attachment_72211" align="alignnone" width="2048"]
盛开的向日葵,为旅程带来明亮的夏日气息。[/caption]
[caption id="attachment_72210" align="alignnone" width="2048"]
小城镇上有名的 The Chestnut Tree[/caption]
[caption id="attachment_72209" align="alignnone" width="2048"]
狗子很开心的玩水[/caption]
[caption id="attachment_72208" align="alignnone" width="2048"]
河水清澈见底,水鸟在浅滩间缓缓游动。[/caption]
[caption id="attachment_72207" align="alignnone" width="2048"]
河里的天鹅[/caption]
[show_posts keyword="旅游"]
2026-07-21 23:44:43
一个人的生命力,往往藏在食欲、性欲、表达欲和分享欲之中。它们代表着我们是否还对生活保持兴趣,是否愿意感受快乐、记录经历、亲近他人。随着年龄增长,欲望会发生变化,但真正重要的并不是一味放纵,而是在节制与热情之间找到平衡。人生不过三万多天,不必人人都去改变世界;能够认真生活、陪伴家人、记录美好,并在年老时回望过去,觉得这一生值得,便已足够。
教媳妇PowerBI - 可能得等到年老的时候才有时间来剪辑这个视频。[/caption]
一个人愿意讲,另一个人愿意听,也是一种生活里的默契。
2026-07-20 23:00:51
本文介绍如何利用定积分 [math]\pi=\int_0^1\frac{4}{1+x^2},dx[/math] 近似计算圆周率 Pi。文章先解释该积分为什么等于 Pi,然后介绍梯形法则和中点法则的数学原理,并给出 Python 与 C++ 的单线程、多线程实现。最后进一步说明如何将积分区间划分到五个分布式计算节点上,并比较不同实现方式的精度、性能、并行能力和适用场景。[caption id="attachment_72138" align="alignnone" width="449"]
定积分计算PI[/caption]
圆周率 Pi,通常写作 [math]\pi[/math],广泛出现在数学、物理、工程、统计学和计算机科学中。虽然大多数编程语言都已经提供了内置的 Pi 常量,但亲自计算 Pi 仍然是学习微积分、数值积分、多线程和分布式计算的一个很好的学习的例子。
本文将通过下面这个定积分计算 Pi:
[math]
\pi=\int_0^1\frac{4}{1+x^2}dx
[/math]
本文将介绍:
| 方法 | 函数取值位置 | 公式 |
|---|---|---|
| 梯形法则 | 每个区间的边界 | [math]h\left[\frac{f(a)+f(b)}{2}+\sum_{i=1}^{N-1}f(a+ih)\right][/math] |
| 中点法则 | 每个区间的中心 | [math]h\sum_{i=0}^{N-1}f\left(a+\left(i+\frac12\right)h\right)[/math] |
import math
import time
def calculate_pi(total_steps: int) -> float:
if total_steps <= 0:
raise ValueError("total_steps 必须为正数")
step_width = 1.0 / total_steps
total = 0.0
for step in range(total_steps):
midpoint = (step + 0.5) * step_width
total += 4.0 / (1.0 + midpoint * midpoint)
return total * step_width
def main() -> None:
total_steps = 25_000_000
started_at = time.perf_counter()
pi_estimate = calculate_pi(total_steps)
elapsed_seconds = time.perf_counter() - started_at
absolute_error = abs(pi_estimate - math.pi)
print(f"Pi 估算值: {pi_estimate:.15f}")
print(f"Pi 参考值: {math.pi:.15f}")
print(f"绝对误差: {absolute_error:.3e}")
print(f"运行时间: {elapsed_seconds:.3f} 秒")
if __name__ == "__main__":
main()
核心计算代码是:
midpoint = (step + 0.5) * step_width
total += 4.0 / (1.0 + midpoint * midpoint)
第一行计算当前小区间的中点:
[math]
x_i=\left(i+\frac12\right)h
[/math]
第二行计算函数值:
[math]
f(x_i)=\frac{4}{1+x_i^2}
[/math]
在累加所有函数值之后,再乘以每个小区间的宽度:
return total * step_width
这在数学上对应:
[math]
h\sum_{i=0}^{N-1}f(x_i)
[/math]
import math
import time
def calculate_pi_trapezoidal(total_steps: int) -> float:
if total_steps <= 0:
raise ValueError("total_steps 必须为正数")
h = 1.0 / total_steps
def f(x: float) -> float:
return 4.0 / (1.0 + x * x)
total = 0.5 * (f(0.0) + f(1.0))
for step in range(1, total_steps):
x = step * h
total += f(x)
return total * h
def main() -> None:
total_steps = 25_000_000
started_at = time.perf_counter()
pi_estimate = calculate_pi_trapezoidal(total_steps)
elapsed_seconds = time.perf_counter() - started_at
absolute_error = abs(pi_estimate - math.pi)
print(f"Pi 估算值: {pi_estimate:.15f}")
print(f"Pi 参考值: {math.pi:.15f}")
print(f"绝对误差: {absolute_error:.3e}")
print(f"运行时间: {elapsed_seconds:.3f} 秒")
if __name__ == "__main__":
main()
两个端点的贡献通过下面的代码计算:
total = 0.5 * (f(0.0) + f(1.0))
这对应:
[math]
\frac{f(0)+f(1)}{2}
[/math]
循环计算所有内部点的函数值之和:
for step in range(1, total_steps):
x = step * h
total += f(x)
这对应:
[math]
\sum_{i=1}^{N-1}f(ih)
[/math]
最后再乘以 [math]h[/math]:
return total * h
| 工作线程 | 起始步骤 | 结束步骤 | 步骤数量 |
|---|---|---|---|
| 0 | 0 | 5,000,000 | 5,000,000 |
| 1 | 5,000,000 | 10,000,000 | 5,000,000 |
| 2 | 10,000,000 | 15,000,000 | 5,000,000 |
| 3 | 15,000,000 | 20,000,000 | 5,000,000 |
| 4 | 20,000,000 | 25,000,000 | 5,000,000 |
ThreadPoolExecutor 实现:
import math
import time
from concurrent.futures import ThreadPoolExecutor
def calculate_partial_sum(
start_step: int,
end_step: int,
step_width: float,
) -> float:
partial_sum = 0.0
for step in range(start_step, end_step):
midpoint = (step + 0.5) * step_width
partial_sum += 4.0 / (1.0 + midpoint * midpoint)
return partial_sum
def calculate_pi_multithreaded(
total_steps: int,
worker_count: int,
) -> float:
if total_steps <= 0:
raise ValueError("total_steps 必须为正数")
if worker_count <= 0:
raise ValueError("worker_count 必须为正数")
step_width = 1.0 / total_steps
ranges = []
for worker_id in range(worker_count):
start_step = total_steps * worker_id // worker_count
end_step = total_steps * (worker_id + 1) // worker_count
ranges.append((start_step, end_step))
with ThreadPoolExecutor(max_workers=worker_count) as executor:
futures = [
executor.submit(
calculate_partial_sum,
start_step,
end_step,
step_width,
)
for start_step, end_step in ranges
]
partial_sums = [
future.result()
for future in futures
]
return sum(partial_sums) * step_width
def main() -> None:
total_steps = 25_000_000
worker_count = 5
started_at = time.perf_counter()
pi_estimate = calculate_pi_multithreaded(
total_steps,
worker_count,
)
elapsed_seconds = time.perf_counter() - started_at
absolute_error = abs(pi_estimate - math.pi)
print(f"工作线程数: {worker_count}")
print(f"Pi 估算值: {pi_estimate:.15f}")
print(f"Pi 参考值: {math.pi:.15f}")
print(f"绝对误差: {absolute_error:.3e}")
print(f"运行时间: {elapsed_seconds:.3f} 秒")
if __name__ == "__main__":
main()
每个工作线程计算一个部分和:
[math]
S_r=
\sum_{i=\text{start}_r}^{\text{end}_r-1}
\frac{4}{1+x_i^2}
[/math]
主线程再把这些结果合并起来:
[math] \pi\approx h(S_0+S_1+S_2+S_3+S_4) [/math]
ProcessPoolExecutormultiprocessing 模块
#include <chrono>
#include <cmath>
#include <cstdint>
#include <iomanip>
#include <iostream>
#include <stdexcept>
double calculate_pi(std::uint64_t total_steps) {
if (total_steps == 0) {
throw std::invalid_argument(
"total_steps 必须为正数"
);
}
const double step_width =
1.0 / static_cast<double>(total_steps);
double total = 0.0;
for (
std::uint64_t step = 0;
step < total_steps;
++step
) {
const double midpoint =
(static_cast<double>(step) + 0.5)
* step_width;
total += 4.0 / (1.0 + midpoint * midpoint);
}
return total * step_width;
}
int main() {
constexpr std::uint64_t total_steps = 25'000'000;
const auto started_at =
std::chrono::steady_clock::now();
const double pi_estimate =
calculate_pi(total_steps);
const auto finished_at =
std::chrono::steady_clock::now();
const double elapsed_seconds =
std::chrono::duration<double>(
finished_at - started_at
).count();
const double reference_pi = std::acos(-1.0);
const double absolute_error =
std::abs(pi_estimate - reference_pi);
std::cout
<< std::fixed
<< std::setprecision(15);
std::cout
<< "Pi 估算值: "
<< pi_estimate
<< '\n';
std::cout
<< "Pi 参考值: "
<< reference_pi
<< '\n';
std::cout
<< std::scientific
<< std::setprecision(3);
std::cout
<< "绝对误差: "
<< absolute_error
<< '\n';
std::cout
<< std::fixed
<< std::setprecision(3);
std::cout
<< "运行时间: "
<< elapsed_seconds
<< " 秒\n";
return 0;
}
使用优化选项进行编译:
g++ -O3 -std=c++17 pi_single.cpp -o pi_single
运行程序:
./pi_single
-O3 选项会启用较为激进的编译器优化。如果不启用优化,程序的运行速度可能会慢很多。
#include <chrono>
#include <cmath>
#include <cstdint>
#include <iomanip>
#include <iostream>
#include <stdexcept>
double f(double x) {
return 4.0 / (1.0 + x * x);
}
double calculate_pi_trapezoidal(
std::uint64_t total_steps
) {
if (total_steps == 0) {
throw std::invalid_argument(
"total_steps 必须为正数"
);
}
const double h =
1.0 / static_cast<double>(total_steps);
double total =
0.5 * (f(0.0) + f(1.0));
for (
std::uint64_t step = 1;
step < total_steps;
++step
) {
const double x =
static_cast<double>(step) * h;
total += f(x);
}
return total * h;
}
int main() {
constexpr std::uint64_t total_steps = 25'000'000;
const auto started_at =
std::chrono::steady_clock::now();
const double pi_estimate =
calculate_pi_trapezoidal(total_steps);
const auto finished_at =
std::chrono::steady_clock::now();
const double elapsed_seconds =
std::chrono::duration<double>(
finished_at - started_at
).count();
const double reference_pi = std::acos(-1.0);
const double absolute_error =
std::abs(pi_estimate - reference_pi);
std::cout
<< std::fixed
<< std::setprecision(15);
std::cout
<< "Pi 估算值: "
<< pi_estimate
<< '\n';
std::cout
<< "Pi 参考值: "
<< reference_pi
<< '\n';
std::cout
<< std::scientific
<< std::setprecision(3);
std::cout
<< "绝对误差: "
<< absolute_error
<< '\n';
std::cout
<< std::fixed
<< std::setprecision(3);
std::cout
<< "运行时间: "
<< elapsed_seconds
<< " 秒\n";
return 0;
}
使用下面的命令编译:
g++ -O3 -std=c++17 pi_trapezoidal.cpp -o pi_trapezoidal
#include <chrono>
#include <cmath>
#include <cstdint>
#include <iomanip>
#include <iostream>
#include <numeric>
#include <stdexcept>
#include <thread>
#include <vector>
double calculate_partial_sum(
std::uint64_t start_step,
std::uint64_t end_step,
double step_width
) {
double partial_sum = 0.0;
for (
std::uint64_t step = start_step;
step < end_step;
++step
) {
const double midpoint =
(static_cast<double>(step) + 0.5)
* step_width;
partial_sum +=
4.0 / (1.0 + midpoint * midpoint);
}
return partial_sum;
}
double calculate_pi_multithreaded(
std::uint64_t total_steps,
std::size_t worker_count
) {
if (total_steps == 0) {
throw std::invalid_argument(
"total_steps 必须为正数"
);
}
if (worker_count == 0) {
throw std::invalid_argument(
"worker_count 必须为正数"
);
}
const double step_width =
1.0 / static_cast<double>(total_steps);
std::vector<std::thread> workers;
std::vector<double> partial_sums(
worker_count,
0.0
);
workers.reserve(worker_count);
for (
std::size_t worker_id = 0;
worker_id < worker_count;
++worker_id
) {
const std::uint64_t start_step =
total_steps
* worker_id
/ worker_count;
const std::uint64_t end_step =
total_steps
* (worker_id + 1)
/ worker_count;
workers.emplace_back(
[
&,
worker_id,
start_step,
end_step
]() {
partial_sums[worker_id] =
calculate_partial_sum(
start_step,
end_step,
step_width
);
}
);
}
for (std::thread& worker : workers) {
worker.join();
}
const double combined_sum =
std::accumulate(
partial_sums.begin(),
partial_sums.end(),
0.0
);
return combined_sum * step_width;
}
int main() {
constexpr std::uint64_t total_steps = 25'000'000;
constexpr std::size_t worker_count = 5;
const auto started_at =
std::chrono::steady_clock::now();
const double pi_estimate =
calculate_pi_multithreaded(
total_steps,
worker_count
);
const auto finished_at =
std::chrono::steady_clock::now();
const double elapsed_seconds =
std::chrono::duration<double>(
finished_at - started_at
).count();
const double reference_pi = std::acos(-1.0);
const double absolute_error =
std::abs(pi_estimate - reference_pi);
std::cout
<< "工作线程数: "
<< worker_count
<< '\n';
std::cout
<< std::fixed
<< std::setprecision(15);
std::cout
<< "Pi 估算值: "
<< pi_estimate
<< '\n';
std::cout
<< "Pi 参考值: "
<< reference_pi
<< '\n';
std::cout
<< std::scientific
<< std::setprecision(3);
std::cout
<< "绝对误差: "
<< absolute_error
<< '\n';
std::cout
<< std::fixed
<< std::setprecision(3);
std::cout
<< "运行时间: "
<< elapsed_seconds
<< " 秒\n";
return 0;
}
使用线程支持和优化选项进行编译:
g++ -O3 -std=c++17 -pthread pi_multithreaded.cpp -o pi_multithreaded
运行程序:
./pi_multithreaded
double partial_sum = 0.0;
线程不会在每一次循环中都修改同一个共享的全局变量。
如果所有线程不断更新同一个共享变量,程序就需要使用互斥锁或者原子操作。
这会引入同步开销和线程竞争,可能抵消多线程带来的性能优势。
因此,程序采用了类似 MapReduce 的模式:
import os
rank = int(os.environ["RANK"])
world_size = int(os.environ["WORLD_SIZE"])
start_step = total_steps * rank // world_size
end_step = total_steps * (rank + 1) // world_size
每个 Rank 只计算自己负责的区间:
partial_sum = 0.0
for step in range(start_step, end_step):
midpoint = (step + 0.5) * step_width
partial_sum += 4.0 / (
1.0 + midpoint * midpoint
)
对于五个 Rank,最终结果为:
[math] \pi\approx h(S_0+S_1+S_2+S_3+S_4) [/math]
在前面的 Singularity 示例中,每个 Rank 都会写入一个结果文件:
rank-0.json
rank-1.json
rank-2.json
rank-3.json
rank-4.json
Rank 0 等待全部五个文件生成,然后读取每个文件中的部分和,将它们相加,最终得到 Pi 的估算值。
这个过程实现了三种分布式操作:
| 实现方式 | 执行模型 | 真正的 CPU 并行 | 预期性能 |
|---|---|---|---|
| Python 单线程 | 一个 Python 线程 | 否 | 实现简单,但速度相对较慢 |
| Python 多线程 | 多个 Python 线程 | 通常不能,因为受到 GIL 限制 | 通常不会比单线程更快 |
| C++ 单线程 | 一个本地线程 | 否 | 通常比纯 Python 快很多 |
| C++ 多线程 | 多个本地线程 | 是 | 通常是本地计算中最快的版本 |
| 五节点分布式计算 | 多个独立工作进程 | 是 | 可以扩展,但存在启动和通信开销 |
任务分割 ---> 计算部分结果 ---> 合并计算最终结果[math] \boxed{ \text{Tasks Partition} \rightarrow \text{Partial Computed Result} \rightarrow \text{Merged Final Result} } [/math] 虽然计算 Pi 只是一个简单的示例,但同样的 MapReduce 思想广泛应用于科学模拟、机器学习、数据处理、图形渲染、金融建模和大规模分布式系统中。 [show_file file="/var/www/wp-post-common/justyy.com/math.php"] 英文:Calculating Pi with Numerical Integration in Python and C [show_posts keyword="数学"]
2026-07-18 23:01:35
弟弟Ryan学钢琴曲一直很快。他的钢琴老师说,一般的新曲子,他大约两个星期就能学下来。不过,他有一个很明显的特点:只愿意认真弹自己喜欢的曲子。如果一首曲子没有打动他,想让他反复练习并不容易;但只要遇到真正喜欢的作品,他便会主动坐到钢琴前,一遍又一遍地弹。 贝多芬的《第十四钢琴奏鸣曲》,也就是大家熟悉的《月光奏鸣曲》,正好是他很喜欢的一首。 Ryan以前在小学的Assembly上表演过几次钢琴。对小学生来说,能够独自站在全校师生面前演奏,本身就需要不少勇气。更让他的同学们羡慕的是,他弹琴时通常不需要看谱,坐下来便可以直接开始演奏。因此,不少同学都觉得他弹琴特别厉害,对他很是崇拜。 当然,他的演奏也不是没有问题。 最明显的一点,就是弹得有些快。我在YouTube上听过一些《月光奏鸣曲》的完整演奏,很多版本大约需要十四分钟,而Ryan只用了十一分多钟就弹完了。速度快,听起来会更加紧凑,也更有冲击力,但这首作品并不只是对技巧和速度的考验,尤其是第一乐章,还需要足够的呼吸、层次和情绪控制。 对于孩子来说,能够完整地弹下来已经很不容易;但如果想继续向更高水平发展,下一步便不能只追求“弹得下来”,而是要学会控制节奏,理解不同乐章的性格,并把音乐中的情绪真正表达出来。 这首曲子也给我们留下过一段很特别的旅行记忆。 有一次全家去比利时旅行,我们来到根特市中心。那里恰好有一架可以演奏的钢琴,Ryan坐下来后,直接弹起了贝多芬的《月光奏鸣曲》。 琴声在市中心响起后,很快吸引了一些路人。有人停下脚步安静地听,也有人拿出手机拍照和录像。原本只是旅途中一次随兴的演奏,慢慢变成了一场小小的街头音乐会。 他连续弹了十一分多钟,我就在旁边一直举着相机录像。等他终于弹完,我的手臂都已经有些酸了。别人看到的是一个孩子在异国城市里从容地演奏,而我在镜头后面最直接的感受却是:这首曲子怎么还没有弹完?举相机也是一项体力活啊!想到这里,还是忍不住笑了出来。 整段演奏中,我最喜欢的是视频大约5分15秒以后进入的第三乐章。 如果说第一乐章像安静月光下的沉思,那么第三乐章则完全换了一种性格:急促、激烈,充满力量。音符不断向前推进,对手指速度、力度控制和整体稳定性都有很高的要求。Ryan自己也很喜欢这一部分,弹起来明显更加投入。 据说《月光奏鸣曲》也是他以后准备Diploma级别考试时可能涉及的曲目之一。现在的演奏当然还有不少可以改进的地方,例如速度需要更加克制,细节和情绪还可以继续打磨。不过,能够在这个年龄完整背谱演奏,并且愿意在学校礼堂和陌生城市的街头主动分享音乐,已经是一件值得记录的事情。 学钢琴并不只是为了通过考试,也不只是为了拿到某个等级证书。 真正珍贵的,是孩子能够遇到自己喜欢的音乐,并愿意投入时间,把它练下来、记在心里,最后在某个意想不到的地方演奏给别人听。 多年以后,我们可能不会记得他当时到底弹快了多少,也不会太在意那次演奏究竟是否完美。但我们大概会记得,在比利时根特的市中心,一个孩子坐在钢琴前,连续弹了十一分钟的贝多芬;路人停下来拍照,而我站在旁边举着相机,累得手臂发酸,却又舍不得错过任何一个画面。 视频:油管/Youtube | B站/小破站 | 微博视频 | 公众号视频 | 西瓜视频 | 微信视频号 | X/推特 | 小红书 | Facebook 视频:油管/Youtube | B站/小破站 | 微博视频 | 西瓜视频 | 微信视频号 | X/推特 | 小红书 | Facebook 视频:油管/Youtube | B站/小破站 | 微博视频 | 西瓜视频 | 微信视频号 | X/推特 | 小红书 | Facebook [show_file file="/var/www/wp-post-common/justyy.com/kid-learn-music.php"]
2026-07-16 22:53:29
前几年孩子们还小的时候,我们常利用假期给他们安排一些有意义的活动,Holiday Orchestra 就是其中之一。孩子们在几天时间里参加集中排练,学习与不同乐器和声部配合,并在最后一天登台演出。2023年12月30日,大娃作为大提琴手、二娃作为小提琴手,在剑桥 West Road Concert Hall 参加了这场音乐会。相比最终的演出,我更看重孩子们在排练过程中获得的参与感、责任感和团队协作经验。[caption id="attachment_72115" align="alignnone" width="1279"]
乐团表演节目单[/caption]
前几年,孩子们还小的时候,每逢学校放假,我们通常都会给他们安排一些活动。一方面是不希望他们整个假期都待在家里,另一方面也想让他们接触一些课堂之外的体验。Holiday Orchestra 就是我们当时比较喜欢的一项活动。
Holiday Orchestra 是面向青少年音乐学习者组织的假期乐团活动,通常会根据孩子的年龄、演奏水平和所学乐器,把他们分到不同的乐团中。活动一般持续几天,孩子们每天到现场参加排练,在老师和指挥的带领下学习新的曲目,与其他乐手一起磨合,最后一天再举行一场正式演出,向家长展示这几天的排练成果。
我一直很喜欢这种形式。首先,它能让孩子们在假期里有事情做,而不是每天无所事事地待在家里。更重要的是,参加乐团和独自练琴完全不同。一个人在家练习时,只需要关注自己的音准、节奏和技巧;进入乐团之后,还要学会听指挥、跟随节拍、配合其他声部,并且知道什么时候应该突出,什么时候应该收敛。
这种经历可以让孩子真正体会到团队协作。一个人的演奏再出色,如果不能与其他乐手配合,也很难完成一场好的乐团表演。每个声部看起来只是整体中的一小部分,但所有乐器组合在一起,才能形成完整而丰富的音乐。
2023年12月30日,Holiday Orchestra 的结业演出在剑桥 West Road 的 The University Concert Hall 举行,地址是 11 West Road, Cambridge CB3 9DP。那一次,大娃参加的是大提琴声部,二娃参加的是小提琴声部。两个孩子学习的乐器不同,在乐团中的位置和作用也不一样,但都需要在短短几天内熟悉曲目,并与其他孩子完成合奏。
孩子们在小学阶段也参加过类似的学校乐团。一般来说,学习小提琴的孩子比较多,而学习大提琴的孩子相对少一些。大提琴体积大,携带起来不方便,价格通常也更高,可能正因为如此,在学校乐团里,大提琴手往往没有小提琴手那么多。
钢琴则是另一种情况。学习钢琴的孩子很多,但传统管弦乐团通常并不需要很多钢琴手,有些曲目甚至完全没有钢琴声部。即使需要,往往也只安排一位,因此竞争会比较激烈。相比之下,小提琴、大提琴、木管和铜管乐器在乐团中都有比较固定的声部和位置,也更容易让孩子获得长期参与合奏的机会。
弟弟小提琴[/caption]
[caption id="attachment_64201" align="alignnone" width="1365"]
老大大提琴 很帅气[/caption]
[caption id="attachment_64200" align="alignnone" width="2048"]
表演玩兄弟们在交流啥[/caption]
[caption id="attachment_64199" align="alignnone" width="1365"]
老大第二乐器是大提琴[/caption]
[caption id="attachment_64198" align="alignnone" width="2048"]
老二第二乐器是小提琴[/caption]
[caption id="attachment_64197" align="alignnone" width="1365"]
老二第二乐器是小提琴[/caption]
[caption id="attachment_64196" align="alignnone" width="2048"]
老二第二乐器是小提琴[/caption]