Skip to content

Commit

Permalink
更新视频代码以及 CMake 引入 fmt、Qt、Boost 的配置,增加使用 OpenMp 的选项
Browse files Browse the repository at this point in the history
  • Loading branch information
Mq-b committed Sep 28, 2024
1 parent f9eed73 commit acda8c6
Show file tree
Hide file tree
Showing 9 changed files with 349 additions and 11 deletions.
40 changes: 40 additions & 0 deletions code/ModernCpp-ConcurrentProgramming-Tutorial/35C++20信号量.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#include <iostream>
#include <thread>
#include <chrono>
#include <random>
#include <semaphore>
using namespace std::chrono_literals;

// 定义一个信号量,最大并发数为 3
std::counting_semaphore<3> semaphore{ 3 };

void handle_request(int request_id) {
// 请求到达,尝试获取信号量
std::cout << "进入 handle_request 尝试获取信号量\n";

semaphore.acquire();

std::cout << "成功获取信号量\n";

// 此处延时三秒可以方便测试,会看到先输出 3 个“成功获取信号量”,因为只有三个线程能成功调用 acquire,剩余的会被阻塞
std::this_thread::sleep_for(3s);

// 模拟处理时间
std::random_device rd;
std::mt19937 gen{ rd() };
std::uniform_int_distribution<> dis(1, 5);
int processing_time = dis(gen);
std::this_thread::sleep_for(std::chrono::seconds(processing_time));

std::cout << std::format("请求 {} 已被处理\n", request_id);

semaphore.release();
}

int main() {
// 模拟 10 个并发请求
std::vector<std::jthread> threads;
for (int i = 0; i < 10; ++i) {
threads.emplace_back(handle_request, i);
}
}
21 changes: 21 additions & 0 deletions code/ModernCpp-ConcurrentProgramming-Tutorial/36C++20闩latch.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#include <iostream>
#include <thread>
#include <chrono>
#include <latch>
using namespace std::chrono_literals;

std::latch latch{ 10 };

void f(int id) {
//todo.. 脑补任务
std::cout << std::format("线程 {} 执行完任务,开始等待其它线程执行到此处\n", id);
latch.arrive_and_wait(); // 减少 并等待 count_down(1); wait(); 等待计数为 0
std::cout << std::format("线程 {} 彻底退出函数\n", id);
}

