Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit acda8c6

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

9 files changed

+349
-11
lines changed
Lines changed: 40 additions & 0 deletions
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+
}
Lines changed: 21 additions & 0 deletions
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+
}
Lines changed: 39 additions & 0 deletions
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
Lines changed: 41 additions & 0 deletions
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+
}
Lines changed: 49 additions & 0 deletions
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+
}
Lines changed: 23 additions & 0 deletions
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+
}
Lines changed: 118 additions & 0 deletions
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

Lines changed: 17 additions & 10 deletions
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

Lines changed: 1 addition & 1 deletion
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)

AltStyle によって変換されたページ (->オリジナル) /