Skip to content

Commit acda8c6

Browse files
committed
更新视频代码以及 CMake 引入 fmt、Qt、Boost 的配置,增加使用 OpenMp 的选项
1 parent f9eed73 commit acda8c6

9 files changed

+349
-11
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#include <iostream>
2+
#include <thread>
3+
#include <chrono>
4+
#include <random>
5+
#include <semaphore>
6+
using namespace std::chrono_literals;
7+
8+
// 定义一个信号量,最大并发数为 3
9+
std::counting_semaphore<3> semaphore{ 3 };
10+
11+
void handle_request(int request_id) {
12+
// 请求到达,尝试获取信号量
13+
std::cout << "进入 handle_request 尝试获取信号量\n";
14+
15+
semaphore.acquire();
16+
17+
std::cout << "成功获取信号量\n";
18+
19+
// 此处延时三秒可以方便测试,会看到先输出 3 个“成功获取信号量”,因为只有三个线程能成功调用 acquire,剩余的会被阻塞
20+
std::this_thread::sleep_for(3s);
21+
22+
// 模拟处理时间
23+
std::random_device rd;
24+
std::mt19937 gen{ rd() };
25+
std::uniform_int_distribution<> dis(1, 5);
26+
int processing_time = dis(gen);
27+
std::this_thread::sleep_for(std::chrono::seconds(processing_time));
28+
29+
std::cout << std::format("请求 {} 已被处理\n", request_id);
30+
31+
semaphore.release();
32+
}
33+
34+
int main() {
35+
// 模拟 10 个并发请求
36+
std::vector<std::jthread> threads;
37+
for (int i = 0; i < 10; ++i) {
38+
threads.emplace_back(handle_request, i);
39+
}
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#include <iostream>
2+
#include <thread>
3+
#include <chrono>
4+
#include <latch>
5+
using namespace std::chrono_literals;
6+
7+
std::latch latch{ 10 };
8+
9+
void f(int id) {
10+
//todo.. 脑补任务
11+
std::cout << std::format("线程 {} 执行完任务,开始等待其它线程执行到此处\n", id);
12+
latch.arrive_and_wait(); // 减少 并等待 count_down(1); wait(); 等待计数为 0
13+
std::cout << std::format("线程 {} 彻底退出函数\n", id);
14+
}
15+
16+
int main() {
17+
std::vector<std::jthread> threads;
18+
for (int i = 0; i < 10; ++i) {
19+
threads.emplace_back(f, i);
20+
}
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#include <iostream>
2+
#include <omp.h>
3+
#include <string>
4+
#include <thread>
5+
6+
void f(int start, int end, int thread_id) {
7+
for (int i = start; i <= end; ++i) {
8+
// 输出当前线程的数字
9+
std::cout << std::to_string(i) + " ";
10+
11+
// 等待所有线程同步到达 barrier 也就是等待都输出完数字
12+
#pragma omp barrier
13+
14+
// 每个线程输出完一句后,主线程输出轮次信息
15+
#pragma omp master
16+
{
17+
static int round_number = 1;
18+
std::cout << "\t" << round_number++ << "轮结束\n";
19+
std::this_thread::sleep_for(std::chrono::seconds(1));
20+
}
21+
22+
// 再次同步 等待所有线程(包括主线程)到达此处、避免其它线程继续执行打断主线程的输出
23+
#pragma omp barrier
24+
}
25+
}
26+
27+
int main() {
28+
constexpr int num_threads = 10;
29+
omp_set_num_threads(num_threads);
30+
31+
#pragma omp parallel
32+
{
33+
const int thread_id = omp_get_thread_num();
34+
f(thread_id * 10 + 1, (thread_id + 1) * 10, thread_id);
35+
}
36+
37+
}
38+
39+
// https://godbolt.org/z/fabqhbx3P
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#include <iostream>
2+
#include <thread>
3+
#include <vector>
4+
5+
struct X {
6+
X() {
7+
// 假设 X 的初始化没那么快
8+
std::this_thread::sleep_for(std::chrono::seconds(1));
9+
std::puts("X");
10+
v.resize(10, 6);
11+
}
12+
std::vector<int> v;
13+
};
14+
15+
struct Test {
16+
Test()/* : t{ &Test::f, this }*/ // 线程已经开始执行
17+
{
18+
// 严格意义来说 这里不算初始化 至少不算 C++ 标准的定义
19+
}
20+
void start()
21+
{
22+
t = std::thread{ &Test::f, this };
23+
}
24+
~Test() {
25+
if (t.joinable())
26+
t.join();
27+
}
28+
void f()const { // 如果在函数执行的线程 f 中使用 x 则会存在问题。使用了未初始化的数据成员 ub
29+
std::cout << "f\n";
30+
std::cout << x.v[9] << '\n';
31+
}
32+
33+
34+
std::thread t; // 声明顺序决定了初始化顺序,优先初始化 t
35+
X x;
36+
};
37+
38+
int main() {
39+
Test t;
40+
t.start();
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#include <iostream>
2+
#include <thread>
3+
#include <chrono>
4+
#include <atomic>
5+
using namespace std::chrono_literals;
6+
7+
// 不要这样使用 不要在多线程并发中使用 volatile
8+
// 它的行为是不保证的
9+
std::atomic<int> n = 0;
10+
11+
void read(){
12+
while(true){
13+
std::this_thread::sleep_for(500ms);
14+
std::cout << n.load() << '\n';
15+
}
16+
}
17+
18+
void write(){
19+
while (true){
20+
++n;
21+
}
22+
}
23+
24+
// 数据竞争 数据竞争未定义行为
25+
// 优化会假设你的程序中没有未定义行为
26+
27+
// C 语言的平凡的结构体
28+
struct trivial_type {
29+
int x;
30+
float y;
31+
};
32+
33+
int main(){
34+
// 创建一个 std::atomic<trivial_type> 对象
35+
std::atomic<trivial_type> atomic_my_type{ { 10, 20.5f } };
36+
37+
// 使用 store 和 load 操作来设置和获取值
38+
trivial_type new_value{ 30, 40.5f };
39+
atomic_my_type.store(new_value);
40+
41+
std::cout << "x: " << atomic_my_type.load().x << ", y: " << atomic_my_type.load().y << std::endl;
42+
43+
// 使用 exchange 操作
44+
trivial_type exchanged_value = atomic_my_type.exchange({ 50, 60.5f });
45+
std::cout << "交换前的 x: " << exchanged_value.x
46+
<< ", 交换前的 y: " << exchanged_value.y << std::endl;
47+
std::cout << "交换后的 x: " << atomic_my_type.load().x
48+
<< ", 交换后的 y: " << atomic_my_type.load().y << std::endl;
49+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#include <QCoreApplication>
2+
#include <QThreadPool>
3+
#include <QRunnable>
4+
#include <QDebug>
5+
6+
int main(int argc, char* argv[]) {
7+
QCoreApplication app(argc, argv);
8+
9+
QThreadPool* threadPool = QThreadPool::globalInstance();
10+
11+
// 线程池最大线程数
12+
qDebug() << threadPool->maxThreadCount();
13+
14+
for (int i = 0; i < 20; ++i) {
15+
threadPool->start([i]{
16+
qDebug() << QString("thread id %1").arg(i);
17+
});
18+
}
19+
// 当前活跃线程数 10
20+
qDebug() << threadPool->activeThreadCount();
21+
22+
app.exec();
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
#include <iostream>
2+
#include <mutex>
3+
#include <condition_variable>
4+
#include <atomic>
5+
#include <queue>
6+
#include <functional>
7+
#include <thread>
8+
#include <vector>
9+
#include <future>
10+
#include <memory>
11+
#include <syncstream>
12+
using namespace std::chrono_literals;
13+
14+
inline std::size_t default_thread_pool_size() noexcept{
15+
std::size_t num_threads = std::thread::hardware_concurrency();
16+
num_threads = num_threads == 0 ? 2 : num_threads; // 防止无法检测当前硬件,让我们线程池至少有 2 个线程
17+
return num_threads;
18+
}
19+
20+
class ThreadPool{
21+
public:
22+
using Task = std::packaged_task<void()>;
23+
24+
ThreadPool(const ThreadPool&) = delete;
25+
ThreadPool& operator=(const ThreadPool&) = delete;
26+
27+
ThreadPool(std::size_t num_thread = default_thread_pool_size()) :
28+
stop_{ false }, num_thread_{ num_thread }
29+
{
30+
start();
31+
}
32+
~ThreadPool(){
33+
stop();
34+
}
35+
36+
void stop(){
37+
stop_ = true;
38+
cv_.notify_all();
39+
for (auto& thread : pool_){
40+
if (thread.joinable())
41+
thread.join();
42+
}
43+
pool_.clear();
44+
}
45+
46+
template<typename F, typename ...Args>
47+
std::future<std::invoke_result_t<std::decay_t<F>, std::decay_t<Args>...>> submit(F&& f, Args&&...args){
48+
using RetType = std::invoke_result_t<std::decay_t<F>, std::decay_t<Args>...>;
49+
if(stop_){
50+
throw std::runtime_error("ThreadPool is stopped");
51+
}
52+
auto task = std::make_shared<std::packaged_task<RetType()>>(std::bind(std::forward<F>(f), std::forward<Args>(args)...));
53+
54+
std::future<RetType> ret = task->get_future();
55+
56+
{
57+
std::lock_guard<std::mutex> lc{ mutex_ };
58+
tasks_.emplace([task] {(*task)(); });
59+
}
60+
cv_.notify_one();
61+
62+
return ret;
63+
}
64+
65+
void start(){
66+
for (std::size_t i = 0; i < num_thread_; ++i){
67+
pool_.emplace_back([this]{
68+
while (!stop_) {
69+
Task task;
70+
{
71+
std::unique_lock<std::mutex> lock{ mutex_ };
72+
cv_.wait(lock, [this] {return stop_ || !tasks_.empty(); });
73+
if (tasks_.empty()) return;
74+
task = std::move(tasks_.front());
75+
tasks_.pop();
76+
}
77+
task();
78+
}
79+
});
80+
}
81+
}
82+
83+
private:
84+
std::mutex mutex_;
85+
std::condition_variable cv_;
86+
std::atomic<bool> stop_;
87+
std::atomic<std::size_t> num_thread_;
88+
std::queue<Task> tasks_;
89+
std::vector<std::thread> pool_;
90+
};
91+
92+
int print_task(int n) {
93+
std::osyncstream{ std::cout } << "Task " << n << " is running on thr: " <<
94+
std::this_thread::get_id() << '\n';
95+
return n;
96+
}
97+
int print_task2(int n) {
98+
std::osyncstream{ std::cout } << "🐢🐢🐢 " << n << " 🐉🐉🐉" << std::endl;
99+
return n;
100+
}
101+
102+
struct X {
103+
void f(const int& n) const {
104+
std::osyncstream{ std::cout } << &n << '\n';
105+
}
106+
};
107+
108+
int main() {
109+
ThreadPool pool{ 4 }; // 创建一个有 4 个线程的线程池
110+
111+
X x;
112+
int n = 6;
113+
std::cout << &n << '\n';
114+
auto t = pool.submit(&X::f, &x, n); // 默认复制,地址不同
115+
auto t2 = pool.submit(&X::f, &x, std::ref(n));
116+
t.wait();
117+
t2.wait();
118+
} // 析构自动 stop()自动 stop()

code/ModernCpp-ConcurrentProgramming-Tutorial/CMakeLists.txt

+17-10
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,31 @@ cmake_minimum_required (VERSION 3.8)
22

33
project ("ModernCpp-ConcurrentProgramming-Tutorial")
44

5-
set(CMAKE_CXX_STANDARD 17)
5+
set(CMAKE_CXX_STANDARD 20)
66

77
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
88

99
if(MSVC)
10-
add_compile_options("/utf-8" "/permissive-" "/Zc:nrvo")
10+
add_compile_options("/utf-8" "/permissive-" "/Zc:nrvo" "/openmp")
1111
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
12-
add_compile_options("-finput-charset=UTF-8" "-fexec-charset=UTF-8")
12+
add_compile_options("-finput-charset=UTF-8" "-fexec-charset=UTF-8" "-fopenmp")
1313
endif()
1414

15-
add_executable(${PROJECT_NAME} "34限时等待-时间点.cpp")
15+
add_executable(${PROJECT_NAME} "41实现一个线程池.cpp")
1616

17-
18-
# 设置 SFML 的 CMake 路径
1917
set(SFML_DIR "D:/lib/SFML-2.6.1-windows-vc17-64-bit/SFML-2.6.1/lib/cmake/SFML")
20-
21-
# 查找 SFML 库
2218
find_package(SFML 2.6.1 COMPONENTS system window graphics audio network REQUIRED)
19+
target_link_libraries(${PROJECT_NAME} PRIVATE sfml-system sfml-window sfml-graphics sfml-audio sfml-network)
20+
21+
set(fmt_DIR "D:/lib/fmt_x64-windows/share/fmt")
22+
find_package(fmt CONFIG REQUIRED)
23+
target_link_libraries(${PROJECT_NAME} PRIVATE fmt::fmt-header-only)
24+
25+
find_package(Qt6 REQUIRED Widgets)
26+
target_link_libraries(${PROJECT_NAME} PRIVATE Qt6::Widgets)
2327

24-
# 链接 SFML 库到项目 设置链接选项
25-
target_link_libraries(${PROJECT_NAME} sfml-system sfml-window sfml-graphics sfml-audio sfml-network)
28+
# 当前环境可以直接查找到 vcpkg 的 Boost_DIR 但是却无法查找到 include 路径,手动设置
29+
set(Boost_INCLUDE_DIR "D:/vcpkg-master/installed/x64-windows/include")
30+
include_directories(${Boost_INCLUDE_DIR})
31+
find_package(Boost REQUIRED COMPONENTS system)
32+
target_link_libraries(${PROJECT_NAME} PRIVATE Boost::system)

md/05内存模型与原子操作.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ else {
105105

106106
宏则更是简单了,最基本的预处理器判断,在预处理阶段就选择编译合适的代码。
107107

108-
在实际应用中,如果一个类型的原子操作总是无锁的,我们可以更放心地在性能关键的代码路径中使用它。例如,在高频交易系统、实时系统或者其它需要高并发性能的场景中,无锁的原子操作可以显著减少锁的开销和争用,提高系统的吞吐量和响应时间。
108+
在实际应用中,如果一个类型的原子操作总是无锁的,我们可以更放心地在性能关键的代码路径中使用它。例如,在高频交易系统、实时系统或者其它需要高并发性能的场景中,无锁的原子操作可以显著减少锁的开销和竞争,提高系统的吞吐量和响应时间。
109109

110110
另一方面,如果发现某些原子类型在目标平台上是有锁的,我们可以考虑以下优化策略:
111111

0 commit comments

Comments
 (0)