int main() {
std::vector<std::jthread> threads;
for (int i = 0; i < 10; ++i) {
threads.emplace_back(f, i);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#include <iostream>
#include <omp.h>
#include <string>
#include <thread>

void f(int start, int end, int thread_id) {
for (int i = start; i <= end; ++i) {
// 输出当前线程的数字
std::cout << std::to_string(i) + " ";

// 等待所有线程同步到达 barrier 也就是等待都输出完数字
#pragma omp barrier

// 每个线程输出完一句后,主线程输出轮次信息
#pragma omp master
{
static int round_number = 1;
std::cout << "\t" << round_number++ << "轮结束\n";
std::this_thread::sleep_for(std::chrono::seconds(1));
}

// 再次同步 等待所有线程(包括主线程)到达此处、避免其它线程继续执行打断主线程的输出
#pragma omp barrier
}
}

int main() {
constexpr int num_threads = 10;
omp_set_num_threads(num_threads);

#pragma omp parallel
{
const int thread_id = omp_get_thread_num();
f(thread_id * 10 + 1, (thread_id + 1) * 10, thread_id);
}

}

// https://godbolt.org/z/fabqhbx3P
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#include <iostream>
#include <thread>
#include <vector>

struct X {
X() {
// 假设 X 的初始化没那么快
std::this_thread::sleep_for(std::chrono::seconds(1));
std::puts("X");
v.resize(10, 6);
}
std::vector<int> v;
};

struct Test {
Test()/* : t{ &Test::f, this }*/ // 线程已经开始执行
{
// 严格意义来说 这里不算初始化 至少不算 C++ 标准的定义
}
void start()
{
t = std::thread{ &Test::f, this };
}
~Test() {
if (t.joinable())
t.join();
}
void f()const { // 如果在函数执行的线程 f 中使用 x 则会存在问题。使用了未初始化的数据成员 ub
std::cout << "f\n";
std::cout << x.v[9] << '\n';
}


std::thread t; // 声明顺序决定了初始化顺序,优先初始化 t
X x;
};

int main() {
Test t;
t.start();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#include <iostream>
#include <thread>
#include <chrono>
#include <atomic>
using namespace std::chrono_literals;

// 不要这样使用 不要在多线程并发中使用 volatile
// 它的行为是不保证的
std::atomic<int> n = 0;

void read(){
while(true){
std::this_thread::sleep_for(500ms);
std::cout << n.load() << '\n';
}
}

void write(){
while (true){
++n;
}
}

// 数据竞争 数据竞争未定义行为
// 优化会假设你的程序中没有未定义行为

// C 语言的平凡的结构体
struct trivial_type {
int x;
float y;
};

int main(){
// 创建一个 std::atomic<trivial_type> 对象
std::atomic<trivial_type> atomic_my_type{ { 10, 20.5f } };

// 使用 store 和 load 操作来设置和获取值
trivial_type new_value{ 30, 40.5f };
atomic_my_type.store(new_value);

std::cout << "x: " << atomic_my_type.load().x << ", y: " << atomic_my_type.load().y << std::endl;

// 使用 exchange 操作
trivial_type exchanged_value = atomic_my_type.exchange({ 50, 60.5f });
std::cout << "交换前的 x: " << exchanged_value.x
<< ", 交换前的 y: " << exchanged_value.y << std::endl;
std::cout << "交换后的 x: " << atomic_my_type.load().x
<< ", 交换后的 y: " << atomic_my_type.load().y << std::endl;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#include <QCoreApplication>
#include <QThreadPool>
#include <QRunnable>
#include <QDebug>

int main(int argc, char* argv[]) {
QCoreApplication app(argc, argv);

QThreadPool* threadPool = QThreadPool::globalInstance();

// 线程池最大线程数
qDebug() << threadPool->maxThreadCount();

for (int i = 0; i < 20; ++i) {
threadPool->start([i]{
qDebug() << QString("thread id %1").arg(i);
});
}
// 当前活跃线程数 10
qDebug() << threadPool->activeThreadCount();

app.exec();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
#include <iostream>
#include <mutex>
#include <condition_variable>
#include <atomic>
#include <queue>
#include <functional>
#include <thread>
#include <vector>
#include <future>
#include <memory>
#include <syncstream>
using namespace std::chrono_literals;

inline std::size_t default_thread_pool_size() noexcept{
std::size_t num_threads = std::thread::hardware_concurrency();
num_threads = num_threads == 0 ? 2 : num_threads; // 防止无法检测当前硬件,让我们线程池至少有 2 个线程
return num_threads;
}

class ThreadPool{
public:
using Task = std::packaged_task<void()>;

ThreadPool(const ThreadPool&) = delete;
ThreadPool& operator=(const ThreadPool&) = delete;

ThreadPool(std::size_t num_thread = default_thread_pool_size()) :
stop_{ false }, num_thread_{ num_thread }
{
start();
}
~ThreadPool(){
stop();
}

void stop(){
stop_ = true;
cv_.notify_all();
for (auto& thread : pool_){
if (thread.joinable())
thread.join();
}
pool_.clear();
}

template<typename F, typename ...Args>
std::future<std::invoke_result_t<std::decay_t<F>, std::decay_t<Args>...>> submit(F&& f, Args&&...args){
using RetType = std::invoke_result_t<std::decay_t<F>, std::decay_t<Args>...>;
if(stop_){
throw std::runtime_error("ThreadPool is stopped");
}
auto task = std::make_shared<std::packaged_task<RetType()>>(std::bind(std::forward<F>(f), std::forward<Args>(args)...));

std::future<RetType> ret = task->get_future();

{
std::lock_guard<std::mutex> lc{ mutex_ };
tasks_.emplace([task] {(*task)(); });
}
cv_.notify_one();

return ret;
}

void start(){
for (std::size_t i = 0; i < num_thread_; ++i){
pool_.emplace_back([this]{
while (!stop_) {
Task task;
{
std::unique_lock<std::mutex> lock{ mutex_ };
cv_.wait(lock, [this] {return stop_ || !tasks_.empty(); });
if (tasks_.empty()) return;
task = std::move(tasks_.front());
tasks_.pop();
}
task();
}
});
}
}

private:
std::mutex mutex_;
std::condition_variable cv_;
std::atomic<bool> stop_;
std::atomic<std::size_t> num_thread_;
std::queue<Task> tasks_;
std::vector<std::thread> pool_;
};

int print_task(int n) {
std::osyncstream{ std::cout } << "Task " << n << " is running on thr: " <<
std::this_thread::get_id() << '\n';
return n;
}
int print_task2(int n) {
std::osyncstream{ std::cout } << "🐢🐢🐢 " << n << " 🐉🐉🐉" << std::endl;
return n;
}

struct X {
void f(const int& n) const {
std::osyncstream{ std::cout } << &n << '\n';
}
};

int main() {
ThreadPool pool{ 4 }; // 创建一个有 4 个线程的线程池

X x;
int n = 6;
std::cout << &n << '\n';
auto t = pool.submit(&X::f, &x, n); // 默认复制,地址不同
auto t2 = pool.submit(&X::f, &x, std::ref(n));
t.wait();
t2.wait();
} // 析构自动 stop()自动 stop()
27 changes: 17 additions & 10 deletions code/ModernCpp-ConcurrentProgramming-Tutorial/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,31 @@ cmake_minimum_required (VERSION 3.8)

project ("ModernCpp-ConcurrentProgramming-Tutorial")

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD 20)

SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

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

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


# 设置 SFML 的 CMake 路径
set(SFML_DIR "D:/lib/SFML-2.6.1-windows-vc17-64-bit/SFML-2.6.1/lib/cmake/SFML")

# 查找 SFML 库
find_package(SFML 2.6.1 COMPONENTS system window graphics audio network REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE sfml-system sfml-window sfml-graphics sfml-audio sfml-network)

set(fmt_DIR "D:/lib/fmt_x64-windows/share/fmt")
find_package(fmt CONFIG REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE fmt::fmt-header-only)

find_package(Qt6 REQUIRED Widgets)
target_link_libraries(${PROJECT_NAME} PRIVATE Qt6::Widgets)

# 链接 SFML 库到项目 设置链接选项
target_link_libraries(${PROJECT_NAME} sfml-system sfml-window sfml-graphics sfml-audio sfml-network)
# 当前环境可以直接查找到 vcpkg 的 Boost_DIR 但是却无法查找到 include 路径,手动设置
set(Boost_INCLUDE_DIR "D:/vcpkg-master/installed/x64-windows/include")
include_directories(${Boost_INCLUDE_DIR})
find_package(Boost REQUIRED COMPONENTS system)
target_link_libraries(${PROJECT_NAME} PRIVATE Boost::system)
2 changes: 1 addition & 1 deletion md/05内存模型与原子操作.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ else {

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

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

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

Expand Down

0 comments on commit acda8c6

Please sign in to comment.