Skip to content

Commit df9bd65

Browse files
volumn 5 concurrency support; fix issue of mixture chinese and english result (#20)
* volumn 5 concurrency support; fix issue of mixture chinese and english result * ci issue
1 parent 4463464 commit df9bd65

97 files changed

Lines changed: 26941 additions & 4647 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
cmake_minimum_required(VERSION 3.20)
2+
project(ch00_concurrency_fundamentals VERSION 0.0.1 LANGUAGES CXX)
3+
4+
set(CMAKE_CXX_STANDARD 17)
5+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
6+
7+
find_package(Threads REQUIRED)
8+
9+
add_executable(false_sharing_bench false_sharing_bench.cpp)
10+
target_link_libraries(false_sharing_bench PRIVATE Threads::Threads)
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/*
2+
* 验证:false sharing 与 alignas(64) 对齐的性能差异
3+
*
4+
* 背景:文章 ch00-03 "CPU cache 与 OS 线程" 声称——
5+
* 两个线程各自写同一个结构体中不同的 int 成员时,
6+
* 由于两个 int 落在同一条 64 字节缓存行上,
7+
* MESI 协议会反复触发 RFO 使缓存行在核心间乒乓,
8+
* 导致性能甚至比单线程更差。
9+
* 使用 alignas(64) 让两个变量各占一条缓存行可消除此问题。
10+
*
11+
* 预期结果:
12+
* False sharing 版本 >> 单线程 >> Aligned 版本(最快)
13+
*
14+
* 编译命令:
15+
* cmake -B build && cmake --build build
16+
* ./build/false_sharing_bench
17+
*
18+
* 编译器:GCC 16.1.1 | Clang 20+
19+
* 平台:x86-64 Linux
20+
*/
21+
22+
#include <chrono>
23+
#include <iostream>
24+
#include <thread>
25+
26+
// --- Version 1: false sharing(两个 int 在同一条缓存行) ---
27+
struct Counters {
28+
volatile int a;
29+
volatile int b;
30+
};
31+
32+
// --- Version 2: alignas(64)(每个变量独占一条缓存行) ---
33+
constexpr std::size_t kCacheLineSize = 64;
34+
35+
struct alignas(kCacheLineSize) AlignedCounter {
36+
volatile int value;
37+
};
38+
39+
int main() {
40+
constexpr int kIterations = 100'000'000;
41+
42+
// --- Benchmark 1: false sharing ---
43+
{
44+
Counters counters{0, 0};
45+
46+
auto start = std::chrono::high_resolution_clock::now();
47+
48+
std::thread t1([&]() {
49+
for (int i = 0; i < kIterations; ++i) {
50+
counters.a++;
51+
}
52+
});
53+
std::thread t2([&]() {
54+
for (int i = 0; i < kIterations; ++i) {
55+
counters.b++;
56+
}
57+
});
58+
59+
t1.join();
60+
t2.join();
61+
62+
auto end = std::chrono::high_resolution_clock::now();
63+
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
64+
std::cout << "False sharing: " << ms.count() << " ms"
65+
<< " (a=" << counters.a << ", b=" << counters.b << ")\n";
66+
}
67+
68+
// --- Benchmark 2: aligned (no false sharing) ---
69+
{
70+
AlignedCounter counter_a{0};
71+
AlignedCounter counter_b{0};
72+
73+
auto start = std::chrono::high_resolution_clock::now();
74+
75+
std::thread t1([&]() {
76+
for (int i = 0; i < kIterations; ++i) {
77+
counter_a.value++;
78+
}
79+
});
80+
std::thread t2([&]() {
81+
for (int i = 0; i < kIterations; ++i) {
82+
counter_b.value++;
83+
}
84+
});
85+
86+
t1.join();
87+
t2.join();
88+
89+
auto end = std::chrono::high_resolution_clock::now();
90+
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
91+
std::cout << "Aligned: " << ms.count() << " ms"
92+
<< " (a=" << counter_a.value << ", b=" << counter_b.value << ")\n";
93+
}
94+
95+
// --- Benchmark 3: single-threaded baseline ---
96+
{
97+
volatile int a = 0;
98+
volatile int b = 0;
99+
100+
auto start = std::chrono::high_resolution_clock::now();
101+
102+
for (int i = 0; i < kIterations; ++i) {
103+
a++;
104+
}
105+
for (int i = 0; i < kIterations; ++i) {
106+
b++;
107+
}
108+
109+
auto end = std::chrono::high_resolution_clock::now();
110+
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
111+
std::cout << "Single-threaded: " << ms.count() << " ms"
112+
<< " (a=" << a << ", b=" << b << ")\n";
113+
}
114+
115+
return 0;
116+
}
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
/*
2+
* 演示:std::thread 的创建方式与基本生命周期管理
3+
*
4+
* 背景:文章 ch01 "线程生命周期与 RAII 管理" 涵盖——
5+
* 1. 函数指针、lambda、仿函数(functor)三种创建线程的方式
6+
* 2. join 与 detach 的语义差异
7+
* 3. std::thread::id 与 hardware_concurrency
8+
* 4. 线程中的异常安全:safe_worker 模式
9+
*
10+
* 预期结果:
11+
* 程序依次演示各种线程创建方式,输出各线程的 ID 与执行结果,
12+
* 并展示异常安全包装器的行为。
13+
*
14+
* 编译命令:
15+
* cmake -B build && cmake --build build
16+
* ./build/01_thread_creation
17+
*
18+
* 编译器:GCC 12+ | Clang 15+ | MSVC 19.3+
19+
* 平台:x86-64 Linux / macOS / Windows
20+
* C++ 标准:C++17
21+
*/
22+
23+
#include <chrono>
24+
#include <exception>
25+
#include <functional>
26+
#include <iostream>
27+
#include <memory>
28+
#include <mutex>
29+
#include <string>
30+
#include <thread>
31+
#include <vector>
32+
33+
// 用于线程安全输出的辅助互斥量
34+
std::mutex g_cout_mutex;
35+
36+
// 线程安全的 printf 风格输出并非本例重点,
37+
// 这里仅用互斥量保证各行不交错
38+
void safe_print(const std::string& msg) {
39+
std::lock_guard<std::mutex> lock(g_cout_mutex);
40+
std::cout << msg << std::endl;
41+
}
42+
43+
// ============================================================
44+
// Demo 1: 函数指针创建线程
45+
// ============================================================
46+
47+
void worker_function(int id, int iterations) {
48+
for (int i = 0; i < iterations; ++i) {
49+
safe_print(" [函数指针线程 " + std::to_string(id) + "] 迭代 " + std::to_string(i));
50+
}
51+
safe_print(" [函数指针线程 " + std::to_string(id) + "] 完成");
52+
}
53+
54+
void demo_function_pointer() {
55+
safe_print("\n=== Demo 1: 函数指针创建线程 ===");
56+
57+
// std::thread 的第一个参数是可调用对象,
58+
// 后续参数会按值传递给该可调用对象
59+
std::thread t1(worker_function, 1, 3);
60+
std::thread t2(worker_function, 2, 3);
61+
62+
t1.join(); // 阻塞等待 t1 结束
63+
t2.join(); // 阻塞等待 t2 结束
64+
65+
safe_print(" 两个函数指针线程均已结束");
66+
}
67+
68+
// ============================================================
69+
// Demo 2: Lambda 创建线程
70+
// ============================================================
71+
72+
void demo_lambda_thread() {
73+
safe_print("\n=== Demo 2: Lambda 创建线程 ===");
74+
75+
int shared_value = 0;
76+
77+
// lambda 捕获外部变量(按引用),启动线程执行
78+
// 注意:此处仅作演示,实际生产中需要同步机制保护 shared_value
79+
std::thread t([&shared_value]() {
80+
for (int i = 0; i < 5; ++i) {
81+
++shared_value;
82+
safe_print(" [Lambda 线程] shared_value = " + std::to_string(shared_value));
83+
}
84+
});
85+
86+
t.join();
87+
safe_print(" [主线程] 最终 shared_value = " + std::to_string(shared_value));
88+
}
89+
90+
// ============================================================
91+
// Demo 3: 仿函数(Functor)创建线程
92+
// ============================================================
93+
94+
// 仿函数:重载 operator() 的类,可像函数一样调用
95+
class TaskWorker {
96+
public:
97+
TaskWorker(std::string name, int count) : name_(std::move(name)), count_(count) {}
98+
99+
void operator()() const {
100+
for (int i = 0; i < count_; ++i) {
101+
safe_print(" [" + name_ + "] 执行任务 " + std::to_string(i));
102+
}
103+
safe_print(" [" + name_ + "] 全部任务完成");
104+
}
105+
106+
private:
107+
std::string name_;
108+
int count_;
109+
};
110+
111+
void demo_functor_thread() {
112+
safe_print("\n=== Demo 3: 仿函数创建线程 ===");
113+
114+
// 注意:C++ 最令人头疼的解析(most vexing parse)问题
115+
// std::thread t(TaskWorker("worker", 3));
116+
// 会被编译器解释为函数声明,而非对象构造!
117+
// 解决方案:使用 {} 初始化或额外括号
118+
std::thread t{TaskWorker("FunctorWorker", 4)};
119+
120+
t.join();
121+
}
122+
123+
// ============================================================
124+
// Demo 4: join 与 detach 演示
125+
// ============================================================
126+
127+
void background_task(int id) {
128+
safe_print(" [detach 线程 " + std::to_string(id) + "] 开始后台工作");
129+
std::this_thread::sleep_for(std::chrono::milliseconds(100));
130+
safe_print(" [detach 线程 " + std::to_string(id) + "] 后台工作完成");
131+
}
132+
133+
void demo_join_vs_detach() {
134+
safe_print("\n=== Demo 4: join 与 detach 演示 ===");
135+
136+
// join: 主线程阻塞等待子线程结束
137+
{
138+
std::thread t([]() { safe_print(" [join 线程] 正在工作..."); });
139+
safe_print(" [主线程] 等待 join 线程结束...");
140+
t.join();
141+
safe_print(" [主线程] join 线程已结束,继续执行");
142+
}
143+
144+
// detach: 将线程与 std::thread 对象分离,线程在后台独立运行
145+
// 分离后 std::thread 对象不再管理该线程,joinable() 变为 false
146+
{
147+
std::thread t(background_task, 99);
148+
safe_print(" [主线程] detach 之前 joinable = " +
149+
std::string(t.joinable() ? "true" : "false"));
150+
t.detach();
151+
safe_print(" [主线程] detach 之后 joinable = " +
152+
std::string(t.joinable() ? "true" : "false"));
153+
safe_print(" [主线程] 已 detach,主线程继续执行");
154+
}
155+
156+
// 等待 detach 线程完成,避免它在 main 结束后才输出
157+
std::this_thread::sleep_for(std::chrono::milliseconds(200));
158+
}
159+
160+
// ============================================================
161+
// Demo 5: 线程 ID 与 hardware_concurrency
162+
// ============================================================
163+
164+
void print_thread_info() {
165+
// std::this_thread::get_id() 获取当前线程的 ID
166+
std::thread::id tid = std::this_thread::get_id();
167+
safe_print(" [子线程] 我的 thread::id = " +
168+
std::to_string(*reinterpret_cast<const unsigned long*>(&tid)));
169+
}
170+
171+
void demo_thread_id_and_hardware() {
172+
safe_print("\n=== Demo 5: 线程 ID 与 hardware_concurrency ===");
173+
174+
// hardware_concurrency 返回硬件支持的并发线程数
175+
// 这是一个提示值,可能返回 0(无法检测时)
176+
unsigned int hw = std::thread::hardware_concurrency();
177+
safe_print(" hardware_concurrency = " + std::to_string(hw));
178+
179+
// 主线程也有自己的 ID
180+
std::thread::id main_id = std::this_thread::get_id();
181+
safe_print(" [主线程] thread::id = " +
182+
std::to_string(*reinterpret_cast<const unsigned long*>(&main_id)));
183+
184+
// 每个线程都有唯一的 ID
185+
std::thread t1(print_thread_info);
186+
std::thread t2(print_thread_info);
187+
t1.join();
188+
t2.join();
189+
}
190+
191+
// ============================================================
192+
// Demo 6: 异常安全 —— safe_worker 包装器
193+
// ============================================================
194+
195+
// 线程函数中抛出异常会导致 std::terminate,
196+
// 因为异常无法跨线程传播。
197+
// safe_worker 模式:在进入线程前捕获异常,通过 std::exception_ptr
198+
// 或 optional<error_code> 传回主线程。
199+
200+
// 简易版:捕获异常并打印,防止程序终止
201+
template <typename Func> void safe_worker(Func&& func, const std::string& task_name) {
202+
try {
203+
std::forward<Func>(func)();
204+
} catch (const std::exception& e) {
205+
safe_print(" [safe_worker: " + task_name + "] 捕获异常: " + std::string(e.what()));
206+
} catch (...) {
207+
safe_print(" [safe_worker: " + task_name + "] 捕获未知异常");
208+
}
209+
}
210+
211+
// 一个会抛出异常的工作函数
212+
void faulty_worker(int value) {
213+
if (value < 0) {
214+
throw std::runtime_error("value 不能为负数: " + std::to_string(value));
215+
}
216+
safe_print(" [faulty_worker] 正常处理 value = " + std::to_string(value));
217+
}
218+
219+
void demo_exception_safety() {
220+
safe_print("\n=== Demo 6: 异常安全 —— safe_worker 包装器 ===");
221+
222+
// 正常调用:不抛异常
223+
std::thread t1([]() { safe_worker([]() { faulty_worker(42); }, "正常任务"); });
224+
225+
// 异常调用:会被 safe_worker 捕获,不会导致 std::terminate
226+
std::thread t2([]() { safe_worker([]() { faulty_worker(-1); }, "异常任务"); });
227+
228+
t1.join();
229+
t2.join();
230+
231+
safe_print(" 所有线程安全结束(异常被捕获,未导致 terminate)");
232+
}
233+
234+
// ============================================================
235+
// 主函数:依次运行所有 Demo
236+
// ============================================================
237+
238+
int main() {
239+
std::cout << "===== ch01: 线程创建方式与基本生命周期 =====\n";
240+
241+
demo_function_pointer();
242+
demo_lambda_thread();
243+
demo_functor_thread();
244+
demo_join_vs_detach();
245+
demo_thread_id_and_hardware();
246+
demo_exception_safety();
247+
248+
std::cout << "\n===== 所有 Demo 运行完毕 =====" << std::endl;
249+
return 0;
250+
}

0 commit comments

Comments
 (0)