Go 内存模型:happens-before

C++ 有 std::atomic + 内存序(memory_order),Go 有更少的口子但边界更清楚——channel / Mutex / Once / atomic 各自建立 happens-before 一、简述 “变量加了锁就不会有竞态"这种直觉在并发世界里是危险的。真正需要回答的问题是:goroutine A 写了一个值,goroutine B 什么时候能确定读到它? Go 用一份官方文档——The Go Memory Model——精确规定了这些"什么时候”。它定义了 happens-before 关系:同步原语(channel、Mutex、WaitGroup、Once、atomic)在收发/加解锁/等待/原子操作之间建立序,从而保证内存可见性。理解它,你才知道为什么"不加同步 flag 读 bool"可能读到旧值,也才知道怎么写出不靠运气的并发程序。 核心要点:happens-before(HB)是"一个事件必须在另一个事件之前发生且其结果可见"的形式化保证。Go 的并发正确性全部建立在"你用了某个同步原语,它就给你对应的一条 HB 边"上。不用原语 = 没有边 = 编译器/硬件随便重排。 二、C++ 对照速查表 概念 C++ Go 关键差异 内存模型文档 [memory_order] 标准章节 The Go Memory Model(官方文档) C++ 需要你精确选 memory_order;Go 不给选项,只规定用了哪个同步原语获得哪条 HB 边 互斥锁 std::mutex 加锁/解锁 sync.Mutex Lock/Unlock C++ 还要额外配 atomic 才能安全读共享变量;Go 锁内一切可见 放松内存序 memory_order_relaxed 无对应概念 Go 只有两档:同步原语→对应 HB 保证,非同步→无保证 原子操作 std::atomic<T>(load/store/fetch等) sync/atomic Go 1.19 之前 atomic 只防撕裂不建 HB;1.19+ 视作同步点 初始化一次 std::once_flag sync.Once once.Do 完成后,所有 Do 返回点看到其写入 线程等待 std::thread::join sync.WaitGroup.Wait Wait 返回后看到各 Done 的写入 通信同步 mutex+cv 手写 channel 收发建立 HB,官方推荐优先 channel 主动读屏障 atomic_thread_fence 无直接对应 Go 用 atomic.Load/互斥代替 fence 悬而未决的"下次可见" 无 “稍后某个时刻可见"是合法但无保证 这是最常见的 bug 来源 数据竞争检测 TSan(第三方) go test -race 内建 干干净净的"竞态即报” 三、逐主题详解 3.1 你已经在 C++ 里见过的"序" C++ 的 memory_order 有三种强度:relaxed(无序)、acquire/release(只保证可见但不能越过)、seq_cst(全局一致)。Go 的模型把选择收敛成一条规则: ...

2026 年 10 月 13 日

Go 语言学习路线(C++ 开发者视角)

目录 1. 学习定位与目标 2. 从 C++ 到 Go 的思维转换 3. 阶段一:基础语法(第 1~2 周) 4. 阶段二:语言进阶(第 3~4 周) 5. 阶段三:并发编程(第 5~6 周) 6. 阶段四:工程化与工具链(第 7~8 周) 7. 阶段五:实战项目 8. 阶段六:深入内幕(进阶) 9. 推荐资源 10. 总结 1. 学习定位与目标 核心要点:Go 是为并发与工程效率而生的语言。它不是 C++ 的替代品,而是面向网络服务、云原生、分布式系统场景的现代选择。有 C++ 基础意味着你已经懂内存、指针、编译原理、并发难点——这些沉淀可以让你学 Go 的速度远超零基础者。 1.1 为什么学 Go 优点 说明 语法极简 没有类继承、泛型魔法(1.18 才加)、运算符重载,几小时能读完语法 编译快 没有模板实例化与复杂的 ODR,编译速度接近 C 语言 并发内建 goroutine + channel 是语言级能力,不是库 部署简单 静态编译,单个可执行文件,天然适合容器/云原生 标准库强大 网络、JSON、加密、压缩开箱即用,生态围绕服务端 工具链统一 官方格式化、测试、文档、依赖管理一条龙 1.2 对 C++ 开发者,Go 最大的"爽点" 没有内存泄漏焦虑:GC 自动回收,专注业务逻辑。 没有头文件割裂:一个 .go 文件即一个单元,import 即用。 没有 build 时间长:秒级编译。 没有模板错误地狱:泛型报错可读性好得多。 2. 从 C++ 到 Go 的思维转换 核心要点:这一节是全篇最关键的部分。C++ 功底在 Go 里既是资产也是包袱——资产是并发、内存模型、数据结构的直觉;包袱是"继承 + 异常 + 手动内存"的思维定式,需要刻意转换。 ...

