diff --git a/README.md b/README.md index 02ddaf23..415527f9 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ # 现代C++并发编程教程 -本仓库用来存放 B 站课程[《现代 C++ 并发编程教程》]()的教案、代码。 +本仓库用来存放 B 站课程[《现代 C++ 并发编程教程》](https://www.bilibili.com/cheese/play/ss34184)的教案、代码。 不管是否购买课程,任何组织和个人遵守 [CC BY-NC-ND 4.0](https://creativecommons.org/licenses/by-nc-nd/4.0/deed.zh-hans) 协议均可随意使用学习。 diff --git a/SUMMARY.md b/SUMMARY.md deleted file mode 100644 index cb06bc84..00000000 --- a/SUMMARY.md +++ /dev/null @@ -1,14 +0,0 @@ -# Summary - -* [首页](README.md) -* [阅读须知](md/README.md) -* [基本概念](md/01基本概念.md) -* [使用线程](md/02使用线程.md) -* [共享数据](md/03共享数据.md) -* [同步操作](md/04同步操作.md) -* [内存模型与原子操作](md/05内存模型与原子操作.md) -* [协程](md/06协程.md) -* [详细分析](md/详细分析/README.md) - * [`std::thread` 的构造-源码解析](md/详细分析/01thread的构造与源码解析.md) - * [`std::scoped_lock` 的源码实现与解析](md/详细分析/02scoped_lock源码解析.md) - * [`std::async` 与 `std::future` 源码解析](md/详细分析/03async与future源码解析.md) diff --git a/code/ModernCpp-ConcurrentProgramming-Tutorial/CMakeLists.txt b/code/ModernCpp-ConcurrentProgramming-Tutorial/CMakeLists.txt index 7f2eafa3..94d9567a 100644 --- a/code/ModernCpp-ConcurrentProgramming-Tutorial/CMakeLists.txt +++ b/code/ModernCpp-ConcurrentProgramming-Tutorial/CMakeLists.txt @@ -12,13 +12,13 @@ elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "C add_compile_options("-finput-charset=UTF-8" "-fexec-charset=UTF-8" "-fopenmp") endif() -add_executable(${PROJECT_NAME} "45原子特化shared_ptr.cpp") +add_executable(${PROJECT_NAME} "test2.cpp") + +set(CMAKE_PREFIX_PATH "D:/lib" CACHE STRING "自定义查找包的安装路径" FORCE) -set(SFML_DIR "D:/lib/SFML-2.6.1-windows-vc17-64-bit/SFML-2.6.1/lib/cmake/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) @@ -30,3 +30,19 @@ 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) + +target_include_directories(${PROJECT_NAME} PRIVATE "D:/project/cpp-terminal/include") +if(CMAKE_BUILD_TYPE STREQUAL "Release") + target_link_libraries(${PROJECT_NAME} PRIVATE + "D:/project/cpp-terminal/lib/cpp-terminal-private.lib" + "D:/project/cpp-terminal/lib/cpp-terminal.lib" + ) +else() + target_link_libraries(${PROJECT_NAME} PRIVATE + "D:/project/cpp-terminal/lib/private/debug/cpp-terminal-private.lib" + "D:/project/cpp-terminal/lib/debug/cpp-terminal.lib" + ) +endif() + +find_package(spdlog REQUIRED) +target_link_libraries(${PROJECT_NAME} PRIVATE spdlog::spdlog_header_only) diff --git a/code/ModernCpp-ConcurrentProgramming-Tutorial/Log.h b/code/ModernCpp-ConcurrentProgramming-Tutorial/Log.h new file mode 100644 index 00000000..78e152c3 --- /dev/null +++ b/code/ModernCpp-ConcurrentProgramming-Tutorial/Log.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include + +inline void setupLogging() { + auto file_sink = std::make_shared("logs.txt"); + file_sink->set_level(spdlog::level::debug); + file_sink->set_pattern("[%Y-%m-%d %H:%M:%S] [%@] [%!] [thread %t] [%oms] [%l] %v"); + + auto console_sink = std::make_shared(); + console_sink->set_level(spdlog::level::debug); + console_sink->set_pattern("%^[%Y-%m-%d %H:%M:%S] [thread %t] [%oms] [%l] %v%$"); + + auto logger = std::make_shared("multi_sink", spdlog::sinks_init_list{ file_sink, console_sink }); + spdlog::register_logger(logger); + + spdlog::set_default_logger(logger); +} + +// spdlog 要想输出文件、路径、函数、行号,只能借助此宏,才会显示。 +// 其实使用 C++20 std::source_location 也能获取这些信息,后面再考虑单独封装吧,目前这样做导致没办法做格式字符串。 + +#define LOG_INFO(msg, ...) SPDLOG_LOGGER_INFO(spdlog::get("multi_sink"), msg) +#define LOG_WARN(msg, ...) SPDLOG_LOGGER_WARN(spdlog::get("multi_sink"), msg) +#define LOG_ERROR(msg, ...) SPDLOG_LOGGER_ERROR(spdlog::get("multi_sink"), msg) + +const auto init_log = (setupLogging(), 0); diff --git a/code/ModernCpp-ConcurrentProgramming-Tutorial/test2.cpp b/code/ModernCpp-ConcurrentProgramming-Tutorial/test2.cpp new file mode 100644 index 00000000..b9266883 --- /dev/null +++ b/code/ModernCpp-ConcurrentProgramming-Tutorial/test2.cpp @@ -0,0 +1,13 @@ +#include "Log.h" +#include +#include +using namespace std::chrono_literals; + +int main() { + LOG_WARN("😅"); + std::jthread t{[]{ + std::this_thread::sleep_for(100ms); + LOG_ERROR("🤣"); + }}; + LOG_INFO("👉"); +} \ No newline at end of file diff --git "a/md/02\344\275\277\347\224\250\347\272\277\347\250\213.md" "b/md/02\344\275\277\347\224\250\347\272\277\347\250\213.md" index ef2d5342..798596db 100644 --- "a/md/02\344\275\277\347\224\250\347\272\277\347\250\213.md" +++ "b/md/02\344\275\277\347\224\250\347\272\277\347\250\213.md" @@ -91,7 +91,7 @@ auto sum(ForwardIt first, ForwardIt last){ std::size_t remainder = distance % num_threads; // 存储每个线程的结果 - std::vector results { num_threads }; + std::vector results(num_threads); // 存储关联线程的线程对象 std::vector threads; @@ -120,7 +120,7 @@ auto sum(ForwardIt first, ForwardIt last){ } ``` -> [运行](https://godbolt.org/z/MdrP98o13)测试。 +> [运行](https://godbolt.org/z/9qW55aY6j)测试。 我们写了这样一个求和函数 `sum`,接受两个迭代器计算它们范围中对象的和。 diff --git "a/md/04\345\220\214\346\255\245\346\223\215\344\275\234.md" "b/md/04\345\220\214\346\255\245\346\223\215\344\275\234.md" index 74f89d78..85e001f0 100644 --- "a/md/04\345\220\214\346\255\245\346\223\215\344\275\234.md" +++ "b/md/04\345\220\214\346\255\245\346\223\215\344\275\234.md" @@ -371,12 +371,11 @@ private: 该代码实现了一个简单的**后台音频播放类型**,通过**条件变量**和**互斥量**确保播放线程 `playMusic` 只在只在**有音频任务需要播放时工作**(当外部通过调用 `addAudioPath()` 向队列添加播放任务时)。在没有任务时,线程保持等待状态,避免占用 CPU 资源影响主程序的运行。 > ### 注意 +> > 其实这段代码还存在着一个初始化顺序导致的问题,见 [**#27**](https://github.com/Mq-b/ModernCpp-ConcurrentProgramming-Tutorial/issues/27) 此外,关于提示音的播报,为了避免每次都手动添加路径,我们可以创建一个音频资源数组,便于使用: -此外,关于提示音的播报,为了避免每次都手动添加路径,我们可以创建一个音频资源数组,便于使用: - ```cpp static constexpr std::array soundResources{ "./sound/01初始化失败.ogg", @@ -416,8 +415,8 @@ enum SoundIndex { 如果是测试使用,不知道去哪生成这些语音播报,我们推荐 [`tts-vue`](https://github.com/LokerL/tts-vue)。 -> 我们的代码也可以在 Linux 中运行,并且整体仅需 C++11 标准,除了 `soundResources` 数组以外。 -> SFML 依赖于 [**FLAC**](https://xiph.org/flac/) 和 [**OpenAL**](https://www.openal.org/) 这两个库。在 Windows 上[下载](https://www.sfml-dev.org/download/sfml/2.5.1/)的 SFML 版本已包含这些依赖,但在 Linux 上需要用户自行下载并安装它们。如: +> 我们的代码也可以在 Linux 中运行,并且整体仅需 C++11 标准(除了 `soundResources` 数组)。 +> SFML 依赖于 [**FLAC**](https://xiph.org/flac/) 和 [**OpenAL**](https://www.openal.org/) 这两个库。官网上[下载](https://www.sfml-dev.org/download/sfml/2.5.1/)的 windows 版本的 SFML 已包含这些依赖,但在 Linux 上需要用户自行下载并安装它们。如: > ```shell > sudo apt-get install libflac-dev > sudo apt-get install libopenal-dev @@ -697,9 +696,9 @@ auto sum(ForwardIt first, ForwardIt last) { std::size_t remainder = distance % num_threads; // 存储每个线程要执行的任务 - std::vector>tasks; + std::vector> tasks; // 和每一个任务进行关联的 future 用于获取返回值 - std::vector>futures(num_threads); + std::vector> futures(num_threads); // 存储关联线程的线程对象 std::vector threads; @@ -733,7 +732,7 @@ auto sum(ForwardIt first, ForwardIt last) { } ``` -> [运行](https://godbolt.org/z/r19MYcv6e)测试。 +> [运行](https://godbolt.org/z/79fe1Gvcq)测试。 相比于之前,其实不同无非是定义了 `std::vector> tasks` 与 `std::vector> futures` ,然后在循环中制造任务插入容器,关联 future,再放到线程中执行。最后汇总的时候写一个循环,`futures[i].get()` 获取任务的返回值加起来即可。 diff --git "a/md/05\345\206\205\345\255\230\346\250\241\345\236\213\344\270\216\345\216\237\345\255\220\346\223\215\344\275\234.md" "b/md/05\345\206\205\345\255\230\346\250\241\345\236\213\344\270\216\345\216\237\345\255\220\346\223\215\344\275\234.md" index 94e41e85..1b576d77 100644 --- "a/md/05\345\206\205\345\255\230\346\250\241\345\236\213\344\270\216\345\216\237\345\255\220\346\223\215\344\275\234.md" +++ "b/md/05\345\206\205\345\255\230\346\250\241\345\236\213\344\270\216\345\216\237\345\255\220\346\223\215\344\275\234.md" @@ -228,7 +228,7 @@ struct trivial_type { > > 最后强调一下:任何 [std::atomic](https://zh.cppreference.com/w/cpp/atomic/atomic) 类型,**初始化不是原子操作**。 -### `st::atomic_flag` +### `std::atomic_flag` `std::atomic_flag` 是最简单的原子类型,这个类型的对象可以在两个状态间切换:**设置(true)**和**清除(false)**。它很简单,通常只是用作构建一些库设施,不会单独使用或直接面向普通开发者。 @@ -815,4 +815,4 @@ RISC-V 采用的也是**弱序内存模型**(weakly-ordered memory model), \ No newline at end of file +--> diff --git "a/md/06\345\215\217\347\250\213.md" "b/md/06\345\215\217\347\250\213.md" index e696832f..eeb2e994 100644 --- "a/md/06\345\215\217\347\250\213.md" +++ "b/md/06\345\215\217\347\250\213.md" @@ -1,9 +1,23 @@ # 协程 +## 前言 + 既然是“**现代**” C++ 并发编程教程,怎么能不聊协程呢? C++20 引入了协程语法,新增了三个用作协程的关键字:`co_await`、`co_yield`、`co_return`。但并未给出标准**协程库**,协程库在 C++23 被引入。 -希望您拥有 `gcc14`、`clang18`,最新的 MSVC。 +希望您拥有 `gcc14`、`clang19`,`Visual Studio 2022 17.11`。 + +我们假设您对 C++20 的协程一无所知、假设您对协程这个概念一无所知、假设您不了解其它语言的协程实现(如 Python、java)。 + +--- + +绝大多数人对协程基本可以说是一无所知,但是应该都听过这个名字,大概是因为这些编程语言都在新版本中引入它作为核心语言特性。 + +这带来了许多的热度,不过这并不完全算是好事,许多的营销号一样的讲述,基本全部都是错误的。 + +据我所知,在我在 B站发布正经 C++20 协程的教学视频之前,几乎所有打着 C++ 旗号说什么协程的,都是胡言乱语。不过也有一些不错的,如:[**等疾风**](https://space.bilibili.com/35186937)、[**happyyang的百草园**](https://space.bilibili.com/312883756),都出过至少算作正经的 C++20 协程的教学视频。 + +- **C++20 的协程是复杂的**。 -> C++ 20 协程的使用尚不成熟,等待后续更新讲解..... +不管是使用上还是概念上,引入了许多新颖的做法。