2026 年 10 月 13 日

goroutine

Go 并发的基本执行单元:比线程轻量十倍的绿色协程,由 Go 运行时调度 一、C++ 对照速查表 概念 C++ Go 关键差异 并发执行单元 std::thread go func() goroutine 由运行时管理,不映射到 OS 线程 创建成本 ~1MB 栈 + 系统调用 ~2KB 栈,动态增长 goroutine 创建成本极低,可轻松创建上万 启动方式 std::thread t(func); t.join(); go func() Go 无需手动 join,用 channel/WaitGroup 同步 调度模型 1:1 映射 OS 线程 M:N 调度(GMP 模型) 运行时把 N 个 goroutine 调度到 M 个线程上 栈大小 固定(通常 1~8MB) 初始 2KB,按需增长/收缩 同一地址空间下可容纳海量 goroutine 终止方式 thread 析构或 detach 主 goroutine 结束则全部退出 goroutine 是 “fire and forget” 核心要点:goroutine 是 Go 并发的基石。创建一个 goroutine 只用一个 go 关键字,运行时自动处理调度、栈管理和线程复用。但 goroutine 之间需要通过 channel 或 sync 包 同步,否则会出现数据竞争。 ...

2026 年 10 月 13 日

Map(哈希表)

Go 的内置关联容器,键值对存储,底层是哈希表 一、概述 map 是 Go 的内置数据结构,用于存储键值对。它等价于 C++ 的 std::unordered_map——基于哈希表实现,不是红黑树(Go 没有 std::map 那样的有序容器)。 核心特性: 引用类型,赋值和传参共享底层数组 键必须支持 == 比较(可哈希) 遍历顺序不确定(哈希表特性) 并发读写会触发 fatal(不是 data race,直接崩程序) 二、C++ 对照速查表 Go C++ 说明 map[K]V 类型声明 std::unordered_map<K,V> 类型声明 声明一个 map 类型 m := map[string]int{} auto m = unordered_map<string,int>(); 字面量初始化 m := make(map[string]int) auto m = unordered_map<string,int>(); make 初始化 m := make(map[string]int, 64) auto m = unordered_map<string,int>(64); 预分配容量 m["key"] = 42 m["key"] = 42; 插入/修改 delete(m, "key") m.erase("key"); 删除键 val, ok := m["key"] auto it = m.find("key"); it != m.end() 查找(双返回值) len(m) m.size() 获取大小 nil map nullptr 的 map 未初始化的 map for k, v := range m for (auto& [k, v] : m) 遍历 无 std::map<K,V> Go 没有有序 map 三、声明与初始化 3.1 字面量初始化(最常用) package main import "fmt" func main() { // 字面量初始化:声明 + 赋值一步到位 ages := map[string]int{ "Alice": 30, "Bob": 25, "Carol": 28, } fmt.Println(ages) // map[Alice:30 Bob:25 Carol:28] // 空 map empty := map[string]int{} fmt.Println(len(empty)) // 0 } 3.2 make 初始化 // make 创建空 map,可选预分配容量(提高性能) scores := make(map[string]int) // 零值容量 scores2 := make(map[string]int, 64) // 预分配 64 个槽位 // 适用场景:已知大致大小,预分配可减少扩容次数 users := make(map[int]string, 1000) 3.3 nil map(未初始化) var m map[string]int // nil map,值为 nil fmt.Println(m) // map[] fmt.Println(len(m)) // 0 — len 对 nil map 返回 0 3.4 C++ 对照 Go C++ ages := map[string]int{"Alice": 30} auto ages = unordered_map<string,int>{{"Alice", 30}}; m := make(map[string]int) auto m = unordered_map<string,int>(); m := make(map[string]int, 64) auto m = unordered_map<string,int>(64); var m map[string]int (nil) unordered_map<string,int>* m = nullptr; 四、增删改查 4.1 增 / 改(赋值) m := map[string]int{} // 不存在的 key → 插入 m["apple"] = 5 m["banana"] = 3 // 已存在的 key → 修改 m["apple"] = 10 fmt.Println(m) // map[apple:10 banana:3] 4.2 查(双返回值 ok 模式) m := map[string]int{"x": 1, "y": 2} // 双返回值:值 + 是否存在(ok 模式) val, ok := m["x"] fmt.Println(val, ok) // 1 true val2, ok2 := m["z"] fmt.Println(val2, ok2) // 0 false(零值,不存在) // C++ 对比: // auto it = m.find("x"); // if (it != m.end()) { /* 找到 */ } 为什么需要 ok 模式? 如果值类型是 int,零值是 0,无法区分"值为 0"和"不存在"。ok 布尔值解决了这个问题。 ...

2026 年 10 月 13 日

OpenCV 4.10 + Visual Studio 环境配置:属性页手动配法

在 Visual Studio 项目里用属性页手动配置 OpenCV 4.10 的完整步骤:包含目录、库目录、链接器输入、环境变量,四步配好后用一段自检代码验证。(如果用 CMake 工程,则改为环境变量 + find_package 的方案,不用逐项目配属性页。) 一、配置步骤 1. 包含目录 项目属性 → VC++ 目录 → 包含目录,添加: <OpenCV安装目录>\build\include 2. 库目录 项目属性 → VC++ 目录 → 库目录,添加: <OpenCV安装目录>\build\x64\vc16\lib vc16 对应 Visual Studio 2019/2022 3. 链接器输入 项目属性 → 链接器 → 输入 → 附加依赖项,添加: opencv_world4100.lib 4. 环境变量 系统环境变量 Path 中添加: <OpenCV安装目录>\build\x64\vc16\bin 二、验证 #include <opencv2/opencv.hpp> #include <iostream> int main() { std::cout << "OpenCV Version: " << CV_VERSION << std::endl; return 0; } 三、常见问题 运行时弹出找不到 dll → 检查环境变量是否配置正确 链接错误 → 检查库目录和附加依赖项 Release / Debug 库混用 → Debug 构建须链接带 d 后缀的 opencv_world4100d.lib,Release 用 opencv_world4100.lib

2026 年 10 月 13 日

OpenCV 常用 API:图像读写、显示与颜色空间转换

OpenCV 入门最常用的几组 API:图像读取、显示、颜色空间转换、保存、获取图像信息。 一、图像读取与显示 #include <opencv2/opencv.hpp> using namespace cv; int main() { // 读取图像 Mat img = imread("test.jpg"); // 显示图像 imshow("窗口名", img); // 等待按键 waitKey(0); return 0; } 二、灰度图转换 Mat gray; cvtColor(img, gray, COLOR_BGR2GRAY); imshow("灰度图", gray); 三、HSV 图像转换 Mat hsv; cvtColor(img, hsv, COLOR_BGR2HSV); imshow("HSV", hsv); 四、常用颜色空间 转换 常量 BGR → 灰度 COLOR_BGR2GRAY BGR → HSV COLOR_BGR2HSV BGR → RGB COLOR_BGR2RGB 灰度 → BGR COLOR_GRAY2BGR 五、图像保存 imwrite("output.jpg", gray); 六、获取图像信息 int width = img.cols; // 宽度 int height = img.rows; // 高度 int channels = img.channels(); // 通道数

2026 年 10 月 13 日

OpenCV 像素遍历:at<> 与 ptr<> 两种方式对比

OpenCV 访问像素的两种方式:at<> 直接访问和 ptr<> 指针访问。 一、方式一:at<> 直接访问 通过行列索引直接访问像素,代码简洁但每次访问都有边界检查开销。 // 单通道(灰度图) uchar pixel = image.at<uchar>(i, j); image.at<uchar>(i, j) = 255 - pixel; // 三通道(彩色图) Vec3b pixel = image.at<Vec3b>(i, j); pixel[0] = 255 - pixel[0]; // B pixel[1] = 255 - pixel[1]; // G pixel[2] = 255 - pixel[2]; // R image.at<Vec3b>(i, j) = pixel; 二、方式二:ptr<> 指针访问 获取行首指针后直接偏移,性能更优(无边界检查)。 // 单通道 uchar* rowPtr = image.ptr<uchar>(i); rowPtr[j] = 255 - rowPtr[j]; // 三通道 Vec3b* rowPtr = image.ptr<Vec3b>(i); Vec3b pixel = rowPtr[j]; pixel[0] = 255 - pixel[0]; pixel[1] = 255 - pixel[1]; pixel[2] = 255 - pixel[2]; rowPtr[j] = pixel; 三、对比 特性 at<> ptr<> 语法 简洁直观 需理解指针 性能 每次边界检查,较慢 无边界检查,较快 适用 小批量访问、调试 大图遍历、性能敏感 四、反转图像示例(完整) void invertImage(Mat& image) { int w = image.cols; int h = image.rows; int channels = image.channels(); for (int i = 0; i < h; i++) { uchar* rowPtr = image.ptr<uchar>(i); for (int j = 0; j < w * channels; j++) { rowPtr[j] = 255 - rowPtr[j]; } } imshow("Inverted", image); } 可用 rowPtr[j] = 255 - rowPtr[j] 一次处理所有通道,无需区分通道数。

2026 年 10 月 13 日

pprof 性能调优

C++ 里要弄 4~5 套工具(perf / valgrind / gprof / TSan);Go 把 CPU、内存、goroutine、锁的剖面图都装进了一个 pprof 一、简述 性能调优第一步永远不是"猜",是"量"。Go 的 pprof(profile)机制提供一套统一的剖析框架:CPU、堆内存、分配、goroutine、锁竞争、阻塞延时的剖面数据,既能离线抓(runtime/pprof、go test -cpuprofile),也能在线实时抓(net/http/pprof)。配合 go tool pprof 的 top / list / peek / web 交互和火焰图,你能在几分钟内定位 CPU 热点、内存黑洞、goroutine 泄漏。对用惯 perf/valgrind/gprof 的 C++ 开发者来说,这是一次"多工具合一"的升级。 核心要点:pprof 是采样式剖析:按频率打断程序,记录当时正在执行的函数调用栈,最后统计"每个函数在多少样本里出现"。flat 是"函数自己耗的",cum 是"含它调用的所有子函数"——读懂这两个数字是使用 pprof 的核心。 二、C++ 对照速查表 概念 C++ Go 关键差异 CPU 剖析 perf record/report runtime/pprof + cpu.prof Go 内置采样器,无需 perf_event 权限 堆内存剖析 valgrind --tool=massif / heaptrack memprofile + heap view pprof 同时给 inuse/alloc 两维度 线级热点 perf annotate go tool pprof -list 直接给源码行统计 火焰图 perf script + 第三方 FlameGraph go tool pprof -http(内链火焰图) 一命令内建 死锁/锁竞争 手写 + helgrind mutex/block profile 并发调优必备 调用图 gprof(编译期插桩) pprof 自动采样 Go 无插桩成本,直接采样 内存泄漏排查 valgrind --leak-check goroutine profile + alloc_objects Go 看"分配来源"而非"泄漏点" 运行中抓取 需 perf 权限/SAMPLING net/http/pprof 一键开 线上服务开端口即可 三、逐主题详解 3.1 profile 是什么:采样式剖析 时间轴 ──────────────────────────────────────────► 采样点:每隔 t(如 100 次/秒 = 10ms)触发 SIGPROF 每次记录:当前 goroutine 的完整调用栈(PC → 函数名) ┌────┐┌────┐┌────┐┌────┐ │foo ││bar ││foo ││baz │ 每个采样点保留栈帧 │ main││ foo││ main││ foo │ 最后统计: foo 出现 3/4 次 └────┘└────┘└────┘└────┘ CPU profile:默认每 10ms(100Hz)采样一次正在执行的 G 的栈。只有"在 CPU 上运行"的 G 会被采到——所以纯 IO/阻塞的 goroutine 在 CPU profile 里很低。 heap profile:不是采样,是快照。记录"当前存活对象"的分配点(用于看内存滞留)或"累计分配"(-alloc_space/-alloc_objects,用于看分配大山)。 goroutine profile:每个 goroutine 当前栈(含状态),看泄漏。 mutex/block profile:记录锁等待与阻塞事件(需要 runtime.SetBlockProfileRate / SetMutexProfileFraction 开启)。 3.2 离线抓取:runtime/pprof 与 go test -profile 方式一:go test 直接出谱(最常用) ...

2026 年 10 月 13 日

QMovie 动画播放

QMovie 是 Qt 中用于播放动画(主要是 GIF 格式)的类。它本身不是一个控件,不能直接显示在界面上,通常需要借助 QLabel(或 QGraphicsView)来展示播放效果。 它的核心作用是:逐帧读取动图文件,按时间间隔把每一帧渲染出来,从而实现动画播放。 1. 效果演示 通过 QLabel 显示一个加载中的 GIF 动画,并提供「开始 / 暂停 / 停止」三个按钮,演示 QMovie 的常用接口。 2. 属性和方法 QMovie 的工作流程大致是:加载文件 → 启动播放 → 播放结束。常用的属性和方法如下。 2.1 构造与加载 // 通过文件名构造 QMovie(const QString &fileName, const QByteArray &format = QByteArray(), QObject *parent = nullptr); // 通过 QIODevice 构造(可用于资源文件、网络数据等) QMovie(QIODevice *device, const QByteArray &format = QByteArray(), QObject *parent = nullptr); // 设置 / 获取动画的文件名 void setFileName(const QString &fileName); QString fileName() const; // 判断动画是否有效(文件是否能被正确解析) bool isValid() const; 2.2 播放控制 // 开始播放 void start(); // 暂停 / 继续(QMovie 没有 pause()/resume(),只有这个开关式接口,且它是一个槽) void setPaused(bool paused); // true 为暂停,false 为继续 // 没有 paused()/isPaused() 这样的查询方法,是否暂停用 state() 判断 MovieState state() const; // NotRunning / Paused / Running // 停止播放,并回到第一帧 void stop(); // 缓存模式 CacheMode cacheMode() const; void setCacheMode(CacheMode mode); QMovie 提供的播放控制比较简单,注意 stop() 与 setPaused(true) 的区别: ...

2026 年 10 月 13 日

QSlider滑动条

本文搬运转载自 明王讲QT - QSlider,仅供学习记录使用。版权归原作者所有。 QSlider 滑动条控件 QSlider是滑动条控件 它允许用户通过拖动滑块来选择一个范围内的值。通常用于调整音量、亮度、进度等需要连续数值输入的场景 1. 效果演示三个滑动条,分别用来调节RGB三个颜色的值,并显示到左侧各自的文本框中 并且将RGB这三个颜色的组合,来设置上面的文本框的背景颜色 2. 属性和方法QSlider继承自QAbstractSlider,它的绝大多数属性都是从QAbstractSlider继承而来的。 QSlider有很多属性,完整的可查看帮助文档。这里仅列出常用的属性和方法 2.1 值滑动条和值相关的属性包括:当前值、最大值、最小值 // 获取和设置当前值 int value() const; void setValue(int); // 获取和设置最大值 int maximum() const; void setMaximum(int); // 获取和设置最小值 int minimum() const; void setMinimum(int); // 一次设置最大值和最小值 void setRange(int min, int max) 2.2 方向Qt中滑动条有水平滑动条和垂直滑动条之分,通过修改orientation属性来设置 // 获取和设置滑动条的方向 Qt::Orientation orientation() const; void setOrientation(Qt::Orientation); 2.3 步长步长是指滑动条一次增加或减少的值。这里又包括两个步长: singleStep:是指按键盘的左右箭头(←/→)时的步长 pageStep:是指点击滑块两侧时的步长 // 获取和设置singleStep int singleStep() const; void setSingleStep(int); // 获取和设置pageStep int pageStep() const; void setPageStep(int); 2.4 刻度线```cpp // 获取和设置刻度线间隔 int tickInterval() const void setTickInterval(int ti) // 获取和设置刻度线的位置 QSlider::TickPosition tickPosition() const void setTickPosition(QSlider::TickPosition position) ...

2026 年 10 月 13 日