From 42b616c96d7957df1f7ba5b01f081f0e6cb1db06 Mon Sep 17 00:00:00 2001 From: downdemo Date: Sat, 28 Sep 2019 00:47:39 +0800 Subject: [PATCH] init --- .gitignore | 1 + README.md | 81 +- docs/01_managing_thread.md | 538 +++++++ docs/02_sharing_data_between_thread.md | 631 ++++++++ docs/03_synchronizing_concurrent_operation.md | 1312 +++++++++++++++++ ...ory_model_and_operations_on_atomic_type.md | 925 ++++++++++++ ...ng_lock_based_concurrent_data_structure.md | 471 ++++++ ...ing_lock_free_concurrent_data_structure.md | 599 ++++++++ docs/07_designing_concurrent_code.md | 949 ++++++++++++ docs/08_advanced_thread_management.md | 743 ++++++++++ docs/09_parallel_algorithm.md | 244 +++ ...and_debugging_multithreaded_application.md | 112 ++ docs/_config.yml | 1 + docs/images/3-1.png | Bin 0 -> 134318 bytes docs/images/4-1.png | Bin 0 -> 44354 bytes docs/images/4-2.png | Bin 0 -> 65145 bytes docs/images/4-3.png | Bin 0 -> 72901 bytes docs/images/7-1.png | Bin 0 -> 15539 bytes docs/images/7-2.png | Bin 0 -> 32561 bytes docs/images/8-1.png | Bin 0 -> 23264 bytes docs/images/8-2.png | Bin 0 -> 23000 bytes docs/images/ref-filesystem-1.png | Bin 0 -> 60989 bytes docs/images/ref-memory_management-1.png | Bin 0 -> 94041 bytes docs/index.md | 80 + docs/reference/IO.md | 51 + docs/reference/deadlocks.md | 135 ++ docs/reference/file_systems.md | 184 +++ docs/reference/memory_management.md | 243 +++ docs/reference/processes_and_threads.md | 570 +++++++ src/atm.cpp | 602 ++++++++ src/concurrent_list.hpp | 84 ++ src/concurrent_map.hpp | 92 ++ src/concurrent_queue.hpp | 110 ++ src/concurrent_stack.hpp | 57 + src/hierarchical_mutex.cpp | 90 ++ src/lock_free_stack.hpp | 82 ++ src/lock_free_stack_hazard_pointer.hpp | 142 ++ src/thread_poll.hpp | 55 + 38 files changed, 9183 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 docs/01_managing_thread.md create mode 100644 docs/02_sharing_data_between_thread.md create mode 100644 docs/03_synchronizing_concurrent_operation.md create mode 100644 docs/04_the_cpp_memory_model_and_operations_on_atomic_type.md create mode 100644 docs/05_designing_lock_based_concurrent_data_structure.md create mode 100644 docs/06_designing_lock_free_concurrent_data_structure.md create mode 100644 docs/07_designing_concurrent_code.md create mode 100644 docs/08_advanced_thread_management.md create mode 100644 docs/09_parallel_algorithm.md create mode 100644 docs/10_testing_and_debugging_multithreaded_application.md create mode 100644 docs/_config.yml create mode 100644 docs/images/3-1.png create mode 100644 docs/images/4-1.png create mode 100644 docs/images/4-2.png create mode 100644 docs/images/4-3.png create mode 100644 docs/images/7-1.png create mode 100644 docs/images/7-2.png create mode 100644 docs/images/8-1.png create mode 100644 docs/images/8-2.png create mode 100644 docs/images/ref-filesystem-1.png create mode 100644 docs/images/ref-memory_management-1.png create mode 100644 docs/index.md create mode 100644 docs/reference/IO.md create mode 100644 docs/reference/deadlocks.md create mode 100644 docs/reference/file_systems.md create mode 100644 docs/reference/memory_management.md create mode 100644 docs/reference/processes_and_threads.md create mode 100644 src/atm.cpp create mode 100644 src/concurrent_list.hpp create mode 100644 src/concurrent_map.hpp create mode 100644 src/concurrent_queue.hpp create mode 100644 src/concurrent_stack.hpp create mode 100644 src/hierarchical_mutex.cpp create mode 100644 src/lock_free_stack.hpp create mode 100644 src/lock_free_stack_hazard_pointer.hpp create mode 100644 src/thread_poll.hpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..722d5e7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.vscode diff --git a/README.md b/README.md index 26766df..a1d298e 100644 --- a/README.md +++ b/README.md @@ -1 +1,80 @@ -# Cpp-Concurrency-in-Action-2ed +* C++11 引入了 Boost 线程库作为标准线程库,作者 Anthony Williams 为介绍其特性,于 2012 年出版了 *[C++ Concurrency in Action](https://book.douban.com/subject/4130141/)* 一书,并顺应 C++17 于 2019 年 2 月出版了[第二版](https://book.douban.com/subject/27036085/)。*[C++ Concurrency in Action 2ed](https://learning.oreilly.com/library/view/c-concurrency-in/9781617294693/)* 前五章介绍了[线程支持库](https://en.cppreference.com/w/cpp/thread)的基本用法,后六章从实践角度介绍了并发编程的设计思想,相比第一版多介绍了一些 C++17 特性,如 [std::scoped_lock](https://en.cppreference.com/w/cpp/thread/scoped_lock)、[std::shared_mutex](https://en.cppreference.com/w/cpp/thread/shared_mutex),并多出一章(第十章)介绍 [C++17 标准库并行算法](https://en.cppreference.com/w/cpp/header/execution),此外个人会在相应处补充 C++20 相关特性,如 [std::jthread](https://en.cppreference.com/w/cpp/thread/jthread)、[std::counting_semaphore](https://en.cppreference.com/w/cpp/thread/counting_semaphore)、[std::barrier](https://en.cppreference.com/w/cpp/thread/barrier)、[std::latch](https://en.cppreference.com/w/cpp/thread/latch) 等。阅读本书前可参考 [Andrew S. Tanenbaum](https://en.wikipedia.org/wiki/Andrew_S._Tanenbaum) 的 [*Modern Operating Systems*](https://book.douban.com/subject/25864553/),预备操作系统的基础知识([进程与线程](https://github.com/downdemo/Cpp-Concurrency-in-Action-2ed/blob/master/docs/reference/processes_and_threads.md)、[死锁](https://github.com/downdemo/Cpp-Concurrency-in-Action-2ed/blob/master/docs/reference/deadlocks.md)、[内存管理](https://github.com/downdemo/Cpp-Concurrency-in-Action-2ed/blob/master/docs/reference/memory_management.md)、[文件系统](https://github.com/downdemo/Cpp-Concurrency-in-Action-2ed/blob/master/docs/reference/file_systems.md)、[I/O](https://github.com/downdemo/Cpp-Concurrency-in-Action-2ed/blob/master/docs/reference/IO.md) 等)。此为个人笔记,仅供参考,更详细内容见[原书](https://learning.oreilly.com/library/view/c-concurrency-in/9781617294693/)。 + +## [线程支持库](https://en.cppreference.com/w/cpp/thread) + +1. [线程管理(Managing thread)](https://github.com/downdemo/Cpp-Concurrency-in-Action-2ed/blob/master/docs/01_managing_thread.md):[\](https://en.cppreference.com/w/cpp/header/thread) +2. [线程间共享数据(Sharing data between thread)](https://github.com/downdemo/Cpp-Concurrency-in-Action-2ed/blob/master/docs/02_sharing_data_between_thread.md):[\](https://en.cppreference.com/w/cpp/header/mutex)、[\](https://en.cppreference.com/w/cpp/header/shared_mutex) +3. [同步并发操作(Synchronizing concurrent operation)](https://github.com/downdemo/Cpp-Concurrency-in-Action-2ed/blob/master/docs/03_synchronizing_concurrent_operation.md):[\](https://en.cppreference.com/w/cpp/header/condition_variable)、[\](https://en.cppreference.com/w/cpp/header/semaphore)、[\](https://en.cppreference.com/w/cpp/header/barrier)、[\](https://en.cppreference.com/w/cpp/header/latch)、[\](https://en.cppreference.com/w/cpp/header/future)、[\](https://en.cppreference.com/w/cpp/header/chrono)、[\](https://en.cppreference.com/w/cpp/header/ratio) +4. [C++ 内存模型和基于原子类型的操作(The C++ memory model and operations on atomic type)](https://github.com/downdemo/Cpp-Concurrency-in-Action-2ed/blob/master/docs/04_the_cpp_memory_model_and_operations_on_atomic_type.md):[\](https://en.cppreference.com/w/cpp/header/atomic) + +## 并发编程实践 + +5. [基于锁的并发数据结构的设计(Designing lock-based concurrent data structure)](https://github.com/downdemo/Cpp-Concurrency-in-Action-2ed/blob/master/docs/05_designing_lock_based_concurrent_data_structure.md) +6. [无锁并发数据结构的设计(Designing lock-free concurrent data structure)](https://github.com/downdemo/Cpp-Concurrency-in-Action-2ed/blob/master/docs/06_designing_lock_free_concurrent_data_structure.md) +7. [并发代码的设计(Designing concurrent code)](https://github.com/downdemo/Cpp-Concurrency-in-Action-2ed/blob/master/docs/07_designing_concurrent_code.md) +8. [高级线程管理(Advanced thread management)](https://github.com/downdemo/Cpp-Concurrency-in-Action-2ed/blob/master/docs/08_advanced_thread_management.md) +9. [并行算法(Parallel algorithm)](https://github.com/downdemo/Cpp-Concurrency-in-Action-2ed/blob/master/docs/09_parallel_algorithm.md):[\](https://en.cppreference.com/w/cpp/header/execution) +10. [多线程应用的测试与调试(Testing and debugging multithreaded application)](https://github.com/downdemo/Cpp-Concurrency-in-Action-2ed/blob/master/docs/10_testing_and_debugging_multithreaded_application.md) + +## 标准库相关头文件 + +|头文件|说明| +|:-:|:-:| +|[\](https://en.cppreference.com/w/cpp/header/thread)、[\](https://en.cppreference.com/w/cpp/header/stop_token)|线程| +|[\](https://en.cppreference.com/w/cpp/header/mutex)、[\](https://en.cppreference.com/w/cpp/header/shared_mutex)|锁| +|[\](https://en.cppreference.com/w/cpp/header/condition_variable)|条件变量| +|[\](https://en.cppreference.com/w/cpp/header/semaphore)|信号量| +|[\](https://en.cppreference.com/w/cpp/header/barrier)、[\](https://en.cppreference.com/w/cpp/header/latch)|屏障| +|[\](https://en.cppreference.com/w/cpp/header/future)|异步处理的结果| +|[\](https://en.cppreference.com/w/cpp/header/chrono)|时钟| +|[\](https://en.cppreference.com/w/cpp/header/ratio)|编译期有理数算数| +|[\](https://en.cppreference.com/w/cpp/header/atomic)|原子类型和原子操作| +|[\](https://en.cppreference.com/w/cpp/header/execution)|标准库算法执行策略| + +## 并发库对比 + +### [C++11 Thread](https://en.cppreference.com/w/cpp/thread) + +|特性|API| +|:-:|:-:| +|thread|[std::thread](https://en.cppreference.com/w/cpp/thread/thread)| +|mutex|[std::mutex](https://en.cppreference.com/w/cpp/thread/mutex)、[std::lock_guard](https://en.cppreference.com/w/cpp/thread/lock_guard)、[std::unique_lock](https://en.cppreference.com/w/cpp/thread/unique_lock)| +|condition variable|[std::condition_variable](https://en.cppreference.com/w/cpp/thread/condition_variable)、[std::condition_variable_any](https://en.cppreference.com/w/cpp/thread/condition_variable_any)| +|atomic|[std::atomic](https://en.cppreference.com/w/cpp/atomic/atomic)、[std::atomic_thread_fence](https://en.cppreference.com/w/cpp/atomic/atomic_thread_fence)| +|future|[std::future](https://en.cppreference.com/w/cpp/thread/future)、[std::shared_future](https://en.cppreference.com/w/cpp/thread/shared_future)| +|interruption|无| + +### [Boost Thread](https://www.boost.org/doc/libs/1_82_0/doc/html/thread.html) + +|特性|API| +|:-:|:-:| +|thread|[boost::thread](https://www.boost.org/doc/libs/1_82_0/doc/html/thread/thread_management.html#thread.thread_management.thread)| +|mutex|[boost::mutex](https://www.boost.org/doc/libs/1_82_0/doc/html/thread/synchronization.html#thread.synchronization.mutex_types.mutex)、[boost::lock_guard](https://www.boost.org/doc/libs/1_82_0/doc/html/thread/synchronization.html#thread.synchronization.lock_guard.lock_guard)、[boost::unique_lock](https://www.boost.org/doc/libs/1_82_0/doc/html/thread/synchronization.html#thread.synchronization.locks.unique_lock)| +|condition variable|[boost::condition_variable](https://www.boost.org/doc/libs/1_82_0/doc/html/thread/synchronization.html#thread.synchronization.condvar_ref.condition_variable)、[boost::condition_variable_any](https://www.boost.org/doc/libs/1_82_0/doc/html/thread/synchronization.html#thread.synchronization.condvar_ref.condition_variable_any)| +|atomic|无| +|future|[boost::future](https://www.boost.org/doc/libs/1_82_0/doc/html/thread/synchronization.html#thread.synchronization.futures.reference.unique_future)、[boost::shared_future](https://www.boost.org/doc/libs/1_82_0/doc/html/thread/synchronization.html#thread.synchronization.futures.reference.shared_future)| +|interruption|[thread::interrupt](https://www.boost.org/doc/libs/1_82_0/doc/html/thread/thread_management.html#thread.thread_management.thread.interrupt)| + +### [POSIX Thread](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/pthread.h.html) + +|特性|API| +|:-:|:-:| +|thread|[pthread_create](http://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_create.html)、[pthread_detach](http://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_detach.html#)、[pthread_join](http://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_join.html#)| +|mutex|[pthread_mutex_lock、pthread_mutex_unlock](http://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_mutex_lock.html)| +|condition variable|[pthread_cond_wait](http://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_cond_wait.html)、[pthread_cond_signal](https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_cond_signal.html)| +|atomic|无| +|future|无| +|interruption|[pthread_cancel](http://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_cancel.html)| + +### [Java Thread](https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/lang/Thread.html) + +|特性|API| +|:-:|:-:| +|thread|[java.lang.Thread](https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/lang/Thread.html)| +|mutex|[synchronized blocks](http://tutorials.jenkov.com/java-concurrency/synchronized.html)| +|condition variable|[java.lang.Object.wait](https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/lang/Object.html#wait())、[java.lang.Object.notify](https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/lang/Object.html#notify())| +|atomic|volatile 变量、[java.util.concurrent.atomic](https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/util/concurrent/atomic/package-summary.html)| +|future|[java.util.concurrent.Future](https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/util/concurrent/Future.html)| +|interruption|[java.lang.Thread.interrupt](https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/lang/Thread.html#interrupt())| +|线程安全的容器|[java.util.concurrent](https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/util/concurrent/package-summary.html) 中的容器| +|线程池|[java.util.concurrent.ThreadPoolExecutor](https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/util/concurrent/ThreadPoolExecutor.html)| diff --git a/docs/01_managing_thread.md b/docs/01_managing_thread.md new file mode 100644 index 0000000..b84f7bf --- /dev/null +++ b/docs/01_managing_thread.md @@ -0,0 +1,538 @@ +## [std::thread](https://en.cppreference.com/w/cpp/thread/thread) + +* 每个程序有一个执行 main() 函数的主线程,将函数添加为 [std::thread](https://en.cppreference.com/w/cpp/thread/thread) 的参数即可启动另一个线程,两个线程会同时运行 + +```cpp +#include +#include + +void f() { std::cout << "hello world"; } + +int main() { + std::thread t{f}; + t.join(); // 等待新起的线程退出 +} +``` + +* [std::thread](https://en.cppreference.com/w/cpp/thread/thread) 的参数也可以是函数对象或者 lambda + +```cpp +#include +#include + +struct A { + void operator()() const { std::cout << 1; } +}; + +int main() { + A a; + std::thread t1(a); // 会调用 A 的拷贝构造函数 + std::thread t2(A()); // most vexing parse,声明名为 t2 参数类型为 A 的函数 + std::thread t3{A()}; + std::thread t4((A())); + std::thread t5{[] { std::cout << 1; }}; + t1.join(); + t3.join(); + t4.join(); + t5.join(); +} +``` + +* 在线程销毁前要对其调用 [join](https://en.cppreference.com/w/cpp/thread/thread/join) 等待线程退出或 [detach](https://en.cppreference.com/w/cpp/thread/thread/detach) 将线程分离,否则 [std::thread](https://en.cppreference.com/w/cpp/thread/thread) 的析构函数会调用 [std::terminate](https://en.cppreference.com/w/cpp/error/terminate) 终止程序,注意分离线程可能出现空悬引用的隐患 + +```cpp +#include +#include + +class A { + public: + A(int& x) : x_(x) {} + + void operator()() const { + for (int i = 0; i < 1000000; ++i) { + call(x_); // 存在对象析构后引用空悬的隐患 + } + } + + private: + void call(int& x) {} + + private: + int& x_; +}; + +void f() { + int x = 0; + A a{x}; + std::thread t{a}; + t.detach(); // 不等待 t 结束 +} // 函数结束后 t 可能还在运行,而 x 已经销毁,a.x_ 为空悬引用 + +int main() { + std::thread t{f}; // 导致空悬引用 + t.join(); +} +``` + +* [join](https://en.cppreference.com/w/cpp/thread/thread/join) 会在线程结束后清理 [std::thread](https://en.cppreference.com/w/cpp/thread/thread),使其与完成的线程不再关联,因此对一个线程只能进行一次 [join](https://en.cppreference.com/w/cpp/thread/thread/join) + +```cpp +#include + +int main() { + std::thread t([] {}); + t.join(); + t.join(); // 错误 +} +``` + +* 如果线程运行过程中发生异常,之后的 [join](https://en.cppreference.com/w/cpp/thread/thread/join) 会被忽略,为此需要捕获异常,并在抛出异常前 [join](https://en.cppreference.com/w/cpp/thread/thread/join) + +```cpp +#include + +int main() { + std::thread t([] {}); + try { + throw 0; + } catch (int x) { + t.join(); // 处理异常前先 join() + throw x; // 再将异常抛出 + } + t.join(); // 之前抛异常,不会执行到此处 +} +``` + +* C++20 提供了 [std::jthread](https://en.cppreference.com/w/cpp/thread/jthread),它会在析构函数中对线程 [join](https://en.cppreference.com/w/cpp/thread/thread/join) + +```cpp +#include + +int main() { + std::jthread t([] {}); +} +``` + +* [detach](https://en.cppreference.com/w/cpp/thread/thread/detach) 分离线程会让线程在后台运行,一般将这种在后台运行的线程称为守护线程,守护线程与主线程无法直接交互,也不能被 [join](https://en.cppreference.com/w/cpp/thread/thread/join) + +```cpp +std::thread t([] {}); +t.detach(); +assert(!t.joinable()); +``` + +* 创建守护线程一般是为了长时间运行,比如有一个文档处理应用,为了同时编辑多个文档,每次新开一个文档,就可以开一个对应的守护线程 + +```cpp +void edit_document(const std::string& filename) { + open_document_and_display_gui(filename); + while (!done_editing()) { + user_command cmd = get_user_input(); + if (cmd.type == open_new_document) { + const std::string new_name = get_filename_from_user(); + std::thread t(edit_document, new_name); + t.detach(); + } else { + process_user_input(cmd); + } + } +} +``` + +## 为带参数的函数创建线程 + +* 有参数的函数也能传给 [std::thread](https://en.cppreference.com/w/cpp/thread/thread),参数的默认实参会被忽略 + +```cpp +#include + +void f(int i = 1) {} + +int main() { + std::thread t{f, 42}; // std::thread t{f} 则会出错,因为默认实参会被忽略 + t.join(); +} +``` + +* 参数的引用类型也会被忽略,为此要使用 [std::ref](https://en.cppreference.com/w/cpp/utility/functional/ref) + +```cpp +#include +#include + +void f(int& i) { ++i; } + +int main() { + int i = 1; + std::thread t{f, std::ref(i)}; + t.join(); + assert(i == 2); +} +``` + +* 如果对一个实例的 non-static 成员函数创建线程,第一个参数类型为成员函数指针,第二个参数类型为实例指针,后续参数为函数参数 + +```cpp +#include +#include + +class A { + public: + void f(int i) { std::cout << i; } +}; + +int main() { + A a; + std::thread t1{&A::f, &a, 42}; // 调用 a->f(42) + std::thread t2{&A::f, a, 42}; // 拷贝构造 tmp_a,再调用 tmp_a.f(42) + t1.join(); + t2.join(); +} +``` + +* 如果要为参数是 move-only 类型的函数创建线程,则需要使用 [std::move](https://en.cppreference.com/w/cpp/utility/move) 传入参数 + +```cpp +#include +#include +#include + +void f(std::unique_ptr p) { std::cout << *p; } + +int main() { + std::unique_ptr p(new int(42)); + std::thread t{f, std::move(p)}; + t.join(); +} +``` + +## 转移线程所有权 + +* [std::thread](https://en.cppreference.com/w/cpp/thread/thread) 是 move-only 类型,不能拷贝,只能通过移动转移所有权,但不能转移所有权到 joinable 的线程 + +```cpp +#include +#include + +void f() {} +void g() {} + +int main() { + std::thread a{f}; + std::thread b = std::move(a); + assert(!a.joinable()); + assert(b.joinable()); + a = std::thread{g}; + assert(a.joinable()); + assert(b.joinable()); + // a = std::move(b); // 错误,不能转移所有权到 joinable 的线程 + a.join(); + a = std::move(b); + assert(a.joinable()); + assert(!b.joinable()); + a.join(); +} +``` + +* 移动操作同样适用于支持移动的容器 + +```cpp +#include +#include +#include + +int main() { + std::vector v; + for (int i = 0; i < 10; ++i) { + v.emplace_back([] {}); + } + std::for_each(std::begin(v), std::end(v), std::mem_fn(&std::thread::join)); +} +``` + +* [std::thread](https://en.cppreference.com/w/cpp/thread/thread) 可以作为函数返回值 + +```cpp +#include + +std::thread f() { + return std::thread{[] {}}; +} + +int main() { + std::thread t{f()}; + t.join(); +} +``` + +* [std::thread](https://en.cppreference.com/w/cpp/thread/thread) 也可以作为函数参数 + +```cpp +#include +#include + +void f(std::thread t) { t.join(); } + +int main() { + f(std::thread([] {})); + std::thread t([] {}); + f(std::move(t)); +} +``` + +* 实现一个可以直接用 [std::thread](https://en.cppreference.com/w/cpp/thread/thread) 构造的自动清理线程的类 + +```cpp +#include +#include +#include + +class scoped_thread { + public: + explicit scoped_thread(std::thread x) : t_(std::move(x)) { + if (!t_.joinable()) { + throw std::logic_error("no thread"); + } + } + ~scoped_thread() { t_.join(); } + scoped_thread(const scoped_thread&) = delete; + scoped_thread& operator=(const scoped_thread&) = delete; + + private: + std::thread t_; +}; + +int main() { + scoped_thread t{std::thread{[] {}}}; +} +``` + +* 类似 [std::jthread](https://en.cppreference.com/w/cpp/thread/jthread) 的类 + +```cpp +#include + +class Jthread { + public: + Jthread() noexcept = default; + + template + explicit Jthread(T&& f, Ts&&... args) + : t_(std::forward(f), std::forward(args)...) {} + + explicit Jthread(std::thread x) noexcept : t_(std::move(x)) {} + Jthread(Jthread&& rhs) noexcept : t_(std::move(rhs.t_)) {} + + Jthread& operator=(Jthread&& rhs) noexcept { + if (joinable()) { + join(); + } + t_ = std::move(rhs.t_); + return *this; + } + + Jthread& operator=(std::thread t) noexcept { + if (joinable()) { + join(); + } + t_ = std::move(t); + return *this; + } + + ~Jthread() noexcept { + if (joinable()) { + join(); + } + } + + void swap(Jthread&& rhs) noexcept { t_.swap(rhs.t_); } + std::thread::id get_id() const noexcept { return t_.get_id(); } + bool joinable() const noexcept { return t_.joinable(); } + void join() { t_.join(); } + void detach() { t_.detach(); } + std::thread& as_thread() noexcept { return t_; } + const std::thread& as_thread() const noexcept { return t_; } + + private: + std::thread t_; +}; + +int main() { + Jthread t{[] {}}; +} +``` + +## 查看硬件支持的线程数量 + +* [hardware_concurrency](https://en.cppreference.com/w/cpp/thread/thread/hardware_concurrency) 会返回硬件支持的并发线程数 + +```cpp +#include +#include + +int main() { + unsigned int n = std::thread::hardware_concurrency(); + std::cout << n << " concurrent threads are supported.\n"; +} +``` + +* 并行版本的 [std::accumulate](https://en.cppreference.com/w/cpp/algorithm/accumulate) + +```cpp +#include +#include +#include +#include +#include +#include +#include + +template +struct accumulate_block { + void operator()(Iterator first, Iterator last, T& res) { + res = std::accumulate(first, last, res); + } +}; + +template +T parallel_accumulate(Iterator first, Iterator last, T init) { + long len = std::distance(first, last); + if (!len) { + return init; + } + long min_per_thread = 25; + long max_threads = (len + min_per_thread - 1) / min_per_thread; + long hardware_threads = std::thread::hardware_concurrency(); + long num_threads = // 线程数量 + std::min(hardware_threads == 0 ? 2 : hardware_threads, max_threads); + long block_size = len / num_threads; // 每个线程中的数据量 + std::vector res(num_threads); + std::vector threads(num_threads - 1); // 已有主线程故少一个线程 + Iterator block_start = first; + for (long i = 0; i < num_threads - 1; ++i) { + Iterator block_end = block_start; + std::advance(block_end, block_size); // block_end 指向当前块尾部 + threads[i] = std::thread{accumulate_block{}, block_start, + block_end, std::ref(res[i])}; + block_start = block_end; + } + accumulate_block()(block_start, last, res[num_threads - 1]); + std::for_each(threads.begin(), threads.end(), + std::mem_fn(&std::thread::join)); + return std::accumulate(res.begin(), res.end(), init); +} + +int main() { + std::vector v(1000000); + std::iota(std::begin(v), std::end(v), 0); + long res = std::accumulate(std::begin(v), std::end(v), 0); + assert(parallel_accumulate(std::begin(v), std::end(v), 0) == res); +} +``` + +## 线程号 + +* 可以通过对线程实例调用成员函数 [get_id](https://en.cppreference.com/w/cpp/thread/thread/get_id) 或在当前线程中调用 [std::this_thread::get_id](https://en.cppreference.com/w/cpp/thread/get_id) 获取 [线程号](https://en.cppreference.com/w/cpp/thread/thread/id),其本质是一个无符号整型的封装,允许拷贝和比较,因此可以将其作为容器的键值,如果两个线程的线程号相等,则两者是同一线程或都是空线程(一般空线程的线程号为 0) + +```cpp +#include +#include + +#ifdef _WIN32 +#include +#elif defined __GNUC__ +#include +#include + +#endif + +void print_current_thread_id() { +#ifdef _WIN32 + std::cout << std::this_thread::get_id() << std::endl; // 19840 + std::cout << GetCurrentThreadId() << std::endl; // 19840 + std::cout << GetThreadId(GetCurrentThread()) << std::endl; // 19840 +#elif defined __GNUC__ + std::cout << std::this_thread::get_id() << std::endl; // 1 + std::cout << pthread_self() << std::endl; // 140250646003520 + std::cout << getpid() << std::endl; // 1502109,ps aux 显示此 pid + std::cout << syscall(SYS_gettid) << std::endl; // 1502109 +#endif +} + +std::thread::id master_thread_id = std::this_thread::get_id(); + +void f() { + if (std::this_thread::get_id() == master_thread_id) { + // do_master_thread_work(); + } + // do_common_work(); +} + +int main() { + print_current_thread_id(); + f(); + std::thread t{f}; + t.join(); +} +``` + +## CPU 亲和性(affinity) + +* 将线程绑定到一个指定的 CPU core 上运行,避免多核 CPU 上下文切换和 cache miss 的开销 + +```cpp +#ifdef _WIN32 +#include +#elif defined __GNUC__ +#include +#include +#include +#endif + +#include +#include + +void affinity_cpu(std::thread::native_handle_type t, int cpu_id) { +#ifdef _WIN32 + if (!SetThreadAffinityMask(t, 1ll << cpu_id)) { + std::cerr << "fail to affinity" << GetLastError() << std::endl; + } +#elif defined __GNUC__ + cpu_set_t cpu_set; + CPU_ZERO(&cpu_set); + CPU_SET(cpu_id, &cpu_set); + int res = pthread_setaffinity_np(t, sizeof(cpu_set), &cpu_set); + if (res != 0) { + errno = res; + std::cerr << "fail to affinity" << strerror(errno) << std::endl; + } +#endif +} + +void affinity_cpu_on_current_thread(int cpu_id) { +#ifdef _WIN32 + if (!SetThreadAffinityMask(GetCurrentThread(), 1ll << cpu_id)) { + std::cerr << "fail to affinity" << GetLastError() << std::endl; + } +#elif defined __GNUC__ + cpu_set_t cpu_set; + CPU_ZERO(&cpu_set); + CPU_SET(cpu_id, &cpu_set); + int res = pthread_setaffinity_np(pthread_self(), sizeof(cpu_set), &cpu_set); + if (res != 0) { + errno = res; + std::cerr << "fail to affinity" << strerror(errno) << std::endl; + } +#endif +} + +void f() { affinity_cpu_on_current_thread(0); } + +int main() { + std::thread t1{[] {}}; + affinity_cpu(t1.native_handle(), 1); + std::thread t2{f}; + t1.join(); + t2.join(); +} +``` diff --git a/docs/02_sharing_data_between_thread.md b/docs/02_sharing_data_between_thread.md new file mode 100644 index 0000000..70d699e --- /dev/null +++ b/docs/02_sharing_data_between_thread.md @@ -0,0 +1,631 @@ +## 线程间共享数据存在的问题 + +* 不变量(invariant):关于一个特定数据结构总为 true 的语句,比如 `双向链表的两个相邻节点 A 和 B,A 的后指针一定指向 B,B 的前指针一定指向 A`。有时程序为了方便会暂时破坏不变量,这通常发生于更新复杂数据结构的过程中,比如删除双向链表中的一个节点 N,要先让 N 的前一个节点指向 N 的后一个节点(不变量被破坏),再让 N 的后节点指向前节点,最后删除 N(此时不变量重新恢复) +* 线程修改共享数据时,就会发生破坏不变量的情况,此时如果有其他线程访问,就可能导致不变量被永久性破坏,这就是 race condition +* 如果线程执行顺序的先后对结果无影响,则为不需要关心的良性竞争。需要关心的是不变量被破坏时产生的 race condition +* C++ 标准中定义了 data race 的概念,指代一种特定的 race condition,即并发修改单个对象。data race 会造成未定义行为 +* race condition 要求一个线程进行时,另一线程访问同一数据块,出现问题时很难复现,因此编程时需要使用大量复杂操作来避免 race condition + +## 互斥锁(mutex) + +* 使用 mutex 在访问共享数据前加锁,访问结束后解锁。一个线程用特定的 mutex 锁定后,其他线程必须等待该线程的 mutex 解锁才能访问共享数据 +* C++11 提供了 [std::mutex](https://en.cppreference.com/w/cpp/thread/mutex) 来创建一个 mutex,可通过 [lock](https://en.cppreference.com/w/cpp/thread/mutex/lock) 加锁,通过 [unlock](https://en.cppreference.com/w/cpp/thread/mutex/unlock) 解锁。一般不手动使用这两个成员函数,而是使用 [std::lock_guard](https://en.cppreference.com/w/cpp/thread/lock_guard) 来自动处理加锁与解锁,它在构造时接受一个 mutex,并会调用 mutex.lock(),析构时会调用 mutex.unlock() + +```cpp +#include +#include + +class A { + public: + void lock() { std::cout << "lock" << std::endl; } + void unlock() { std::cout << "unlock" << std::endl; } +}; + +int main() { + A a; + { + std::lock_guard l(a); // lock + } // unlock +} +``` + +* C++17 提供了的 [std::scoped_lock](https://en.cppreference.com/w/cpp/thread/scoped_lock),它可以接受任意数量的 mutex,并将这些 mutex 传给 [std::lock](https://en.cppreference.com/w/cpp/thread/lock) 来同时上锁,它会对其中一个 mutex 调用 lock(),对其他调用 try_lock(),若 try_lock() 返回 false 则对已经上锁的 mutex 调用 unlock(),然后重新进行下一轮上锁,标准未规定下一轮的上锁顺序,可能不一致,重复此过程直到所有 mutex 上锁,从而达到同时上锁的效果。C++17 支持类模板实参推断,可以省略模板参数 + +```cpp +#include +#include + +class A { + public: + void lock() { std::cout << 1; } + void unlock() { std::cout << 2; } + bool try_lock() { + std::cout << 3; + return true; + } +}; + +class B { + public: + void lock() { std::cout << 4; } + void unlock() { std::cout << 5; } + bool try_lock() { + std::cout << 6; + return true; + } +}; + +int main() { + A a; + B b; + { + std::scoped_lock l(a, b); // 16 + std::cout << std::endl; + } // 25 +} +``` + +* 一般 mutex 和要保护的数据一起放在类中,定义为 private 数据成员,而非全局变量,这样能让代码更清晰。但如果某个成员函数返回指向数据成员的指针或引用,则通过这个指针的访问行为不会被 mutex 限制,因此需要谨慎设置接口,确保 mutex 能锁住数据 + +```cpp +#include + +class A { + public: + void f() {} +}; + +class B { + public: + A* get_data() { + std::lock_guard l(m_); + return &data_; + } + + private: + std::mutex m_; + A data_; +}; + +int main() { + B b; + A* p = b.get_data(); + p->f(); // 未锁定 mutex 的情况下访问数据 +} +``` + +* 即便在很简单的接口中,也可能遇到 race condition + +```cpp +std::stack s; +if (!s.empty()) { + int n = s.top(); // 此时其他线程 pop 就会获取错误的 top + s.pop(); +} +``` + +* 上述代码先检查非空再获取栈顶元素,在单线程中是安全的,但在多线程中,检查非空之后,如果其他线程先 pop,就会导致当前线程 top 出错。另一个潜在的竞争是,如果两个线程都未 pop,而是分别获取了 top,虽然不会产生未定义行为,但这种对同一值处理了两次的行为更为严重,因为看起来没有任何错误,很难定位 bug +* 既然如此,为什么不直接让 pop 返回栈顶元素?原因在于,构造返回值的过程可能抛异常,弹出后未返回会导致数据丢失。比如有一个元素为 vector 的 stack,拷贝 vector 需要在堆上分配内存,如果系统负载严重或资源有限(比如 vector 有大量元素),vector 的拷贝构造函数就会抛出 [std::bad_alloc](https://en.cppreference.com/w/cpp/memory/new/bad_alloc) 异常。如果 pop 可以返回栈顶元素值,返回一定是最后执行的语句,stack 在返回前已经弹出了元素,但如果拷贝返回值时抛出异常,就会导致弹出的数据丢失(从栈上移除但拷贝失败)。因此 [std::stack](https://en.cppreference.com/w/cpp/container/stack) 的设计者将这个操作分解为 top 和 pop 两部分 +* 下面思考几种把 top 和 pop 合为一步的方法。第一种容易想到的方法是传入一个引用来获取结果值,这种方式的明显缺点是,需要构造一个栈元素类型的实例,这是不现实的,为了获取结果而临时构造一个对象并不划算,元素类型可能不支持赋值(比如用户自定义某个类型),构造函数可能还需要一些参数 + +```cpp +std::vector res; +s.pop(res); +``` + +* 因为 pop 返回值时只担心该过程抛异常,第二种方案是为元素类型设置不抛异常的拷贝或移动构造函数,使用 [std::is_nothrow_copy_constructible](https://en.cppreference.com/w/cpp/types/is_copy_constructible) 和 [std::is_nothrow_move_constructible](https://en.cppreference.com/w/cpp/types/is_move_constructible)。但这种方式过于局限,只支持拷贝或移动不抛异常的类型 +* 第三种方案是返回指向弹出元素的指针,指针可以自由拷贝且不会抛异常,[std::shared_ptr](https://en.cppreference.com/w/cpp/memory/shared_ptr) 是个不错的选择,但这个方案的开销太大,尤其是对于内置类型来说,比如 int 为 4 字节, `shared_ptr` 为 16 字节,开销是原来的 4 倍 +* 第四种方案是结合方案一二或者一三,比如结合方案一三实现一个线程安全的 stack + +```cpp +#include +#include +#include +#include +#include + +struct EmptyStack : std::exception { + const char* what() const noexcept { return "empty stack!"; } +}; + +template +class ConcurrentStack { + public: + ConcurrentStack() = default; + + ConcurrentStack(const ConcurrentStack& rhs) { + std::lock_guard l(rhs.m_); + s_ = rhs.s_; + } + + ConcurrentStack& operator=(const ConcurrentStack&) = delete; + + void push(T x) { + std::lock_guard l(m_); + s_.push(std::move(x)); + } + + bool empty() const { + std::lock_guard l(m_); + return s_.empty(); + } + + std::shared_ptr pop() { + std::lock_guard l(m_); + if (s_.empty()) { + throw EmptyStack(); + } + auto res = std::make_shared(std::move(s_.top())); + s_.pop(); + return res; + } + + void pop(T& res) { + std::lock_guard l(m_); + if (s_.empty()) { + throw EmptyStack(); + } + res = std::move(s_.top()); + s_.pop(); + } + + private: + mutable std::mutex m_; + std::stack s_; +}; +``` + +* 之前锁的粒度(锁保护的数据量大小)太小,保护操作覆盖不周全,这里的粒度就较大,覆盖了大量操作。但并非粒度越大越好,如果锁粒度太大,过多线程请求竞争占用资源时,并发的性能就会较差 +* 如果给定操作需要对多个 mutex 上锁时,就会引入一个新的潜在问题,即死锁 + +## 死锁 + +* 死锁的四个必要条件:互斥、占有且等待、不可抢占、循环等待 +* 避免死锁通常建议让两个 mutex 以相同顺序上锁,总是先锁 A 再锁 B,但这并不适用所有情况。[std::lock](https://en.cppreference.com/w/cpp/thread/lock) 可以同时对多个 mutex 上锁,并且没有死锁风险,它可能抛异常,此时就不会上锁,因此要么都锁住,要么都不锁 + +```cpp +#include +#include + +struct A { + explicit A(int n) : n_(n) {} + std::mutex m_; + int n_; +}; + +void f(A &a, A &b, int n) { + if (&a == &b) { + return; // 防止对同一对象重复加锁 + } + std::lock(a.m_, b.m_); // 同时上锁防止死锁 + // 下面按固定顺序加锁,看似不会有死锁的问题 + // 但如果没有 std::lock 同时上锁,另一线程中执行 f(b, a, n) + // 两个锁的顺序就反了过来,从而可能导致死锁 + std::lock_guard lock1(a.m_, std::adopt_lock); + std::lock_guard lock2(b.m_, std::adopt_lock); + + // 等价实现,先不上锁,后同时上锁 + // std::unique_lock lock1(a.m_, std::defer_lock); + // std::unique_lock lock2(b.m_, std::defer_lock); + // std::lock(lock1, lock2); + + a.n_ -= n; + b.n_ += n; +} + +int main() { + A x{70}; + A y{30}; + + std::thread t1(f, std::ref(x), std::ref(y), 20); + std::thread t2(f, std::ref(y), std::ref(x), 10); + + t1.join(); + t2.join(); +} +``` + +* [std::unique_lock](https://en.cppreference.com/w/cpp/thread/unique_lock) 在构造时接受一个 mutex,并会调用 mutex.lock(),析构时会调用 mutex.unlock() + +```cpp +#include +#include + +class A { + public: + void lock() { std::cout << "lock" << std::endl; } + void unlock() { std::cout << "unlock" << std::endl; } +}; + +int main() { + A a; + { + std::unique_lock l(a); // lock + } // unlock +} +``` + +* [std::lock_guard](https://en.cppreference.com/w/cpp/thread/lock_guard) 未提供任何接口且不支持拷贝和移动,而 [std::unique_lock](https://en.cppreference.com/w/cpp/thread/unique_lock) 多提供了一些接口,使用更灵活,占用的空间也多一点。一种要求灵活性的情况是转移锁的所有权到另一个作用域 + +```cpp +std::unique_lock get_lock() { + extern std::mutex m; + std::unique_lock l(m); + prepare_data(); + return l; // 不需要 std::move,编译器负责调用移动构造函数 +} + +void f() { + std::unique_lock l(get_lock()); + do_something(); +} +``` + +* 对一些费时的操作上锁可能造成很多操作被阻塞,可以在面对这些操作时先解锁 + +```cpp +void process_file_data() { + std::unique_lock l(m); + auto data = get_data(); + l.unlock(); // 费时操作没有必要持有锁,先解锁 + auto res = process(data); + l.lock(); // 写入数据前上锁 + write_result(data, res); +} +``` + +* C++17 最优的同时上锁方法是使用 [std::scoped_lock](https://en.cppreference.com/w/cpp/thread/scoped_lock) +* 解决死锁并不简单,[std::lock](https://en.cppreference.com/w/cpp/thread/lock) 和 [std::scoped_lock](https://en.cppreference.com/w/cpp/thread/scoped_lock) 无法获取其中的锁,此时解决死锁更依赖于开发者的能力。避免死锁有四个建议 + * 第一个避免死锁的建议是,一个线程已经获取一个锁时就不要获取第二个。如果每个线程只有一个锁,锁上就不会产生死锁(但除了互斥锁,其他方面也可能造成死锁,比如即使无锁,线程间相互等待也可能造成死锁) + * 第二个建议是,持有锁时避免调用用户提供的代码。用户提供的代码可能做任何时,包括获取锁,如果持有锁时调用用户代码获取锁,就会违反第一个建议,并造成死锁。但有时调用用户代码是无法避免的 + * 第三个建议是,按固定顺序获取锁。如果必须获取多个锁且不能用 [std::lock](https://en.cppreference.com/w/cpp/thread/lock) 同时获取,最好在每个线程上用固定顺序获取。上面的例子虽然是按固定顺序获取锁,但如果不同时加锁就会出现死锁,对于这种情况的建议是规定固定的调用顺序 + * 第四个建议是使用层级锁,如果一个锁被低层持有,就不允许在高层再上锁 +* 层级锁实现如下 + +```cpp +#include +#include +#include + +class HierarchicalMutex { + public: + explicit HierarchicalMutex(int hierarchy_value) + : cur_hierarchy_(hierarchy_value), prev_hierarchy_(0) {} + + void lock() { + validate_hierarchy(); // 层级错误则抛异常 + m_.lock(); + update_hierarchy(); + } + + bool try_lock() { + validate_hierarchy(); + if (!m_.try_lock()) { + return false; + } + update_hierarchy(); + return true; + } + + void unlock() { + if (thread_hierarchy_ != cur_hierarchy_) { + throw std::logic_error("mutex hierarchy violated"); + } + thread_hierarchy_ = prev_hierarchy_; // 恢复前一线程的层级值 + m_.unlock(); + } + + private: + void validate_hierarchy() { + if (thread_hierarchy_ <= cur_hierarchy_) { + throw std::logic_error("mutex hierarchy violated"); + } + } + + void update_hierarchy() { + // 先存储当前线程的层级值(用于解锁时恢复) + prev_hierarchy_ = thread_hierarchy_; + // 再把其设为锁的层级值 + thread_hierarchy_ = cur_hierarchy_; + } + + private: + std::mutex m_; + const int cur_hierarchy_; + int prev_hierarchy_; + static thread_local int thread_hierarchy_; // 所在线程的层级值 +}; + +// static thread_local 表示存活于一个线程周期 +thread_local int HierarchicalMutex::thread_hierarchy_(INT_MAX); + +HierarchicalMutex high(10000); +HierarchicalMutex mid(6000); +HierarchicalMutex low(5000); + +void lf() { // 最低层函数 + std::lock_guard l(low); + // 调用 low.lock(),thread_hierarchy_ 为 INT_MAX, + // cur_hierarchy_ 为 5000,thread_hierarchy_ > cur_hierarchy_, + // 通过检查,上锁,prev_hierarchy_ 更新为 INT_MAX, + // thread_hierarchy_ 更新为 5000 +} // 调用 low.unlock(),thread_hierarchy_ == cur_hierarchy_, +// 通过检查,thread_hierarchy_ 恢复为 prev_hierarchy_ 保存的 INT_MAX,解锁 + +void hf() { + std::lock_guard l(high); // high.cur_hierarchy_ 为 10000 + // thread_hierarchy_ 为 10000,可以调用低层函数 + lf(); // thread_hierarchy_ 从 10000 更新为 5000 + // thread_hierarchy_ 恢复为 10000 +} // thread_hierarchy_ 恢复为 INT_MAX + +void mf() { + std::lock_guard l(mid); // thread_hierarchy_ 为 6000 + hf(); // thread_hierarchy_ < high.cur_hierarchy_,违反了层级结构,抛异常 +} + +int main() { + lf(); + hf(); + try { + mf(); + } catch (std::logic_error& ex) { + std::cout << ex.what(); + } +} +``` + +### 读写锁(reader-writer mutex) + +* 有时会希望对一个数据上锁时,根据情况,对某些操作相当于不上锁,可以并发访问,对某些操作保持上锁,同时最多只允许一个线程访问。比如对于需要经常访问但很少更新的缓存数据,用 [std::mutex](https://en.cppreference.com/w/cpp/thread/mutex) 加锁会导致同时最多只有一个线程可以读数据,这就需要用上读写锁,读写锁允许多个线程并发读但仅一个线程写 +* C++14 提供了 [std::shared_timed_mutex](https://en.cppreference.com/w/cpp/thread/shared_timed_mutex),C++17 提供了接口更少性能更高的 [std::shared_mutex](https://en.cppreference.com/w/cpp/thread/shared_mutex),如果多个线程调用 shared_mutex.lock_shared(),多个线程可以同时读,如果此时有一个写线程调用 shared_mutex.lock(),则读线程均会等待该写线程调用 shared_mutex.unlock()。C++11 没有提供读写锁,可使用 [boost::shared_mutex](https://www.boost.org/doc/libs/1_82_0/doc/html/thread/synchronization.html#thread.synchronization.mutex_types.shared_mutex) +* C++14 提供了 [std::shared_lock](https://en.cppreference.com/w/cpp/thread/shared_lock),它在构造时接受一个 mutex,并会调用 mutex.lock_shared(),析构时会调用 mutex.unlock_shared() + +```cpp +#include +#include + +class A { + public: + void lock_shared() { std::cout << "lock_shared" << std::endl; } + void unlock_shared() { std::cout << "unlock_shared" << std::endl; } +}; + +int main() { + A a; + { + std::shared_lock l(a); // lock_shared + } // unlock_shared +} +``` + +* 对于 [std::shared_mutex](https://en.cppreference.com/w/cpp/thread/shared_mutex),通常在读线程中用 [std::shared_lock](https://en.cppreference.com/w/cpp/thread/shared_lock) 管理,在写线程中用 [std::unique_lock](https://en.cppreference.com/w/cpp/thread/unique_lock) 管理 + +```cpp +class A { + public: + int read() const { + std::shared_lock l(m_); + return n_; + } + + int write() { + std::unique_lock l(m_); + return ++n_; + } + + private: + mutable std::shared_mutex m_; + int n_ = 0; +}; +``` + +### 递归锁 + +* [std::mutex](https://en.cppreference.com/w/cpp/thread/mutex) 是不可重入的,未释放前再次上锁是未定义行为 + +```cpp +#include + +class A { + public: + void f() { + m_.lock(); + m_.unlock(); + } + + void g() { + m_.lock(); + f(); + m_.unlock(); + } + + private: + std::mutex m_; +}; + +int main() { + A{}.g(); // Undefined Behavior +} +``` + +* 为此 C++ 提供了 [std::recursive_mutex](https://en.cppreference.com/w/cpp/thread/recursive_mutex),它可以在一个线程上多次获取锁,但在其他线程获取锁之前必须释放所有的锁 + +```cpp +#include + +class A { + public: + void f() { + m_.lock(); + m_.unlock(); + } + + void g() { + m_.lock(); + f(); + m_.unlock(); + } + + private: + std::recursive_mutex m_; +}; + +int main() { + A{}.g(); // OK +} +``` + +* 多数情况下,如果需要递归锁,说明代码设计存在问题。比如一个类的每个成员函数都会上锁,一个成员函数调用另一个成员函数,就可能多次上锁,这种情况用递归锁就可以避免产生未定义行为。但显然这个设计本身是有问题的,更好的办法是提取其中一个函数作为 private 成员并且不上锁,其他成员先上锁再调用该函数 + +## 对并发初始化的保护 + +* 除了对并发访问共享数据的保护,另一种常见的情况是对并发初始化的保护 + +```cpp +#include +#include +#include + +class A { + public: + void f() {} +}; + +std::shared_ptr p; +std::mutex m; + +void init() { + m.lock(); + if (!p) { + p.reset(new A); + } + m.unlock(); + p->f(); +} + +int main() { + std::thread t1{init}; + std::thread t2{init}; + + t1.join(); + t2.join(); +} +``` + +* 上锁只是为了保护初始化过程,会不必要地影响性能,一种容易想到的优化方式是双重检查锁模式,但这存在潜在的 race condition + +```cpp +#include +#include +#include + +class A { + public: + void f() {} +}; + +std::shared_ptr p; +std::mutex m; + +void init() { + if (!p) { // 未上锁,其他线程可能在执行 #1,则此时 p 不为空 + std::lock_guard l(m); + if (!p) { + p.reset(new A); // 1 + // 先分配内存,再在内存上构造 A 的实例并返回内存的指针,最后让 p 指向它 + // 也可能先让 p 指向它,再在内存上构造 A 的实例 + } + } + p->f(); // p 可能指向一块还未构造实例的内存,从而崩溃 +} + +int main() { + std::thread t1{init}; + std::thread t2{init}; + + t1.join(); + t2.join(); +} +``` + +* 为此,C++11 提供了 [std::once_flag](https://en.cppreference.com/w/cpp/thread/once_flag) 和 [std::call_once](https://en.cppreference.com/w/cpp/thread/call_once) 来保证对某个操作只执行一次 + +```cpp +#include +#include +#include + +class A { + public: + void f() {} +}; + +std::shared_ptr p; +std::once_flag flag; + +void init() { + std::call_once(flag, [&] { p.reset(new A); }); + p->f(); +} + +int main() { + std::thread t1{init}; + std::thread t2{init}; + + t1.join(); + t2.join(); +} +``` + +* [std::call_once](https://en.cppreference.com/w/cpp/thread/call_once) 也可以用在类中 + +```cpp +#include +#include +#include + +class A { + public: + void f() { + std::call_once(flag_, &A::print, this); + std::cout << 2; + } + + private: + void print() { std::cout << 1; } + + private: + std::once_flag flag_; +}; + +int main() { + A a; + std::thread t1{&A::f, &a}; + std::thread t2{&A::f, &a}; + t1.join(); + t2.join(); +} // 122 +``` + +* static 局部变量在声明后就完成了初始化,这存在潜在的 race condition,如果多线程的控制流同时到达 static 局部变量的声明处,即使变量已在一个线程中初始化,其他线程并不知晓,仍会对其尝试初始化。为此,C++11 规定,如果 static 局部变量正在初始化,线程到达此处时,将等待其完成,从而避免了 race condition。只有一个全局实例时,可以直接用 static 而不需要 [std::call_once](https://en.cppreference.com/w/cpp/thread/call_once) + +```cpp +template +class Singleton { + public: + static T& Instance(); + Singleton(const Singleton&) = delete; + Singleton& operator=(const Singleton&) = delete; + + private: + Singleton() = default; + ~Singleton() = default; +}; + +template +T& Singleton::Instance() { + static T instance; + return instance; +} +``` diff --git a/docs/03_synchronizing_concurrent_operation.md b/docs/03_synchronizing_concurrent_operation.md new file mode 100644 index 0000000..ee09fdf --- /dev/null +++ b/docs/03_synchronizing_concurrent_operation.md @@ -0,0 +1,1312 @@ +## 条件变量(condition variable) + +* 在并发编程中,一种常见的需求是,一个线程等待另一个线程完成某个事件后,再继续执行任务。对于这种情况,标准库提供了 [std::condition_variable](https://en.cppreference.com/w/cpp/thread/condition_variable) + +```cpp +#include +#include +#include +#include + +class A { + public: + void step1() { + { + std::lock_guard l(m_); + step1_done_ = true; + } + std::cout << 1; + cv_.notify_one(); + } + + void step2() { + std::unique_lock l(m_); + cv_.wait(l, [this] { return step1_done_; }); + step2_done_ = true; + std::cout << 2; + cv_.notify_one(); + } + + void step3() { + std::unique_lock l(m_); + cv_.wait(l, [this] { return step2_done_; }); + std::cout << 3; + } + + private: + std::mutex m_; + std::condition_variable cv_; + bool step1_done_ = false; + bool step2_done_ = false; +}; + +int main() { + A a; + std::thread t1(&A::step1, &a); + std::thread t2(&A::step2, &a); + std::thread t3(&A::step3, &a); + t1.join(); + t2.join(); + t3.join(); +} // maybe: 123, 213, 231, 1-block +``` + +* 有多个能唤醒的任务时,[notify_one()](https://en.cppreference.com/w/cpp/thread/condition_variable/notify_one) 会随机唤醒一个 + +```cpp +#include +#include +#include +#include + +class A { + public: + void wait1() { + std::unique_lock l(m_); + cv_.wait(l, [this] { return done_; }); + std::cout << 1; + } + + void wait2() { + std::unique_lock l(m_); + cv_.wait(l, [this] { return done_; }); + std::cout << 2; + } + + void signal() { + { + std::lock_guard l(m_); + done_ = true; + } + cv_.notify_all(); + } + + private: + std::mutex m_; + std::condition_variable cv_; + bool done_ = false; +}; + +int main() { + A a; + std::thread t1(&A::wait1, &a); + std::thread t2(&A::wait2, &a); + std::thread t3(&A::signal, &a); + t1.join(); + t2.join(); + t3.join(); +} // 12 or 21 +``` + +* [std::condition_variable](https://en.cppreference.com/w/cpp/thread/condition_variable) 只能与 [std::unique_lock](https://en.cppreference.com/w/cpp/thread/unique_lock) 协作,为此标准库提供了更通用的 [std::condition_variable_any](https://en.cppreference.com/w/cpp/thread/condition_variable_any) + +```cpp +#include +#include +#include +#include + +class Mutex { + public: + void lock() {} + void unlock() {} +}; + +class A { + public: + void signal() { + std::cout << 1; + cv_.notify_one(); + } + + void wait() { + Mutex m; + cv_.wait(m); + std::cout << 2; + } + + private: + std::condition_variable_any cv_; +}; + +int main() { + A a; + std::thread t1(&A::signal, &a); + std::thread t2(&A::wait, &a); + t1.join(); + t2.join(); +} // 12 +``` + +* 与 [std::stack](https://en.cppreference.com/w/cpp/container/stack) 一样,[std::queue](https://en.cppreference.com/w/cpp/container/queue) 的 front 和 pop 存在 race condition,为此将 front 和 pop 合并成 try_pop 函数,此外利用 [std::condition_variable](https://en.cppreference.com/w/cpp/thread/condition_variable) 实现 wait_and_pop 的接口,当没有元素可弹出时会阻塞,直至有元素可弹出 + +```cpp +#include +#include +#include +#include + +template +class ConcurrentQueue { + public: + ConcurrentQueue() = default; + + ConcurrentQueue(const ConcurrentQueue& rhs) { + std::lock_guard l(rhs.m_); + q_ = rhs.q_; + } + + void push(T x) { + std::lock_guard l(m_); + q_.push(std::move(x)); + cv_.notify_one(); + } + + void wait_and_pop(T& res) { + std::unique_lock l(m_); + cv_.wait(l, [this] { return !q_.empty(); }); + res = std::move(q_.front()); + q_.pop(); + } + + std::shared_ptr wait_and_pop() { + std::unique_lock l(m_); + cv_.wait(l, [this] { return !q_.empty(); }); + auto res = std::make_shared(std::move(q_.front())); + q_.pop(); + return res; + } + + bool try_pop(T& res) { + std::lock_guard l(m_); + if (q_.empty()) { + return false; + } + res = std::move(q_.front()); + q_.pop(); + return true; + } + + std::shared_ptr try_pop() { + std::lock_guard l(m_); + if (q_.empty()) { + return nullptr; + } + auto res = std::make_shared(std::move(q_.front())); + q_.pop(); + return res; + } + + bool empty() const { + std::lock_guard l(m_); + // 其他线程可能有此对象(拷贝构造)所以要上锁 + return q_.empty(); + } + + private: + mutable std::mutex m_; + std::condition_variable cv_; + std::queue q_; +}; +``` + +* 这个实现有一个异常安全问题,[notify_one()](https://en.cppreference.com/w/cpp/thread/condition_variable/notify_one) 只会唤醒一个线程,如果多线程等待时,被唤醒线程 wait_and_pop 中抛出异常(如构造 [std::shared_ptr](https://en.cppreference.com/w/cpp/memory/shared_ptr) 对象时可能抛异常),其余线程将永远不被唤醒。用 [notify_all()](https://en.cppreference.com/w/cpp/thread/condition_variable/notify_all) 可解决此问题,但会有不必要的唤醒,抛出异常时再调用 [notify_one()](https://en.cppreference.com/w/cpp/thread/condition_variable/notify_one) 更好一些。对于此场景,最好的做法是将内部的 `std::queue` 改为 `std::queue>`,[std::shared_ptr](https://en.cppreference.com/w/cpp/memory/shared_ptr) 对象只在 push 中构造,这样 wait_and_pop 就不会抛异常 + +```cpp +#include +#include +#include +#include +#include + +template +class ConcurrentQueue { + public: + ConcurrentQueue() = default; + + ConcurrentQueue(const ConcurrentQueue& rhs) { + std::lock_guard l(rhs.m_); + q_ = rhs.q_; + } + + void push(T x) { + auto data = std::make_shared(std::move(x)); + std::lock_guard l(m_); + q_.push(data); + cv_.notify_one(); + } + + void wait_and_pop(T& res) { + std::unique_lock l(m_); + cv_.wait(l, [this] { return !q_.empty(); }); + res = std::move(*q_.front()); + q_.pop(); + } + + std::shared_ptr wait_and_pop() { + std::unique_lock l(m_); + cv_.wait(l, [this] { return !q_.empty(); }); + auto res = q_.front(); + q_.pop(); + return res; + } + + bool try_pop(T& res) { + std::lock_guard l(m_); + if (q_.empty()) { + return false; + } + res = std::move(*q_.front()); + q_.pop(); + return true; + } + + std::shared_ptr try_pop() { + std::lock_guard l(m_); + if (q_.empty()) { + return nullptr; + } + auto res = q_.front(); + q_.pop(); + return res; + } + + bool empty() const { + std::lock_guard l(m_); + return q_.empty(); + } + + private: + mutable std::mutex m_; + std::condition_variable cv_; + std::queue> q_; +}; +``` + +## 信号量(semaphore) + +* 信号量用于实现多线程之间指定数量的事件通知,P 操作对信号量减 1,V 操作对信号量加 1,若 P 操作将导致信号量小于 0 则阻塞,直至可减少信号量为止。C++20 提供了 [std::counting_semaphore](https://en.cppreference.com/w/cpp/thread/counting_semaphore) ,构造时通过模板参数设置信号量的最大值,通过函数参数设置信号量的初始值,[acquire()](https://en.cppreference.com/w/cpp/thread/counting_semaphore/acquire) 即 P 操作,会在信号量值大于 0 时将信号量减 1,否则阻塞至可以减 1 为止,[release()](https://en.cppreference.com/w/cpp/thread/counting_semaphore/release) 即 V 操作,会将信号量加上指定值(不指定则加 1),并唤醒指定数量的被 [acquire()](https://en.cppreference.com/w/cpp/thread/counting_semaphore/acquire) 阻塞的信号量 + +```cpp +#include +#include +#include + +class A { + public: + void wait1() { + sem_.acquire(); + std::cout << 1; + } + + void wait2() { + sem_.acquire(); + std::cout << 2; + } + + void signal() { sem_.release(2); } + + private: + std::counting_semaphore<2> sem_{0}; // 初始值 0,最大值 2 +}; + +int main() { + A a; + std::thread t1(&A::wait1, &a); + std::thread t2(&A::wait2, &a); + std::thread t3(&A::signal, &a); + t1.join(); + t2.join(); + t3.join(); +} // 12 or 21 +``` + +* [std::binary_semaphore](https://en.cppreference.com/w/cpp/thread/counting_semaphore) 是最大值为 1 的信号量,它是模板参数为 1 的 [std::counting_semaphore](https://en.cppreference.com/w/cpp/thread/counting_semaphore) 的别名 + +```cpp +#include +#include +#include + +class A { + public: + void wait() { + sem_.acquire(); + std::cout << 2; + } + + void signal() { + std::cout << 1; + sem_.release(); + } + + private: + std::binary_semaphore sem_{0}; +}; + +int main() { + A a; + std::thread t1(&A::wait, &a); + std::thread t2(&A::signal, &a); + t1.join(); + t2.join(); +} // 12 +``` + +## 屏障(barrier) + +* C++20 提供了 [std::barrier](https://en.cppreference.com/w/cpp/thread/barrier),它用一个值作为要等待的线程的数量来构造,调用 [std::barrier::arrive_and_wait](https://en.cppreference.com/w/cpp/thread/barrier/arrive_and_wait) 会阻塞至所有线程完成任务(因此称为屏障),当最后一个线程完成任务时,所有线程被释放,barrier 被重置。构造 [std::barrier](https://en.cppreference.com/w/cpp/thread/barrier) 时可以额外设置一个 noexcept 函数,当所有线程到达阻塞点时,由其中一个线程运行该函数。如果想从线程集中移除线程,则在该线程中对 barrier 调用 [std::barrier::arrive_and_drop](https://en.cppreference.com/w/cpp/thread/barrier/arrive_and_drop) + +```cpp +#include +#include +#include +#include + +class A { + public: + void f() { + std::barrier sync_point{3, [&]() noexcept { ++i_; }}; + for (auto& x : tasks_) { + x = std::thread([&] { + std::cout << 1; + sync_point.arrive_and_wait(); + assert(i_ == 1); + std::cout << 2; + sync_point.arrive_and_wait(); + assert(i_ == 2); + std::cout << 3; + }); + } + for (auto& x : tasks_) { + x.join(); // 析构 barrier 前 join 所有使用了 barrier 的线程 + } // 析构 barrier 时,线程再调用 barrier 的成员函数是 undefined behavior + } + + private: + std::thread tasks_[3] = {}; + int i_ = 0; +}; + +int main() { + A a; + a.f(); +} +``` + +* C++20 提供了 [std::latch](https://en.cppreference.com/w/cpp/thread/latch) 作为一次性屏障,它用一个值作为计数器的初始值来构造,[std::latch::count_down](https://en.cppreference.com/w/cpp/thread/latch/count_down) 将计数器减 1,[std::latch::wait](https://en.cppreference.com/w/cpp/thread/latch/wait) 将阻塞至计数器为 0,如果想让计数器减一并阻塞至为 0 则可以调用 [std::latch::arrive_and_wait](https://en.cppreference.com/w/cpp/thread/latch/arrive_and_wait) + +```cpp +#include +#include +#include +#include + +class A { + public: + void f() { + for (auto& x : data_) { + x.t = std::jthread([&] { + x.s += x.s; + done_.count_down(); + }); + } + done_.wait(); + for (auto& x : data_) { + std::cout << x.s << std::endl; + } + } + + private: + struct { + std::string s; + std::jthread t; + } data_[3] = { + {"hello"}, + {"down"}, + {"demo"}, + }; + + std::latch done_{3}; +}; + +int main() { + A a; + a.f(); +} +``` + +## 期值(future) + +* [std::thread](https://en.cppreference.com/w/cpp/thread/thread) 只能运行函数,无法获取函数的返回值,为此标准库提供了 [std::future](https://en.cppreference.com/w/cpp/thread/future) 来关联线程运行的函数和函数的返回结果,这种获取结果的方式是异步的。通过 [std::async()](https://en.cppreference.com/w/cpp/thread/async) 创建异步任务的 [std::future](https://en.cppreference.com/w/cpp/thread/future),[std::async](https://en.cppreference.com/w/cpp/thread/async) 的创建任务的传参方式和 [std::thread](https://en.cppreference.com/w/cpp/thread/thread) 一样 + +```cpp +#include +#include + +class A { + public: + int f(int i) { return i; } +}; + +int main() { + A a; + std::future res = std::async(&A::f, &a, 1); + std::cout << res.get(); // 1,阻塞至线程返回结果 +} +``` + +* [std::future](https://en.cppreference.com/w/cpp/thread/future) 只能 [get()](https://en.cppreference.com/w/cpp/thread/future/get) 一次 + +```cpp +#include +#include + +int main() { + std::future res = std::async([] {}); + res.get(); + try { + res.get(); + } catch (const std::future_error& e) { + std::cout << e.what() << std::endl; // no state + } +} +``` + +* [std::async](https://en.cppreference.com/w/cpp/thread/async) 的第一个参数可以指定为枚举 [std::launch](https://en.cppreference.com/w/cpp/thread/launch) 的值,用于设置任务的运行策略 + +```cpp +namespace std { +enum class launch { // names for launch options passed to async + async = 0x1, // 运行新线程来执行任务 + deferred = 0x2 // 惰性求值,请求结果时才执行任务 +}; +} + +// std::async 创建任务默认使用两者 +std::async([] {}); // 等价于 std::async(std::launch::async | std::launch::deferred, [] {}) +``` + +* 还可以用 [std::packaged_task](https://en.cppreference.com/w/cpp/thread/packaged_task) 封装异步任务,它可以用于在两个线程之间传递任务,比如一个线程将异步任务加入队列,另一个线程不断从队列中取任务执行 + +```cpp +#include +#include + +int main() { + std::packaged_task task([](int i) { return i; }); + task(1); // 请求计算结果,内部的 future 将设置结果值 + std::future res = task.get_future(); + std::cout << res.get(); // 1 +} +``` + +* 一种更简单的情况是,只需要一个固定的返回值,为此使用 [std::promise](https://en.cppreference.com/w/cpp/thread/promise) 即可 + +```cpp +#include +#include + +int main() { + std::promise ps; + ps.set_value(1); // 内部的 future 将设置结果值 + std::future res = ps.get_future(); + std::cout << res.get(); // 1 +} +``` + +* [std::promise](https://en.cppreference.com/w/cpp/thread/promise) 可以实现事件通知的效果 + +```cpp +#include +#include +#include + +class A { + public: + void signal() { + std::cout << 1; + ps_.set_value(); + } + + void wait() { + std::future res = ps_.get_future(); + res.wait(); + std::cout << 2; + } + + private: + std::promise ps_; +}; + +int main() { + A a; + std::thread t1{&A::signal, &a}; + std::thread t2{&A::wait, &a}; + t1.join(); + t2.join(); +} +``` + +* 不同于 [std::condition_variable](https://en.cppreference.com/w/cpp/thread/condition_variable) 的是,[std::promise](https://en.cppreference.com/w/cpp/thread/promise) 只能通知一次,因此通常用来创建暂停状态的线程 + +```cpp +#include +#include +#include + +class A { + public: + void task() { std::cout << 1; } + void wait_for_task() { + ps_.get_future().wait(); + task(); + } + void signal() { ps_.set_value(); } + + private: + std::promise ps_; +}; + +void task() { std::cout << 1; } + +int main() { + A a; + std::thread t(&A::wait_for_task, &a); + a.signal(); + t.join(); +} +``` + +* [std::promise](https://en.cppreference.com/w/cpp/thread/promise) 只能关联一个 [std::future](https://en.cppreference.com/w/cpp/thread/future) + +```cpp +#include +#include + +int main() { + std::promise ps; + auto a = ps.get_future(); + try { + auto b = ps.get_future(); + } catch (const std::future_error& e) { + std::cout << e.what() << std::endl; // future already retrieved + } +} +``` + +* [std::future](https://en.cppreference.com/w/cpp/thread/future) 可以存储任务中的异常 + +```cpp +#include +#include +#include + +int main() { + std::future res = std::async([] { throw std::logic_error("error"); }); + try { + res.get(); + } catch (const std::exception& e) { + std::cout << e.what() << std::endl; + } +} +``` + +* [std::promise](https://en.cppreference.com/w/cpp/thread/promise) 需要手动存储异常 + +```cpp +#include +#include +#include + +int main() { + std::promise ps; + try { + throw std::logic_error("error"); + } catch (...) { + ps.set_exception(std::current_exception()); + } + auto res = ps.get_future(); + try { + res.get(); + } catch (const std::exception& e) { + std::cout << e.what() << std::endl; + } +} +``` + +* 注意 [set_value()](https://en.cppreference.com/w/cpp/thread/promise/set_value) 时的异常不会被设置到 future 中 + +```cpp +#include +#include +#include + +int main() { + std::promise ps; + try { + ps.set_value([] { + throw std::logic_error("error"); + return 0; + }()); + } catch (const std::exception& e) { + std::cout << e.what() << std::endl; + } + ps.set_value(1); + auto res = ps.get_future(); + std::cout << res.get(); // 1 +} +``` + +* 如果 [std::packaged_task](https://en.cppreference.com/w/cpp/thread/packaged_task) 和 [std::promise](https://en.cppreference.com/w/cpp/thread/promise) 直到析构都未设置值,[std::future::get()](https://en.cppreference.com/w/cpp/thread/future/get) 会抛异常 + +```cpp +#include +#include + +int main() { + std::future ft1; + std::future ft2; + { + std::packaged_task task([] {}); + std::promise ps; + ft1 = task.get_future(); + ft2 = ps.get_future(); + } + try { + ft1.get(); + } catch (const std::future_error& e) { + std::cout << e.what() << std::endl; // broken promise + } + try { + ft2.get(); + } catch (const std::future_error& e) { + std::cout << e.what() << std::endl; // broken promise + } +} +``` + +* [std::shared_future](https://en.cppreference.com/w/cpp/thread/shared_future) 可以多次获取结果,它可以通过 [std::future](https://en.cppreference.com/w/cpp/thread/future) 的右值构造。每一个 [std::shared_future](https://en.cppreference.com/w/cpp/thread/shared_future) 对象上返回的结果不同步,多线程访问 [std::shared_future](https://en.cppreference.com/w/cpp/thread/shared_future) 需要加锁防止 race condition,更好的方法是给每个线程拷贝一个 [std::shared_future](https://en.cppreference.com/w/cpp/thread/shared_future) 对象,这样就可以安全访问而无需加锁 + +```cpp +#include + +int main() { + std::promise ps; + std::future ft = ps.get_future(); + std::shared_future sf(std::move(ft)); + // 或直接 std::shared_future sf{ps.get_future()}; + ps.set_value(); + sf.get(); + sf.get(); +} +``` + +* 可以直接用 [std::future::share()](https://en.cppreference.com/w/cpp/thread/future/share) 生成 [std::shared_future](https://en.cppreference.com/w/cpp/thread/shared_future) + +```cpp +#include + +int main() { + std::promise ps; + auto sf = ps.get_future().share(); + ps.set_value(); + sf.get(); + sf.get(); +} +``` + +## 时钟 + +* 对于标准库来说,时钟是提供了四种信息的类 + * 当前时间,如 [std::chrono::system_clock::now()](https://en.cppreference.com/w/cpp/chrono/system_clock/now) + * 表示时间值的类型,如 [std::chrono::time_point](https://en.cppreference.com/w/cpp/chrono/time_point) + * 时钟节拍(一个 tick 的周期),一般一秒有 25 个 tick,一个周期则为 [std::ratio\<1, 25\>](https://en.cppreference.com/w/cpp/numeric/ratio/ratio) + * 通过时钟节拍确定时钟是否稳定(steady,匀速),如 [std::chrono::steady_clock::is_steady()](https://en.cppreference.com/w/cpp/chrono/steady_clock)(稳定时钟,代表系统时钟的真实时间)、[std::chrono::system_clock::is_steady()](https://en.cppreference.com/w/cpp/chrono/system_clock)(一般因为时钟可调节而不稳定,即使这是为了考虑本地时钟偏差的自动调节)、[high_resolution_clock::is_steady()](https://en.cppreference.com/w/cpp/chrono/high_resolution_clock)(最小节拍最高精度的时钟) +* 获取当前 UNIX 时间戳,单位为纳秒 + +```cpp +#ifdef _WIN32 +#include +#elif defined __GNUC__ +#include +#endif + +long long now_in_ns() { +#ifdef _WIN32 + return std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); +#elif defined __GNUC__ + struct timespec t; + clockid_t clk_id = CLOCK_REALTIME; + clock_gettime(clk_id, &t); + return t.tv_sec * 1e9 + t.tv_nsec; +#endif +} +``` + +* 用 [std::put_time](https://en.cppreference.com/w/cpp/io/manip/put_time) 格式化打印时间 + +```cpp +#include +#include +#include + +int main() { + std::chrono::system_clock::time_point t = std::chrono::system_clock::now(); + std::time_t c = std::chrono::system_clock::to_time_t(t); // UNIX 时间戳,秒 + // %F 即 %Y-%m-%d,%T 即 %H:%M:%S,如 2011-11-11 11:11:11 + std::cout << std::put_time(std::localtime(&c), "%F %T"); +} +``` + +* [std::chrono::duration](https://en.cppreference.com/w/cpp/chrono/duration) 表示时间间隔 + +```cpp +namespace std { +namespace chrono { +using nanoseconds = duration; +using microseconds = duration; +using milliseconds = duration; +using seconds = duration; +using minutes = duration>; +using hours = duration>; +// C++20 +using days = duration, hours::period>>; +using weeks = duration, days::period>>; +using years = duration, days::period>>; +using months = duration>>; +} // namespace chrono +} // namespace std +``` + +* C++14 在 [std::literals::chrono_literals](https://en.cppreference.com/w/cpp/symbol_index/chrono_literals) 中提供了表示时间的后缀 + +```cpp +#include +#include + +using namespace std::literals::chrono_literals; + +int main() { + auto a = 45min; + assert(a.count() == 45); + auto b = std::chrono::duration_cast(a); + assert(b.count() == 2700); + auto c = std::chrono::duration_cast(a); + assert(c.count() == 0); // 转换会截断 +} +``` + +* duration 支持四则运算 + +```cpp +#include +#include + +using namespace std::literals::chrono_literals; + +int main() { + assert((1h - 2 * 15min).count() == 30); + assert((0.5h + 2 * 15min + 60s).count() == 3660); +} +``` + +* 使用 duration 设置等待时间 + +```cpp +#include +#include +#include +#include + +int f() { + std::this_thread::sleep_for(std::chrono::seconds(1)); + return 1; +} + +int main() { + auto res = std::async(f); + if (res.wait_for(std::chrono::seconds(5)) == std::future_status::ready) { + std::cout << res.get(); + } +} +``` + +* [std::chrono::time_point](https://en.cppreference.com/w/cpp/chrono/time_point) 是表示时间的类型,值为从某个时间点开始计时的时间长度 + +```cpp +// 第一个模板参数为开始时间点的时钟类型,第二个为时间单位 +std::chrono::time_point +``` + +* [std::chrono::time_point](https://en.cppreference.com/w/cpp/chrono/time_point) 可以与 duration 加减,也可以与自身相减 + +```cpp +#include +#include + +int main() { + std::chrono::system_clock::time_point a = std::chrono::system_clock::now(); + std::chrono::system_clock::time_point b = a + std::chrono::hours(1); + long long diff = + std::chrono::duration_cast(b - a).count(); + assert(diff == 3600); +} +``` + +* 如下函数支持设置超时时间,函数最多阻塞至时间到期 + * [std::this_thread::sleep_for](https://en.cppreference.com/w/cpp/thread/sleep_for) + * [std::this_thread::sleep_until](https://en.cppreference.com/w/cpp/thread/sleep_until) + * [std::condition_variable::wait_for](https://en.cppreference.com/w/cpp/thread/condition_variable/wait_for) + * [std::condition_variable::wait_until](https://en.cppreference.com/w/cpp/thread/condition_variable/wait_until) + * [std::condition_variable_any::wait_for](https://en.cppreference.com/w/cpp/thread/condition_variable_any/wait_for) + * [std::condition_variable_any::wait_until](https://en.cppreference.com/w/cpp/thread/condition_variable_any/wait_until) + * [std::timed_mutex::try_lock_for](https://en.cppreference.com/w/cpp/thread/timed_mutex/try_lock_for) + * [std::timed_mutex::try_lock_until](https://en.cppreference.com/w/cpp/thread/timed_mutex/try_lock_until) + * [std::recursive_timed_mutex::try_lock_for](https://en.cppreference.com/w/cpp/thread/recursive_timed_mutex/try_lock_for) + * [std::recursive_timed_mutex::try_lock_until](https://en.cppreference.com/w/cpp/thread/recursive_timed_mutex/try_lock_until) + * [std::unique_lock::try_lock_for](https://en.cppreference.com/w/cpp/thread/unique_lock/try_lock_for) + * [std::unique_lock::try_lock_until](https://en.cppreference.com/w/cpp/thread/unique_lock/try_lock_until) + * [std::future::wait_for](https://en.cppreference.com/w/cpp/thread/future/wait_for) + * [std::future::wait_until](https://en.cppreference.com/w/cpp/thread/future/wait_until) + * [std::shared_future::wait_for](https://en.cppreference.com/w/cpp/thread/shared_future/wait_for) + * [std::shared_future::wait_until](https://en.cppreference.com/w/cpp/thread/shared_future/wait_until) + * [std::counting_semaphore::try_acquire_for](https://en.cppreference.com/w/cpp/thread/counting_semaphore/try_acquire_for) + * [std::counting_semaphore::try_acquire_until](https://en.cppreference.com/w/cpp/thread/counting_semaphore/try_acquire_until) + +* 由于不同机器的 CPU 频率不同,为了进行更精确的性能测试,通常不直接使用时间而是用 rdtsc 指令获取 CPU 周期,rdtsc 把 tsc 的低 32 位存放在 EAX,高 32 位存放在 EDX,不同 CPU 上获取的 tsc 可能不同步,如果开启了 constant_tsc 的 flag(通过 `cat /proc/cpuinfo | grep constant_tsc` 检查),不同 CPU 的不同核心的 tsc 都是同步的。如果是 Intel 的 CPU 但没有 constant_tsc 的 flag,同一处理器的不同核的 tsc 是同步的,不同 CPU 的不同核是不同步的。对于 CPU 可能乱序重排指令到 rdtsc 之后的情况,则需要在读取 tsc 后添加内存屏障。对于 CPU 可能乱序重排指令到 rdtsc 之前的情况,用 rdtscp 替代 rdtsc 即可,开销会多约 10 个时钟周期,但比使用 rdtsc 并在之前设置内存屏障开销小,使用 rdtscp 要求 CPU 支持该指令,可以通过 `cat /proc/cpuinfo | grep rdtscp` 查看 + +```cpp +#include + +#ifdef _WIN32 +#include +#endif + +static inline std::uint64_t read_tsc() { +#ifdef _WIN32 + return __rdtsc(); +#elif defined __GNUC__ + std::uint64_t res; + __asm__ __volatile__( + "rdtsc;" + "shl $32, %%rdx;" + "or %%rdx, %%rax" + : "=a"(res) + : + : "%rcx", "%rdx"); + return res; +#endif +} + +static inline std::uint64_t read_tscp() { +#ifdef _WIN32 + std::uint32_t t; + return __rdtscp(&t); +#elif defined __GNUC__ + std::uint64_t res; + __asm__ __volatile__( + "rdtscp;" + "shl $32, %%rdx;" + "or %%rdx, %%rax" + : "=a"(res) + : + : "%rcx", "%rdx"); + return res; +#endif +} + +static inline void fence() { +#ifdef _WIN32 + __faststorefence(); +#elif defined __GNUC__ + __asm__ __volatile__("mfence" : : : "memory"); +#endif +} + +inline std::uint64_t tsc_begin() { + std::uint64_t res = read_tsc(); + fence(); + return res; +} + +inline std::uint64_t tsc_mid() { + std::uint64_t res = read_tscp(); + fence(); + return res; +} + +inline std::uint64_t tsc_end() { return read_tscp(); } +``` + +## 函数式编程(functional programming) + +* 函数式编程是一种编程范式,使用的函数为纯函数,即如果函数的调用参数相同,则永远返回相同的结果,纯函数不会改变外部状态,因此对于只使用纯函数的函数式编程,天生就不存在 race condition 的问题。Haskell 是一种常见的函数式编程语言,以快速排序为例,Haskell 中的实现如下 + +```hs +quickSort :: Ord a => [a] -> [a] +quickSort [] = [] +quickSort (x : xs) = l ++ [x] ++ r + where + l = quickSort (filter (<= x) xs) + r = quickSort (filter (> x) xs) + +main :: IO () +main = print (quickSort "downdemo") -- "ddemnoow" +``` + +* 相同思路的 C++ 实现 + +```cpp +#include +#include +#include +#include + +template +std::list quick_sort(std::list v) { + if (v.empty()) { + return v; + } + std::list res; + res.splice(res.begin(), v, v.begin()); // 将 v 的首元素移到 res 中 + // 将 v 按条件划分为两部分,并返回第一个不满足条件元素的迭代器 + auto it = std::partition(v.begin(), v.end(), + [&](const T& x) { return x < res.front(); }); + std::list low; + low.splice(low.end(), v, v.begin(), it); // 转移左半部分到 low + auto l(quick_sort(std::move(low))); // 递归对左半部分快速排序 + auto r(quick_sort(std::move(v))); // 递归对右半部分快速排序 + res.splice(res.end(), r); // 右半部分移到结果后 + res.splice(res.begin(), l); // 左半部分移到结果前 + return res; +} + +int main() { + for (auto& x : quick_sort(std::list{1, 3, 2, 4, 5})) { + std::cout << x; // 12345 + } +} +``` + +* 使用 [std::future](https://en.cppreference.com/w/cpp/thread/future) 实现并行版本 + +```cpp +#include +#include +#include +#include +#include + +template +std::list quick_sort(std::list v) { + if (v.empty()) { + return v; + } + std::list res; + res.splice(res.begin(), v, v.begin()); + auto it = std::partition(v.begin(), v.end(), + [&](const T& x) { return x < res.front(); }); + std::list low; + low.splice(low.end(), v, v.begin(), it); + // 用另一个线程对左半部分排序 + std::future> l(std::async(&quick_sort, std::move(low))); + auto r(quick_sort(std::move(v))); + res.splice(res.end(), r); + res.splice(res.begin(), l.get()); + return res; +} + +int main() { + for (auto& x : quick_sort(std::list{1, 3, 2, 4, 5})) { + std::cout << x; // 12345 + } +} +``` + +## 链式调用 + +* 链式调用是函数式编程中经常使用的形式,常见于 [ReactiveX](https://reactivex.io/intro.html),比如 [RxJS](https://github.com/ReactiveX/rxjs),当上游产生数据时交给下游处理,将复杂的异步逻辑拆散成了多个小的操作,只需要关注每一步操作并逐步转换到目标结果即可。C++20 的 [ranges](https://en.cppreference.com/w/cpp/ranges) 使用的 [range-v3](https://github.com/ericniebler/range-v3) 就脱胎自 [RxCpp](https://github.com/ReactiveX/RxCpp) + +```ts +import { interval } from 'rxjs'; +import { withLatestFrom } from 'rxjs/operators'; + +const source1$ = interval(500); +const source2$ = interval(1000); +source1$.pipe(withLatestFrom(source2$, (x, y) => `${x}${y}`)); // 10 20 31 41 52 62--- +``` + +* [并发 TS](https://en.cppreference.com/w/cpp/experimental/concurrency) 提供了 [std::experimental::promise](https://en.cppreference.com/w/cpp/experimental/concurrency/promise) 和 [std::experimental::packaged_task](https://en.cppreference.com/w/cpp/experimental/concurrency/packaged_task),与标准库唯一不同的是,它们返回 [std::experimental::future](https://en.cppreference.com/w/cpp/experimental/future),[std::experimental::future::then()](https://en.cppreference.com/w/cpp/experimental/future/then) 可链式调用 + +```cpp +int f(std::experimental::future); + +std::experimental::future eft; +auto ft1 = eft(); // std::experimental::future 由本身的构造函数生成 +// 与 std::async 不同,不能传入 f 的参数 +// 因为参数已经在运行库中定义为了一个就绪的期值 +// 这里 f 的返回 int,因此参数就是 std::experimental::future +auto ft2 = ft1.then(f); +// then 后原期值就无效了 +assert(!ft1.valid()); +assert(ft2.valid()); +``` + +* [std::async](https://en.cppreference.com/w/cpp/thread/async) 只能返回 [std::future](https://en.cppreference.com/w/cpp/thread/future),如果想返回 [std::experimental::future](https://en.cppreference.com/w/cpp/experimental/future) 则需要手动实现一个新的 async + +```cpp +template +std::experimental::future()())> new_async(F&& func) { + std::experimental::promise()())> p; + auto ft = p.get_future(); + std::thread t([p = std::move(p), f = std::decay_t(func)]() mutable { + try { + p.set_value_at_thread_exit(f()); + } catch (...) { + p.set_exception_at_thread_exit(std::current_exception()); + } + }); + t.detach(); + return ft; +} +``` + +* 假如要实现一个登录逻辑,将用户名和密码发送给后台验证,取得用户信息后更新到显示界面,串行实现如下 + +```cpp +void process_login(const std::string& username, const std::string& password) { + try { + const user_id id = backend.authenticate_user(username, password); + const user_data info_to_display = backend.request_current_info(id); + update_display(info_to_display); + } catch (std::exception& e) { + display_error(e); + } +} +``` + +* 为了不阻塞 UI 线程,就需要异步实现 + +```cpp +std::future process_login(const std::string& username, + const std::string& password) { + return std::async(std::launch::async, [=]() { + try { + const user_id id = backend.authenticate_user(username, password); + const user_data info_to_display = backend.request_current_info(id); + update_display(info_to_display); + } catch (std::exception& e) { + display_error(e); + } + }); +} +``` + +* 但这个实现仍然会阻塞 UI 线程,为此就需要链式调用的机制,每个任务完成后连接到前一个任务上 + +```cpp +std::experimental::future process_login(const std::string& username, + const std::string& password) { + return new_async( + [=]() { return backend.authenticate_user(username, password); }) + .then([](std::experimental::future id) { + return backend.request_current_info(id.get()); + }) + .then([](std::experimental::future info_to_display) { + try { + update_display(info_to_display.get()); + } catch (std::exception& e) { + display_error(e); + } + }); +} +``` + +* 如果调用后台函数内部阻塞,可能是因为需要等待消息通过网络或者完成一个数据库操作,而这些还没有完成。即使把任务划分为多个独立部分,也仍会阻塞调用,得到阻塞的线程。这时后台调用真正需要的是,在数据准备好时返回就绪的期值,而不阻塞任何线程,所以这里用返回 `std::experimental::future` 的 `backend.async_authenticate_user` 替代返回 `user_id` 的 `backend.authenticate_user` + +```cpp +std::experimental::future process_login(const std::string& username, + const std::string& password) { + return backend.async_authenticate_user(username, password) + .then([](std::experimental::future id) { + return backend.async_request_current_info(id.get()); + }) + .then([](std::experimental::future info_to_display) { + try { + update_display(info_to_display.get()); + } catch (std::exception& e) { + display_error(e); + } + }); +} +``` + +* 这样在异步函数链上就不存在阻塞了。最后这里还可以用泛型 lambda 来简化代码 + +```cpp +std::experimental::future process_login(const std::string& username, + const std::string& password) { + return backend.async_authenticate_user(username, password) + .then( + [](auto id) { return backend.async_request_current_info(id.get()); }) + .then([](auto info_to_display) { + try { + update_display(info_to_display.get()); + } catch (std::exception& e) { + display_error(e); + } + }); +} +``` + +* 除了 [std::experimental::future](https://en.cppreference.com/w/cpp/experimental/future),支持链式调用的还有 [std::experimental::shared_future](https://en.cppreference.com/w/cpp/experimental/shared_future) + +```cpp +auto ft1 = new_async(some_function).share(); +auto ft2 = ft1.then( + [](std::experimental::shared_future data) { do_stuff(data); }); +auto ft3 = ft1.then([](std::experimental::shared_future data) { + return do_other_stuff(data); +}); +``` + +* 使用 [std::async](https://en.cppreference.com/w/cpp/thread/async) 从多个期值中获取结果存在反复唤醒导致的开销 + +```cpp +std::future process_data(std::vector& vec) { + const size_t chunk_size = whatever; + std::vector> res; + for (auto begin = vec.begin(), end = vec.end(); beg ! = end;) { + const size_t remaining_size = end - begin; + const size_t this_chunk_size = std::min(remaining_size, chunk_size); + res.push_back(std::async(process_chunk, begin, begin + this_chunk_size)); + begin += this_chunk_size; + } + return std::async([all_results = std::move(res)]() { + std::vector v; + v.reserve(all_results.size()); + for (auto& f : all_results) { + v.push_back(f.get()); // 这里会导致反复唤醒,增加了很多开销 + } + return gather_results(v); + }); +} +``` + +* 使用 [std::experimental::when_all](https://en.cppreference.com/w/cpp/experimental/when_all) 可以避免反复唤醒导致的开销,为其传入一组需要等待的期值,将返回一个新的期值。当传入的所有期值都就绪时,则返回的期值就绪 + +```cpp +std::experimental::future process_data(std::vector& vec) { + const size_t chunk_size = whatever; + std::vector> res; + for (auto begin = vec.begin(), end = vec.end(); beg ! = end;) { + const size_t remaining_size = end - begin; + const size_t this_chunk_size = std::min(remaining_size, chunk_size); + res.push_back(new_async(process_chunk, begin, begin + this_chunk_size)); + begin += this_chunk_size; + } + return std::experimental::when_all(res.begin(), res.end()) + .then([](std::future>> + ready_results) { + std::vector> all_results = + ready_results.get(); + std::vector v; + v.reserve(all_results.size()); + for (auto& f : all_results) { + v.push_back(f.get()); + } + return gather_results(v); + }); +} +``` + +* 在传入的期值中有一个就绪时,则 [std::experimental::when_any](https://en.cppreference.com/w/cpp/experimental/when_any) 返回的期值就绪 + +```cpp +std::experimental::future find_and_process_value( + std::vector& data) { + const unsigned concurrency = std::thread::hardware_concurrency(); + const unsigned num_tasks = (concurrency > 0) ? concurrency : 2; + std::vector> res; + const auto chunk_size = (data.size() + num_tasks - 1) / num_tasks; + auto chunk_begin = data.begin(); + std::shared_ptr> done_flag = + std::make_shared>(false); + for (unsigned i = 0; i < num_tasks; ++i) { // 生成异步任务到 res 中 + auto chunk_end = + (i < (num_tasks - 1)) ? chunk_begin + chunk_size : data.end(); + res.push_back(new_async([=] { + for (auto entry = chunk_begin; !*done_flag && (entry != chunk_end); + ++entry) { + if (matches_find_criteria(*entry)) { + *done_flag = true; + return &*entry; + } + } + return (MyData**)nullptr; + })); + chunk_begin = chunk_end; + } + std::shared_ptr> final_result = + std::make_shared>(); + + struct DoneCheck { + std::shared_ptr> final_result; + + DoneCheck( + std::shared_ptr> final_result_) + : final_result(std::move(final_result_)) {} + + void operator()( + std::experimental::future>>> + res_param) { + auto res = res_param.get(); + MyData* const ready_result = + res.futures[res.index].get(); // 从就绪的期值中获取值 + // 找到符合条件的值则处理结果并 set_value + if (ready_result) { + final_result->set_value(process_found_value(*ready_result)); + } else { + res.futures.erase(res.futures.begin() + res.index); // 否则丢弃值 + if (!res.futures.empty()) { // 如果还有需要检查的值则再次调用 when_any + std::experimental::when_any(res.futures.begin(), res.futures.end()) + .then(std::move(*this)); + } else { // 如果没有其他期值则在 promise 中设置一个异常 + final_result->set_exception( + std::make_exception_ptr(std::runtime_error("Not found"))); + } + } + } + }; + std::experimental::when_any(res.begin(), res.end()) + .then(DoneCheck(final_result)); + return final_result->get_future(); +} +``` + +* when_all 和 when_any 除了可以接收一对迭代器,也可以直接接受期值 + +```cpp +std::experimental::future ft1 = new_async(f1); +std::experimental::future ft2 = new_async(f2); +std::experimental::future ft3 = new_async(f3); +std::experimental::future, + std::experimental::future, + std::experimental::future>> + res = std::experimental::when_all(std::move(ft1), std::move(ft2), + std::move(ft3)); +``` + +## CSP(Communicating Sequential Processes) + +* CSP 是一种描述并发系统交互的编程模型,线程理论上是分开的,没有共享数据,每个线程可以完全独立地思考,消息通过 communication channel 在不同线程间传递,线程行为取决于收到的消息,因此每个线程实际上是一个状态机,收到一条消息时就以某种方式更新状态,并且还可能发送消息给其他线程。Erlang 采用了这种编程模型,并用于 [MPI](https://en.wikipedia.org/wiki/Message_Passing_Interface) 做 C 和 C++ 的高性能计算。真正的 CSP 没有共享数据,所有通信通过消息队列传递,但由于 C++ 线程共享地址空间,无法强制实现这个要求,所以需要应用或者库的作者来确保线程间不会共享数据 +* 考虑实现一个 ATM 应用,它需要处理取钱时和银行的交互,并控制物理机器对银行卡的反应。一个处理方法是分三个线程,分别处理物理机器、ATM 逻辑、与银行的交互,线程间通过消息通讯而非共享数据,比如插卡时机器线程发送消息给逻辑线程,逻辑线程返回一条消息通知机器线程可以给多少钱 +* 一个简单的 ATM 逻辑的状态机建模如下 + +![](images/3-1.png) + +* 这个 ATM 逻辑的状态机与系统的其他部分各自运行在独立的线程上,不需要考虑同步和并发的问题,只要考虑在某个点接受和发送的消息,这种设计方式称为 actor model,系统中有多个独立的 actor,actor 之间可以互相发送消息但不会共享状态,这种方式可以极大简化并发系统的设计,完整代码见 [atm.cpp](https://github.com/downdemo/Cpp-Concurrency-in-Action-2ed/blob/master/src/atm.cpp) diff --git a/docs/04_the_cpp_memory_model_and_operations_on_atomic_type.md b/docs/04_the_cpp_memory_model_and_operations_on_atomic_type.md new file mode 100644 index 0000000..5d723b4 --- /dev/null +++ b/docs/04_the_cpp_memory_model_and_operations_on_atomic_type.md @@ -0,0 +1,925 @@ +## 内存模型基础 + +* 为了避免 race condition,线程就要规定执行顺序。一种方式是使用 mutex,后一线程必须等待前一线程解锁。第二种方式是使用原子操作来避免竞争访问同一内存位置 +* 原子操作是不可分割的操作,要么做了要么没做,不存在做一半的状态。如果读取对象值的加载操作是原子的,那么对象上的所有修改操作也是原子的,读取的要么是初始值,要么是某个修改完成后的存储值。因此,原子操作不存在修改过程中值被其他线程看到的情况,也就避免了竞争风险 +* 每个对象从初始化开始都有一个修改顺序,这个顺序由来自所有线程对该对象的写操作组成。通常这个顺序在运行时会变动,但在任何给定的程序执行中,系统中所有线程都必须遵循此顺序 +* 如果对象不是原子类型,就要通过同步来保证线程遵循每个变量的修改顺序。如果一个变量对于不同线程表现出不同的值序列,就会导致数据竞争和未定义行为。使用原子操作就可以把同步的责任抛给编译器 + +## 原子操作和原子类型 + +### 标准原子类型 + +* 标准原子类型定义在 [\](https://en.cppreference.com/w/cpp/header/atomic) 中。也可以用 mutex 模拟原子操作,实际上标准原子类型可能就是这样实现的,它们都有一个 [is_lock_free](https://en.cppreference.com/w/cpp/atomic/atomic/is_lock_free) 函数,返回 true 说明该原子类型操作是无锁的,用的是原子指令,返回 false 则是用锁 + +```cpp +struct A { + int a[100]; +}; + +struct B { + int x, y; +}; + +assert(!std::atomic{}.is_lock_free()); +assert(std::atomic{}.is_lock_free()); +``` + +* 原子操作的主要用处是替代 mutex 实现同步。如果原子操作内部是用 mutex 实现的,就不会有期望的性能提升,还不如直接用 mutex 来同步。C++17 中每个原子类型都有一个 [is_always_lock_free](https://en.cppreference.com/w/cpp/atomic/atomic/is_always_lock_free) 成员变量,为 true 时表示该原子类型在此平台上 lock-free + +```cpp +assert(std::atomic{}.is_always_lock_free); +``` + +* C++17 之前可以用标准库为各个原子类型定义的 [ATOMIC_xxx_LOCK_FREE](https://en.cppreference.com/w/c/atomic/ATOMIC_LOCK_FREE_consts) 宏来判断该类型是否无锁,值为 0 表示原子类型是有锁的,为 2 表示无锁,为 1 表示运行时才能确定 + +```cpp +// LOCK-FREE PROPERTY +#define ATOMIC_BOOL_LOCK_FREE 2 +#define ATOMIC_CHAR_LOCK_FREE 2 +#ifdef __cpp_lib_char8_t +#define ATOMIC_CHAR8_T_LOCK_FREE 2 +#endif // __cpp_lib_char8_t +#define ATOMIC_CHAR16_T_LOCK_FREE 2 +#define ATOMIC_CHAR32_T_LOCK_FREE 2 +#define ATOMIC_WCHAR_T_LOCK_FREE 2 +#define ATOMIC_SHORT_LOCK_FREE 2 +#define ATOMIC_INT_LOCK_FREE 2 +#define ATOMIC_LONG_LOCK_FREE 2 +#define ATOMIC_LLONG_LOCK_FREE 2 +#define ATOMIC_POINTER_LOCK_FREE 2 +``` + +* 只有 [std::atomic_flag](https://en.cppreference.com/w/cpp/atomic/atomic_flag) 未提供 is_lock_free,该类型是一个简单的布尔标志,所有操作都保证 lock-free。基于 [std::atomic_flag](https://en.cppreference.com/w/cpp/atomic/atomic_flag) 就能实现一个简单的锁,并实现其他基础原子类型。其余原子类型可以通过特化 [std::atomic](https://en.cppreference.com/w/cpp/atomic/atomic) 来实现,且可以有更完整的功能,但不保证 lock-free +* 标准库中为 [std::atomic](https://en.cppreference.com/w/cpp/atomic/atomic) 对内置类型的特化定义了类型别名 + +```cpp +namespace std { +using atomic_bool = atomic; +using atomic_char = std::atomic; +} // namespace std +``` + +* 通常类型 `std::atomic` 的别名就是 `atomic_T`,只有以下几种例外:signed 缩写为 s,unsigned 缩写为 u,long long 缩写为 llong + +```cpp +namespace std { +using atomic_schar = std::atomic; +using atomic_uchar = std::atomic; +using atomic_uint = std::atomic; +using atomic_ushort = std::atomic; +using atomic_ulong = std::atomic; +using atomic_llong = std::atomic; +using atomic_ullong = std::atomic; +} // namespace std +``` + +* 原子类型不允许由另一个原子类型拷贝赋值,因为拷贝赋值调用了两个对象,破坏了操作的原子性。但可以用对应的内置类型赋值 + +```cpp +T operator=(T desired) noexcept; +T operator=(T desired) volatile noexcept; +atomic& operator=(const atomic&) = delete; +atomic& operator=(const atomic&) volatile = delete; +``` + +* 此外 [std::atomic](https://en.cppreference.com/w/cpp/atomic/atomic) 为支持赋值提供了成员函数 + +```cpp +std::atomic::store // 替换当前值 +std::atomic::load // 返回当前值 +std::atomic::exchange // 替换值,并返回被替换前的值 + +// 与期望值比较,不等则将期望值设为原子值并返回 false +// 相等则将原子值设为目标值并返回 true +// 在缺少 CAS(compare-and-exchange)指令的机器上,weak 版本在相等时可能替换失败并返回 false +// 因此 weak 版本通常要求循环,而 strong 版本返回 false 就能确保不相等 +std::atomic::compare_exchange_weak +std::atomic::compare_exchange_strong + +std::atomic::fetch_add // 原子加法,返回相加前的值 +std::atomic::fetch_sub // 原子减法,返回相减前的值 +std::atomic::fetch_and +std::atomic::fetch_or +std::atomic::fetch_xor +std::atomic::operator++ // 前自增等价于 fetch_add(1) + 1 +std::atomic::operator++(int) // 后自增等价于 fetch_add(1) +std::atomic::operator-- // 前自减等价于 fetch_sub(1) - 1 +std::atomic::operator--(int) // 后自减等价于 fetch_sub(1) +std::atomic::operator+= // fetch_add(x) + x +std::atomic::operator-= // fetch_sub(x) - x +std::atomic::operator&= // fetch_and(x) & x +std::atomic::operator|= // fetch_or(x) | x +std::atomic::operator^= // fetch_xor(x) ^ x +``` + +* 这些成员函数有一个用来指定内存序的参数 [std::memory_order](https://en.cppreference.com/w/cpp/atomic/memory_order),后续会解释内存序的含义 + +```cpp +typedef enum memory_order { + memory_order_relaxed, + memory_order_consume, + memory_order_acquire, + memory_order_release, + memory_order_acq_rel, + memory_order_seq_cst +} memory_order; + +void store(T desired, std::memory_order order = std::memory_order_seq_cst); +// store 的内存序只能是 +// memory_order_relaxed、memory_order_release、memory_order_seq_cst +T load(std::memory_order order = std::memory_order_seq_cst); +// load 的内存序只能是 +// memory_order_relaxed、memory_order_consume、memory_order_acquire、memory_order_seq_cst +``` + +### [std::atomic_flag](https://en.cppreference.com/w/cpp/atomic/atomic_flag) + +* [std::atomic_flag](https://en.cppreference.com/w/cpp/atomic/atomic_flag) 是一个原子的布尔类型,也是唯一保证 lock-free 的原子类型,只能用 [ATOMIC_FLAG_INIT](https://en.cppreference.com/w/cpp/atomic/ATOMIC_FLAG_INIT) 初始化为 false + +```cpp +std::atomic_flag x = ATOMIC_FLAG_INIT; + +x.clear(std::memory_order_release); // 将状态设为 false +// 不能为读操作语义:memory_order_consume、memory_order_acquire、memory_order_acq_rel + +bool y = x.test_and_set(); // 将状态设为 true 且返回之前的值 +``` + +* 用 [std::atomic_flag](https://en.cppreference.com/w/cpp/atomic/atomic_flag) 实现自旋锁 + +```cpp +#include +#include +#include +#include + +class Spinlock { + public: + void lock() { + while (flag_.test_and_set(std::memory_order_acquire)) { + } + } + + void unlock() { flag_.clear(std::memory_order_release); } + + private: + std::atomic_flag flag_ = ATOMIC_FLAG_INIT; +}; + +Spinlock m; + +void f(int n) { + for (int i = 0; i < 100; ++i) { + m.lock(); + std::cout << "Output from thread " << n << '\n'; + m.unlock(); + } +} + +int main() { + std::vector v; + for (int i = 0; i < 10; ++i) { + v.emplace_back(f, i); + } + for (auto& x : v) { + x.join(); + } +} +``` + +### 其他原子类型 + +* [std::atomic_flag](https://en.cppreference.com/w/cpp/atomic/atomic_flag) 功能过于局限,甚至无法像布尔类型一样使用,相比之下,`std::atomic` 更易用,它不保证 lock-free,可以用 [is_lock_free](https://en.cppreference.com/w/cpp/atomic/atomic/is_lock_free) 检验在当前平台上是否 lock-free + +```cpp +std::atomic x(true); +x = false; +bool y = x.load(std::memory_order_acquire); // 读取 x 值返回给 y +x.store(true); // x 写为 true +y = x.exchange(false, + std::memory_order_acq_rel); // x 用 false 替换,并返回旧值给 y +bool expected = false; // 期望值 +// 不等则将期望值设为 x 并返回 false,相等则将 x 设为目标值 true 并返回 true +// weak 版本在相等时也可能替换失败而返回 false,因此一般用于循环 +while (!x.compare_exchange_weak(expected, true) && !expected) { +} +// 对于只有两种值的 std::atomic 来说显得有些繁琐 +// 但对其他原子类型来说,这个影响就大了 +``` + +* 指针原子类型 `std::atomic` 也支持 [is_lock_free](https://en.cppreference.com/w/cpp/atomic/atomic/is_lock_free)、[load](https://en.cppreference.com/w/cpp/atomic/atomic/load)、[store](https://en.cppreference.com/w/cpp/atomic/atomic/store)、[exchange](https://en.cppreference.com/w/cpp/atomic/atomic/exchange)、[compare_exchange_weak、compare_exchange_strong](https://en.cppreference.com/w/cpp/atomic/atomic/compare_exchange),与 `std::atomic` 语义相同,只不过读取和返回的类型是 `T*` 而非 bool。此外指针原子类型还支持运算操作:[fetch_add](https://en.cppreference.com/w/cpp/atomic/atomic/fetch_add)、[fetch_sub](https://en.cppreference.com/w/cpp/atomic/atomic/fetch_sub)、[++、--](https://en.cppreference.com/w/cpp/atomic/atomic/operator_arith)、[+=、-=](https://en.cppreference.com/w/cpp/atomic/atomic/operator_arith2) + +```cpp +class A {}; +A a[5]; +std::atomic p(a); // p 为 &a[0] +A* x = p.fetch_add(2); // p 为 &a[2],并返回原始值 a[0] +assert(x == a); +assert(p.load() == &a[2]); +x = (p -= 1); // p 为 &a[1],并返回给 x,相当于 x = p.fetch_sub(1) - 1 +assert(x == &a[1]); +assert(p.load() == &a[1]); +``` + +* 整型原子类型(如 `std::atomic`)在上述操作之外还支持 [fetch_or](https://en.cppreference.com/w/cpp/atomic/atomic/fetch_or)、[fetch_and](https://en.cppreference.com/w/cpp/atomic/atomic/fetch_and)、[fetch_xor](https://en.cppreference.com/w/cpp/atomic/atomic/fetch_xor)、[\|=、&=、^=](https://en.cppreference.com/w/cpp/atomic/atomic/operator_arith2) + +```cpp +std::atomic i(5); +int j = i.fetch_and(3); // 101 & 011 = 001 +assert(i == 1); +assert(j == 5); +``` + +* 用整型原子类型实现 Spinlock + +```cpp +#include + +class Spinlock { + public: + void lock() { + int expected = 0; + while (!flag_.compare_exchange_weak(expected, 1, std::memory_order_release, + std::memory_order_relaxed)) { + expected = 0; + } + } + + void unlock() { flag_.store(0, std::memory_order_release); } + + private: + std::atomic flag_ = 0; +}; +``` + +* 用整型原子类型实现 SharedSpinlock + +```cpp +#include + +class SharedSpinlock { + public: + void lock() { + int expected = 0; + while (!flag_.compare_exchange_weak(expected, 1, std::memory_order_release, + std::memory_order_relaxed)) { + expected = 0; + } + } + + void unlock() { flag_.store(0, std::memory_order_release); } + + void lock_shared() { + int expected = 0; + while (!flag_.compare_exchange_weak(expected, 2, std::memory_order_release, + std::memory_order_acquire) && + expected == 2) { + expected = 0; + } + count_.fetch_add(1, std::memory_order_release); + } + + void unlock_shared() { + if (count_.fetch_sub(1, std::memory_order_release) == 1) { + flag_.store(0, std::memory_order_release); + } + } + + private: + std::atomic flag_ = 0; + std::atomic count_ = 0; +}; +``` + +* 用整型原子类型实现 Barrier + +```cpp +#include +#include + +class Barrier { + public: + explicit Barrier(unsigned n) : count_(n), spaces_(n), generation_(0) {} + + void wait() { + unsigned gen = generation_.load(); + if (--spaces_ == 0) { + spaces_ = count_.load(); + ++generation_; + return; + } + while (generation_.load() == gen) { + std::this_thread::yield(); + } + } + + void arrive() { + --count_; + if (--spaces_ == 0) { + spaces_ = count_.load(); + ++generation_; + } + } + + private: + std::atomic count_; // 需要同步的线程数 + std::atomic spaces_; // 剩余未到达 Barrier 的线程数 + std::atomic generation_; // 所有线程到达 Barrier 的总次数 +}; +``` + +* 如果原子类型是自定义类型,该自定义类型必须[可平凡复制(trivially copyable)](https://en.cppreference.com/w/cpp/named_req/TriviallyCopyable),也就意味着该类型不能有虚函数或虚基类。这可以用 [is_trivially_copyable](https://en.cppreference.com/w/cpp/types/is_trivially_copyable) 检验 + +```cpp +class A { + public: + virtual void f() {} +}; + +assert(!std::is_trivially_copyable_v); +std::atomic a; // 错误:A 不满足 trivially copyable +std::atomic> v; // 错误 +std::atomic s; // 错误 +``` + +* 自定义类型的原子类型不允许运算操作,只允许 [is_lock_free](https://en.cppreference.com/w/cpp/atomic/atomic/is_lock_free)、[load](https://en.cppreference.com/w/cpp/atomic/atomic/load)、[store](https://en.cppreference.com/w/cpp/atomic/atomic/store)、[exchange](https://en.cppreference.com/w/cpp/atomic/atomic/exchange)、[compare_exchange_weak、compare_exchange_strong](https://en.cppreference.com/w/cpp/atomic/atomic/compare_exchange),以及赋值操作和向自定义类型转换的操作 +* 除了每个类型各自的成员函数,[原子操作库](https://en.cppreference.com/w/cpp/atomic)还提供了通用的自由函数,只不过函数名多了一个 `atomic_` 前缀,参数变为指针类型 + +```cpp +std::atomic i(42); +int j = std::atomic_load(&i); // 等价于 i.load() +``` + +* 除 [std::atomic_is_lock_free](https://en.cppreference.com/w/cpp/atomic/atomic_is_lock_free) 外,每个自由函数有一个 `_explicit` 后缀版本,`_explicit` 自由函数额外接受一个内存序参数 + +```cpp +std::atomic i(42); +std::atomic_load_explicit(&i, std::memory_order_acquire); // i.load(std::memory_order_acquire) +``` + +* 自由函数的设计主要考虑的是 C 语言没有引用而只能使用指针,[compare_exchange_weak、compare_exchange_strong](https://en.cppreference.com/w/cpp/atomic/atomic/compare_exchange) 的第一个参数是引用,因此 [std::atomic_compare_exchange_weak、std::atomic_compare_exchange_strong](https://en.cppreference.com/w/cpp/atomic/atomic_compare_exchange) 的参数用的是指针 + +```cpp +bool compare_exchange_weak(T& expected, T desired, std::memory_order success, + std::memory_order failure); + +template +bool atomic_compare_exchange_weak(std::atomic* obj, + typename std::atomic::value_type* expected, + typename std::atomic::value_type desired); + +template +bool atomic_compare_exchange_weak_explicit( + std::atomic* obj, typename std::atomic::value_type* expected, + typename std::atomic::value_type desired, std::memory_order succ, + std::memory_order fail); +``` + +* [std::atomic_flag](https://en.cppreference.com/w/cpp/atomic/atomic_flag) 对应的自由函数的前缀不是 `atomic_` 而是 `atomic_flag_`,但接受内存序参数的版本一样是 `_explicit` 后缀 + +```cpp +std::atomic_flag x = ATOMIC_FLAG_INIT; +bool y = std::atomic_flag_test_and_set_explicit(&x, std::memory_order_acquire); +std::atomic_flag_clear_explicit(&x, std::memory_order_release); +``` + +* C++20 允许 [std::atomic](https://en.cppreference.com/w/cpp/atomic/atomic) 的模板参数为 [std::shared_ptr](https://en.cppreference.com/w/cpp/memory/shared_ptr) + +```cpp +std::atomic> x; +``` + +## 同步操作和强制排序(enforced ordering) + +* 两个线程分别读写数据,为了避免竞争,设置一个标记 + +```cpp +std::vector data; +std::atomic data_ready(false); + +void read_thread() { + while (!data_ready.load()) { // 1 happens-before 2 + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + std::cout << data[0]; // 2 +} + +void write_thread() { + data.emplace_back(42); // 3 happens-before 4 + data_ready = true; // 4 inter-thread happens-before 1 +} +``` + +* `std::atomic` 上的操作要求强制排序,该顺序由内存模型关系 happens-before 和 synchronizes-with 提供 +* happens-before 保证了 1 在 2 之前发生,3 在 4 之前发生,而 1 要求 4,所以 4 在 1 之前发生,最终顺序确定为 3412 + +![](images/4-1.png) + +* 如果没有强制排序,CPU 可能会调整指令顺序,如果顺序是 4123,读操作就会因为越界而出错 + +### synchronizes-with + +* synchronizes-with 关系只存在于原子类型操作上,如果一个数据结构包含原子类型,这个数据结构上的操作(比如加锁)也可能提供 synchronizes-with 关系 +* 变量 x 上,标记了内存序的原子写操作 W,和标记了内存序的原子读操作,如果两者存在 synchronizes-with 关系,表示读操作读取的是:W 写入的值,或 W 之后同一线程上原子写操作写入 x 的值,或任意线程上对 x 的一系列原子读改写操作(比如 fetch_add、compare_exchange_weak)的值 +* 简单来说,如果线程 A 写入一个值,线程 B 读取该值,则 A synchronizes-with B + +### [happens-before](https://en.cppreference.com/w/cpp/atomic/memory_order#Happens-before) + +* [happens-before](https://en.cppreference.com/w/cpp/atomic/memory_order#Happens-before) 和 [strongly-happens-before](https://en.cppreference.com/w/cpp/atomic/memory_order#Strongly_happens-before) 关系是程序操作顺序的基本构建块,它指定某个操作可以看到其他操作的结果。对单线程来说很简单,如果一个操作在另一个之前,就可以说前一个操作 [happens-before](https://en.cppreference.com/w/cpp/atomic/memory_order#Happens-before)(且 [strongly-happens-before](https://en.cppreference.com/w/cpp/atomic/memory_order#Strongly_happens-before)) 后一个操作 +* 如果操作发生在同一语句中,一般不存在 [happens-before](https://en.cppreference.com/w/cpp/atomic/memory_order#Happens-before) 关系,因为它们是无序的 + +```cpp +#include + +void f(int x, int y) { std::cout << x << y; } + +int g() { + static int i = 0; + return ++i; +} + +int main() { + f(g(), g()); // 无序调用 g,可能是 21 也可能是 12 + // 一般 C++ 默认使用 __cdecl 调用模式,参数从右往左入栈,就是21 +} +``` + +* 前一条语句中的所有操作都 [happens-before](https://en.cppreference.com/w/cpp/atomic/memory_order#Happens-before) 下一条语句中的所有操作 + +### [inter-thread happens-before](https://en.cppreference.com/w/cpp/atomic/memory_order#Inter-thread_happens-before) + +* 如果一个线程中的操作 A [happens-before](https://en.cppreference.com/w/cpp/atomic/memory_order#Happens-before) 另一个线程中的操作 B,则 A [inter-thread happens-before](https://en.cppreference.com/w/cpp/atomic/memory_order#Inter-thread_happens-before) B +* A [inter-thread happens-before](https://en.cppreference.com/w/cpp/atomic/memory_order#Inter-thread_happens-before) B 包括以下情况 + * A synchronizes-with B + * A [dependency-ordered-before](https://en.cppreference.com/w/cpp/atomic/memory_order#Dependency-ordered_before) B + * A [inter-thread happens-before](https://en.cppreference.com/w/cpp/atomic/memory_order#Inter-thread_happens-before) X,X [inter-thread happens-before](https://en.cppreference.com/w/cpp/atomic/memory_order#Inter-thread_happens-before) B + * A [sequenced-before](https://en.cppreference.com/w/cpp/language/eval_order) X,X [inter-thread happens-before](https://en.cppreference.com/w/cpp/atomic/memory_order#Inter-thread_happens-before) B + * A synchronizes-with X,X [sequenced-before](https://en.cppreference.com/w/cpp/language/eval_order) B + +### [strongly-happens-before](https://en.cppreference.com/w/cpp/atomic/memory_order#Strongly_happens-before) + +* [strongly-happens-before](https://en.cppreference.com/w/cpp/atomic/memory_order#Strongly_happens-before) 关系大多数情况下和 [happens-before](https://en.cppreference.com/w/cpp/atomic/memory_order#Happens-before) 一样,A [strongly-happens-before](https://en.cppreference.com/w/cpp/atomic/memory_order#Strongly_happens-before) B 包括以下情况 + * A synchronizes-with B + * A [sequenced-before](https://en.cppreference.com/w/cpp/language/eval_order) X,X [inter-thread happens-before](https://en.cppreference.com/w/cpp/atomic/memory_order#Inter-thread_happens-before) B + * A [strongly-happens-before](https://en.cppreference.com/w/cpp/atomic/memory_order#Strongly_happens-before) X,X [strongly-happens-before](https://en.cppreference.com/w/cpp/atomic/memory_order#Strongly_happens-before) B +* 略微不同的是,[inter-thread happens-before](https://en.cppreference.com/w/cpp/atomic/memory_order#Inter-thread_happens-before) 关系可以用 memory_order_consume 标记,而 [strongly-happens-before](https://en.cppreference.com/w/cpp/atomic/memory_order#Strongly_happens-before) 不行。但大多数代码不应该使用 memory_order_consume,所以这点实际上影响不大 + +### [std::memory_order](https://en.cppreference.com/w/cpp/atomic/memory_order) + +```cpp +typedef enum memory_order { + memory_order_relaxed, // 无同步或顺序限制,只保证当前操作原子性 + memory_order_consume, // 标记读操作,依赖于该值的读写不能重排到此操作前 + memory_order_acquire, // 标记读操作,之后的读写不能重排到此操作前 + memory_order_release, // 标记写操作,之前的读写不能重排到此操作后 + memory_order_acq_rel, // 仅标记读改写操作,读操作相当于 acquire,写操作相当于 release + memory_order_seq_cst // sequential consistency:顺序一致性,不允许重排,所有原子操作的默认选项 +} memory_order; +``` + +### [Relaxed ordering](https://en.cppreference.com/w/cpp/atomic/memory_order#Relaxed_ordering) + +* 标记为 memory_order_relaxed 的原子操作不是同步操作,不强制要求并发内存的访问顺序,只保证原子性和修改顺序一致性 + +```cpp +#include +#include + +std::atomic x = 0; +std::atomic y = 0; + +void f() { + int i = y.load(std::memory_order_relaxed); // 1 + x.store(i, std::memory_order_relaxed); // 2 +} + +void g() { + int j = x.load(std::memory_order_relaxed); // 3 + y.store(42, std::memory_order_relaxed); // 4 +} + +int main() { + std::thread t1(f); + std::thread t2(g); + t1.join(); + t2.join(); + // 可能执行顺序为 4123,结果 i == 42, j == 42 +} +``` + +* [Relaxed ordering](https://en.cppreference.com/w/cpp/atomic/memory_order#Relaxed_ordering) 不允许循环依赖 + +```cpp +#include +#include + +std::atomic x = 0; +std::atomic y = 0; + +void f() { + i = y.load(std::memory_order_relaxed); // 1 + if (i == 42) { + x.store(i, std::memory_order_relaxed); // 2 + } +} + +void g() { + j = x.load(std::memory_order_relaxed); // 3 + if (j == 42) { + y.store(42, std::memory_order_relaxed); // 4 + } +} + +int main() { + std::thread t1(f); + std::thread t2(g); + t1.join(); + t2.join(); + // 结果不允许为i == 42, j == 42 + // 因为要产生这个结果,1 依赖 4,4 依赖 3,3 依赖 2,2 依赖 1 +} +``` + +* 典型使用场景是自增计数器,比如 [std::shared_ptr](https://en.cppreference.com/w/cpp/memory/shared_ptr) 的引用计数器,它只要求原子性,不要求顺序和同步 + +```cpp +#include +#include +#include +#include + +std::atomic x = 0; + +void f() { + for (int i = 0; i < 1000; ++i) { + x.fetch_add(1, std::memory_order_relaxed); + } +} + +int main() { + std::vector v; + for (int i = 0; i < 10; ++i) { + v.emplace_back(f); + } + for (auto& x : v) { + x.join(); + } + std::cout << x; // 10000 +} +``` + +### [Release-Consume ordering](https://en.cppreference.com/w/cpp/atomic/memory_order#Release-Consume_ordering) + +* 对于标记为 memory_order_consume 原子变量 x 的读操作 R,当前线程中依赖于 x 的读写不允许重排到 R 之前,其他线程中对依赖于 x 的变量写操作对当前线程可见 +* 如果线程 A 对一个原子变量 x 的写操作为 memory_order_release,线程 B 对同一原子变量的读操作为 memory_order_consume,带来的副作用是,线程 A 中所有 [dependency-ordered-before](https://en.cppreference.com/w/cpp/atomic/memory_order#Dependency-ordered_before) 该写操作的其他写操作(non-atomic 和 relaxed atomic),在线程 B 的其他依赖于该变量的读操作中可见 +* 典型使用场景是访问很少进行写操作的数据结构(比如路由表),以及以指针为中介的 publisher-subscriber 场景,即生产者发布一个指针给消费者访问信息,但生产者写入内存的其他内容不需要对消费者可见,这个场景的一个例子是 RCU(Read-Copy Update)。该顺序的规范正在修订中,并且暂时不鼓励使用 memory_order_consume + +```cpp +#include +#include +#include + +std::atomic x; +int i; + +void producer() { + int* p = new int(42); + i = 42; + x.store(p, std::memory_order_release); +} + +void consumer() { + int* q; + while (!(q = x.load(std::memory_order_consume))) { + } + assert(*q == 42); // 一定不出错:*q 带有 x 的依赖 + assert(i == 42); // 可能出错也可能不出错:i 不依赖于 x +} + +int main() { + std::thread t1(producer); + std::thread t2(consumer); + t1.join(); + t2.join(); +} +``` + +### [Release-Acquire ordering](https://en.cppreference.com/w/cpp/atomic/memory_order#Release-Acquire_ordering) + +* 对于标记为 memory_order_acquire 的读操作 R,当前线程的其他读写操作不允许重排到 R 之前,其他线程中在同一原子变量上所有的写操作在当前线程可见 +* 如果线程 A 对一个原子变量的写操作 W 为 memory_order_release,线程 B 对同一原子变量的读操作为 memory_order_acquire,带来的副作用是,线程 A 中所有 [happens-before](https://en.cppreference.com/w/cpp/atomic/memory_order#Happens-before) W 的写操作(non-atomic 和 relaxed atomic)都在线程 B 中可见 +* 典型使用场景是互斥锁,线程 A 的释放后被线程 B 获取,则 A 中释放锁之前发生在 critical section 的所有内容都在 B 中可见 + +```cpp +#include +#include +#include + +std::atomic x; +int i; + +void producer() { + int* p = new int(42); + i = 42; + x.store(p, std::memory_order_release); +} + +void consumer() { + int* q; + while (!(q = x.load(std::memory_order_acquire))) { + } + assert(*q == 42); // 一定不出错 + assert(i == 42); // 一定不出错 +} + +int main() { + std::thread t1(producer); + std::thread t2(consumer); + t1.join(); + t2.join(); +} +``` + +* 对于标记为 memory_order_release 的写操作 W,当前线程中的其他读写操作不允许重排到 W 之后,若其他线程 acquire 该原子变量,则当前线程所有 [happens-before](https://en.cppreference.com/w/cpp/atomic/memory_order#Happens-before) 的写操作在其他线程中可见,若其他线程 consume 该原子变量,则当前线程所有 [dependency-ordered-before](https://en.cppreference.com/w/cpp/atomic/memory_order#Dependency-ordered_before) W 的其他写操作在其他线程中可见 +* 对于标记为 memory_order_acq_rel 的读改写(read-modify-write)操作,相当于写操作是 memory_order_release,读操作是 memory_order_acquire,当前线程的读写不允许重排到这个写操作之前或之后,其他线程中 release 该原子变量的写操作在修改前可见,并且此修改对其他 acquire 该原子变量的线程可见 +* [Release-Acquire ordering](https://en.cppreference.com/w/cpp/atomic/memory_order#Release-Acquire_ordering) 并不表示 total ordering + +```cpp +#include +#include + +std::atomic x = false; +std::atomic y = false; +std::atomic z = 0; + +void write_x() { + x.store(true, + std::memory_order_release); // 1 happens-before 3(由于 3 的循环) +} + +void write_y() { + y.store(true, + std::memory_order_release); // 2 happens-before 5(由于 5 的循环) +} + +void read_x_then_y() { + while (!x.load(std::memory_order_acquire)) { // 3 happens-before 4 + } + if (y.load(std::memory_order_acquire)) { // 4 + ++z; + } +} + +void read_y_then_x() { + while (!y.load(std::memory_order_acquire)) { // 5 happens-before 6 + } + if (x.load(std::memory_order_acquire)) { // 6 + ++z; + } +} + +int main() { + std::thread t1(write_x); + std::thread t2(write_y); + std::thread t3(read_x_then_y); + std::thread t4(read_y_then_x); + t1.join(); + t2.join(); + t3.join(); + t4.join(); + // z 可能为 0,134 y 为 false,256 x 为 false,但 12 之间没有关系 +} +``` + +![](images/4-2.png) + +* 为了使两个写操作有序,将其放到一个线程里 + +```cpp +#include +#include +#include + +std::atomic x = false; +std::atomic y = false; +std::atomic z = 0; + +void write_x_then_y() { + x.store(true, std::memory_order_relaxed); // 1 happens-before 2 + y.store(true, + std::memory_order_release); // 2 happens-before 3(由于 3 的循环) +} + +void read_y_then_x() { + while (!y.load(std::memory_order_acquire)) { // 3 happens-before 4 + } + if (x.load(std::memory_order_relaxed)) { // 4 + ++z; + } +} + +int main() { + std::thread t1(write_x_then_y); + std::thread t2(read_y_then_x); + t1.join(); + t2.join(); + assert(z.load() != 0); // 顺序一定为 1234,z 一定不为 0 +} +``` + +* 利用 [Release-Acquire ordering](https://en.cppreference.com/w/cpp/atomic/memory_order#Release-Acquire_ordering) 可以传递同步 + +```cpp +#include +#include + +std::atomic x = false; +std::atomic y = false; +std::atomic v[2]; + +void f() { + // v[0]、v[1] 的设置没有先后顺序,但都 happens-before 1 + v[0].store(1, std::memory_order_relaxed); + v[1].store(2, std::memory_order_relaxed); + x.store(true, + std::memory_order_release); // 1 happens-before 2(由于 2 的循环) +} + +void g() { + while (!x.load(std::memory_order_acquire)) { // 2:happens-before 3 + } + y.store(true, + std::memory_order_release); // 3 happens-before 4(由于 4 的循环) +} + +void h() { + while (!y.load( + std::memory_order_acquire)) { // 4 happens-before v[0]、v[1] 的读取 + } + assert(v[0].load(std::memory_order_relaxed) == 1); + assert(v[1].load(std::memory_order_relaxed) == 2); +} +``` + +* 使用读改写操作可以将上面的两个标记合并为一个 + +```cpp +#include +#include + +std::atomic x = 0; +std::atomic v[2]; + +void f() { + v[0].store(1, std::memory_order_relaxed); + v[1].store(2, std::memory_order_relaxed); + x.store(1, std::memory_order_release); // 1 happens-before 2(由于 2 的循环) +} + +void g() { + int i = 1; + while (!x.compare_exchange_strong( + i, 2, + std::memory_order_acq_rel)) { // 2 happens-before 3(由于 3 的循环) + // x 为 1 时,将 x 替换为 2,返回 true + // x 为 0 时,将 i 替换为 x,返回 false + i = 1; // 返回 false 时,x 未被替换,i 被替换为 0,因此将 i 重新设为 1 + } +} + +void h() { + while (x.load(std::memory_order_acquire) < 2) { // 3 + } + assert(v[0].load(std::memory_order_relaxed) == 1); + assert(v[1].load(std::memory_order_relaxed) == 2); +} +``` + +### [Sequentially-consistent ordering](https://en.cppreference.com/w/cpp/atomic/memory_order#Sequentially-consistent_ordering) + +* memory_order_seq_cst 是所有原子操作的默认选项,可以省略不写。对于标记为 memory_order_seq_cst 的操作,读操作相当于 memory_order_acquire,写操作相当于 memory_order_release,读改写操作相当于 memory_order_acq_rel,此外还附加一个单独的 total ordering,即所有线程对同一操作看到的顺序也是相同的。这是最简单直观的顺序,但由于要求全局的线程同步,因此也是开销最大的 + +```cpp +#include +#include +#include + +std::atomic x = false; +std::atomic y = false; +std::atomic z = 0; + +// 要么 1 happens-before 2,要么 2 happens-before 1 +void write_x() { + x.store(true); // 1 happens-before 3(由于 3 的循环) +} + +void write_y() { + y.store(true); // 2 happens-before 5(由于 5 的循环) +} + +void read_x_then_y() { + while (!x.load()) { // 3 happens-before 4 + } + if (y.load()) { // 4 为 false 则 1 happens-before 2 + ++z; + } +} + +void read_y_then_x() { + while (!y.load()) { // 5 happens-before 6 + } + if (x.load()) { // 6 如果返回 false 则一定是 2 happens-before 1 + ++z; + } +} + +int main() { + std::thread t1(write_x); + std::thread t2(write_y); + std::thread t3(read_x_then_y); + std::thread t4(read_y_then_x); + t1.join(); + t2.join(); + t3.join(); + t4.join(); + assert(z.load() != 0); // z 一定不为 0 + // z 可能为 1 或 2,12 之间必定存在 happens-before 关系 +} +``` + +![](images/4-3.png) + +### [std::atomic_thread_fence](https://en.cppreference.com/w/cpp/atomic/atomic_thread_fence) + +```cpp +#include +#include +#include + +std::atomic x, y; +std::atomic z; + +void f() { + x.store(true, std::memory_order_relaxed); // 1 happens-before 2 + std::atomic_thread_fence(std::memory_order_release); // 2 synchronizes-with 3 + y.store(true, std::memory_order_relaxed); +} + +void g() { + while (!y.load(std::memory_order_relaxed)) { + } + std::atomic_thread_fence(std::memory_order_acquire); // 3 happens-before 4 + if (x.load(std::memory_order_relaxed)) { // 4 + ++z; + } +} + +int main() { + x = false; + y = false; + z = 0; + std::thread t1(f); + std::thread t2(g); + t1.join(); + t2.join(); + assert(z.load() != 0); // 1 happens-before 4 +} +``` + +* 将 x 替换为非原子 bool 类型,行为也一样 + +```cpp +#include +#include +#include + +bool x = false; +std::atomic y; +std::atomic z; + +void f() { + x = true; // 1 happens-before 2 + std::atomic_thread_fence(std::memory_order_release); // 2 synchronizes-with 3 + y.store(true, std::memory_order_relaxed); +} + +void g() { + while (!y.load(std::memory_order_relaxed)) { + } + std::atomic_thread_fence(std::memory_order_acquire); // 3 happens-before 4 + if (x) { // 4 + ++z; + } +} + +int main() { + x = false; + y = false; + z = 0; + std::thread t1(f); + std::thread t2(g); + t1.join(); + t2.join(); + assert(z.load() != 0); // 1 happens-before 4 +} +``` diff --git a/docs/05_designing_lock_based_concurrent_data_structure.md b/docs/05_designing_lock_based_concurrent_data_structure.md new file mode 100644 index 0000000..735ffff --- /dev/null +++ b/docs/05_designing_lock_based_concurrent_data_structure.md @@ -0,0 +1,471 @@ +* 设计并发数据结构要考虑两点,一是确保访问 thread-safe,二是提高并发度 + * thread-safe 基本要求如下 + * 数据结构的不变量(invariant)被一个线程破坏时,确保不被线程看到此状态 + * 提供操作完整的函数来避免数据结构接口中固有的 race condition + * 注意数据结构出现异常时的行为,以确保不变量不被破坏 + * 限制锁的范围,避免可能的嵌套锁,最小化死锁的概率 + * 作为数据结构的设计者,要提高数据结构的并发度,可以从以下角度考虑 + * 部分操作能否在锁的范围外执行 + * 数据结构的不同部分是否被不同的 mutex 保护 + * 是否所有操作需要同级别的保护 + * 在不影响操作语义的前提下,能否对数据结构做简单的修改提高并发度 + * 总结为一点,即最小化线程对共享数据的轮流访问,最大化真实的并发量 + +## thread-safe queue + +* 之前实现过的 thread-safe stack 和 queue 都是用一把锁定保护整个数据结构,这限制了并发性,多线程在成员函数中阻塞时,同一时间只有一个线程能工作。这种限制主要是因为内部实现使用的是 [std::queue](https://en.cppreference.com/w/cpp/container/queue),为了支持更高的并发,需要更换内部的实现方式,使用细粒度的(fine-grained)锁。最简单的实现方式是包含头尾指针的单链表,不考虑并发的单链表实现如下 + +```cpp +#include +#include + +template +class Queue { + public: + Queue() = default; + + Queue(const Queue&) = delete; + + Queue& operator=(const Queue&) = delete; + + void push(T x) { + auto new_node = std::make_unique(std::move(x)); + Node* new_tail_node = new_node.get(); + if (tail_) { + tail_->next = std::move(new_node); + } else { + head_ = std::move(new_node); + } + tail_ = new_tail_node; + } + + std::shared_ptr try_pop() { + if (!head_) { + return nullptr; + } + auto res = std::make_shared(std::move(head_->v)); + std::unique_ptr head_node = std::move(head_); + head_ = std::move(head_node->next); + return res; + } + + private: + struct Node { + explicit Node(T x) : v(std::move(x)) {} + T v; + std::unique_ptr next; + }; + + std::unique_ptr head_; + Node* tail_ = nullptr; +}; +``` + +* 即使用两个 mutex 分别保护头尾指针,这个实现在多线程下也有明显问题。push 可以同时修改头尾指针,会对两个 mutex 上锁,另外仅有一个元素时头尾指针相等,push 写和 try_pop 读的 next 节点是同一对象,产生了竞争,锁的也是同一个 mutex +* 该问题很容易解决,在头节点前初始化一个 dummy 节点即可,这样 push 只访问尾节点,不会再与 try_pop 竞争头节点 + +```cpp +#include +#include + +template +class Queue { + public: + Queue() : head_(new Node), tail_(head_.get()) {} + + Queue(const Queue&) = delete; + + Queue& operator=(const Queue&) = delete; + + void push(T x) { + auto new_val = std::make_shared(std::move(x)); + auto new_node = std::make_unique(); + Node* new_tail_node = new_node.get(); + tail_->v = new_val; + tail_->next = std::move(new_node); + tail_ = new_tail_node; + } + + std::shared_ptr try_pop() { + if (head_.get() == tail_) { + return nullptr; + } + std::shared_ptr res = head->v; + std::unique_ptr head_node = std::move(head_); + head_ = std::move(head_node->next); + return res; + } + + private: + struct Node { + std::shared_ptr v; + std::unique_ptr next; + }; + + std::unique_ptr head_; + Node* tail_ = nullptr; +}; +``` + +* 接着加上锁,锁的范围应该尽可能小 + +```cpp +#include +#include +#include + +template +class ConcurrentQueue { + public: + ConcurrentQueue() : head_(new Node), tail_(head_.get()) {} + + ConcurrentQueue(const ConcurrentQueue&) = delete; + + ConcurrentQueue& operator=(const ConcurrentQueue&) = delete; + + void push(T x) { + auto new_val = std::make_shared(std::move(x)); + auto new_node = std::make_unique(); + Node* new_tail_node = new_node.get(); + + std::lock_guard l(tail_mutex_); + tail_->v = new_val; + tail_->next = std::move(new_node); + tail_ = new_tail_node; + } + + std::shared_ptr try_pop() { + std::unique_ptr head_node = pop_head(); + return head_node ? head_node->v : nullptr; + } + + private: + struct Node { + std::shared_ptr v; + std::unique_ptr next; + }; + + private: + std::unique_ptr pop_head() { + std::lock_guard l(head_mutex_); + if (head_.get() == get_tail()) { + return nullptr; + } + std::unique_ptr head_node = std::move(head_); + head_ = std::move(head_node->next); + return head_node; + } + + Node* get_tail() { + std::lock_guard l(tail_mutex_); + return tail_; + } + + private: + std::unique_ptr head_; + Node* tail_ = nullptr; + std::mutex head_mutex_; + std::mutex tail_mutex_; +}; +``` + +* push 中创建新值和新节点都没上锁,多线程可用并发创建新值和新节点。虽然同时只有一个线程能添加新节点,但这只需要一个指针赋值操作,锁住尾节点的时间很短,try_pop 中对尾节点只是用来做一次比较,持有尾节点的时间同样很短,因此 try_pop 和 push 几乎可以同时调用。try_pop 中锁住头节点所做的也只是指针赋值操作,开销较大的析构在锁外进行,这意味着虽然同时只有一个线程能 pop_head,但允许多线程删除节点并返回数据,提升了 try_pop 的并发调用数量 +* 最后再结合 [std::condition_variable](https://en.cppreference.com/w/cpp/thread/condition_variable) 实现 wait_and_pop,即得到与之前接口相同但并发度更高的 thread-safe queue + +```cpp +#include +#include +#include +#include + +template +class ConcurrentQueue { + public: + ConcurrentQueue() : head_(new Node), tail_(head_.get()) {} + + ConcurrentQueue(const ConcurrentQueue&) = delete; + + ConcurrentQueue& operator=(const ConcurrentQueue&) = delete; + + void push(T x) { + auto new_val = std::make_shared(std::move(x)); + auto new_node = std::make_unique(); + Node* new_tail_node = new_node.get(); + { + std::lock_guard l(tail_mutex_); + tail_->v = new_val; + tail_->next = std::move(new_node); + tail_ = new_tail_node; + } + cv_.notify_one(); + } + + std::shared_ptr try_pop() { + std::unique_ptr head_node = try_pop_head(); + return head_node ? head_node->v : nullptr; + } + + bool try_pop(T& res) { + std::unique_ptr head_node = try_pop_head(res); + return head_node != nullptr; + } + + std::shared_ptr wait_and_pop() { + std::unique_ptr head_node = wait_pop_head(); + return head_node->v; + } + + void wait_and_pop(T& res) { wait_pop_head(res); } + + bool empty() const { + std::lock_guard l(head_mutex_); + return head_.get() == get_tail(); + } + + private: + struct Node { + std::shared_ptr v; + std::unique_ptr next; + }; + + private: + std::unique_ptr try_pop_head() { + std::lock_guard l(head_mutex_); + if (head_.get() == get_tail()) { + return nullptr; + } + return pop_head(); + } + + std::unique_ptr try_pop_head(T& res) { + std::lock_guard l(head_mutex_); + if (head_.get() == get_tail()) { + return nullptr; + } + res = std::move(*head_->v); + return pop_head(); + } + + std::unique_ptr wait_pop_head() { + std::unique_lock l(wait_for_data()); + return pop_head(); + } + + std::unique_ptr wait_pop_head(T& res) { + std::unique_lock l(wait_for_data()); + res = std::move(*head_->v); + return pop_head(); + } + + std::unique_lock wait_for_data() { + std::unique_lock l(head_mutex_); + cv_.wait(l, [this] { return head_.get() != get_tail(); }); + return l; + } + + std::unique_ptr pop_head() { + std::unique_ptr head_node = std::move(head_); + head_ = std::move(head_node->next); + return head_node; + } + + Node* get_tail() { + std::lock_guard l(tail_mutex_); + return tail_; + } + + private: + std::unique_ptr head_; + Node* tail_ = nullptr; + std::mutex head_mutex_; + mutable std::mutex tail_mutex_; + std::condition_variable cv_; +}; +``` + +## thread-safe map + +* 并发访问 [std::map](https://en.cppreference.com/w/cpp/container/map) 和 [std::unordered_map](https://en.cppreference.com/w/cpp/container/unordered_map) 的接口的问题在于迭代器,其他线程删除元素时会导致迭代器失效,因此 thread-safe map 的接口设计就要跳过迭代器 +* 为了使用细粒度锁,就不应该使用标准库容器。可选的关联容器数据结构有三种,一是二叉树(如红黑树),但每次查找修改都要从访问根节点开始,也就表示根节点需要上锁,尽管沿着树向下访问节点时会解锁,但这个比起覆盖整个数据结构的单个锁好不了多少 +* 第二种方式是有序数组,这比二叉树还差,因为无法提前得知一个给定的值应该放在哪,于是同样需要一个覆盖整个数组的锁 +* 第三种方式是哈希表。假如有一个固定数量的桶,一个 key 属于哪个桶取决于 key 的属性和哈希函数,这意味着可以安全地分开锁住每个桶。如果使用读写锁,就能将并发度提高相当于桶数量的倍数 + +```cpp +#include +#include +#include +#include +#include +#include +#include +#include +#include + +template > +class ConcurrentMap { + public: + // 桶数默认为 19(一般用 x % 桶数作为 x 的桶索引,桶数为质数可使桶分布均匀) + ConcurrentMap(std::size_t n = 19, const Hash& h = Hash{}) + : buckets_(n), hasher_(h) { + for (auto& x : buckets_) { + x.reset(new Bucket); + } + } + + ConcurrentMap(const ConcurrentMap&) = delete; + + ConcurrentMap& operator=(const ConcurrentMap&) = delete; + + V get(const K& k, const V& default_value = V{}) const { + return get_bucket(k).get(k, default_value); + } + + void set(const K& k, const V& v) { get_bucket(k).set(k, v); } + + void erase(const K& k) { get_bucket(k).erase(k); } + + // 为了方便使用,提供一个到 std::map 的映射 + std::map to_map() const { + std::vector> locks; + for (auto& x : buckets_) { + locks.emplace_back(std::unique_lock(x->m)); + } + std::map res; + for (auto& x : buckets_) { + for (auto& y : x->data) { + res.emplace(y); + } + } + return res; + } + + private: + struct Bucket { + std::list> data; + mutable std::shared_mutex m; // 每个桶都用这个锁保护 + + V get(const K& k, const V& default_value) const { + // 没有修改任何值,异常安全 + std::shared_lock l(m); // 只读锁,可共享 + auto it = std::find_if(data.begin(), data.end(), + [&](auto& x) { return x.first == k; }); + return it == data.end() ? default_value : it->second; + } + + void set(const K& k, const V& v) { + std::unique_lock l(m); // 写,单独占用 + auto it = std::find_if(data.begin(), data.end(), + [&](auto& x) { return x.first == k; }); + if (it == data.end()) { + data.emplace_back(k, v); // emplace_back 异常安全 + } else { + it->second = v; // 赋值可能抛异常,但值是用户提供的,可放心让用户处理 + } + } + + void erase(const K& k) { + std::unique_lock l(m); // 写,单独占用 + auto it = std::find_if(data.begin(), data.end(), + [&](auto& x) { return x.first == k; }); + if (it != data.end()) { + data.erase(it); + } + } + }; + + Bucket& get_bucket(const K& k) const { // 桶数固定因此可以无锁调用 + return *buckets_[hasher_(k) % buckets_.size()]; + } + + private: + std::vector> buckets_; + Hash hasher_; +}; +``` + +## thread-safe list + +```cpp +#include +#include +#include + +template +class ConcurrentList { + public: + ConcurrentList() = default; + + ~ConcurrentList() { + remove_if([](const Node&) { return true; }); + } + + ConcurrentList(const ConcurrentList&) = delete; + + ConcurrentList& operator=(const ConcurrentList&) = delete; + + void push_front(const T& x) { + std::unique_ptr t(new Node(x)); + std::lock_guard head_lock(head_.m); + t->next = std::move(head_.next); + head_.next = std::move(t); + } + + template + void for_each(F f) { + Node* cur = &head_; + std::unique_lock head_lock(head_.m); + while (Node* const next = cur->next.get()) { + std::unique_lock next_lock(next->m); + head_lock.unlock(); // 锁住了下一节点,因此可以释放上一节点的锁 + f(*next->data); + cur = next; // 当前节点指向下一节点 + head_lock = std::move(next_lock); // 转交下一节点锁的所有权,循环上述过程 + } + } + + template + std::shared_ptr find_first_if(F f) { + Node* cur = &head_; + std::unique_lock head_lock(head_.m); + while (Node* const next = cur->next.get()) { + std::unique_lock next_lock(next->m); + head_lock.unlock(); + if (f(*next->data)) { + return next->data; // 返回目标值,无需继续查找 + } + cur = next; + head_lock = std::move(next_lock); + } + return nullptr; + } + + template + void remove_if(F f) { + Node* cur = &head_; + std::unique_lock head_lock(head_.m); + while (Node* const next = cur->next.get()) { + std::unique_lock next_lock(next->m); + if (f(*next->data)) { // 为 true 则移除下一节点 + std::unique_ptr old_next = std::move(cur->next); + cur->next = std::move(next->next); // 下一节点设为下下节点 + next_lock.unlock(); + } else { // 否则继续转至下一节点 + head_lock.unlock(); + cur = next; + head_lock = std::move(next_lock); + } + } + } + + private: + struct Node { + std::mutex m; + std::shared_ptr data; + std::unique_ptr next; + Node() = default; + Node(const T& x) : data(std::make_shared(x)) {} + }; + + Node head_; +}; +``` diff --git a/docs/06_designing_lock_free_concurrent_data_structure.md b/docs/06_designing_lock_free_concurrent_data_structure.md new file mode 100644 index 0000000..70fdf55 --- /dev/null +++ b/docs/06_designing_lock_free_concurrent_data_structure.md @@ -0,0 +1,599 @@ +## 非阻塞数据结构 + +* 阻塞的算法和数据结构使用 mutex、条件变量、期值来同步数据,但非阻塞不等价于 lock-free,比如自旋锁没有使用任何阻塞函数的调用,是非阻塞的,但并非 lock-free +* 非阻塞数据结构由松到严可分为三个等级:obstruction-free、lock-free、wait-free + * obstruction-free(无障碍):如果其他线程都暂停了,任何一个给定的线程都会在有限步数内完成操作。上例就是这种情况,但这种情况很少见,所以满足这个条件只能算一个失败的 lock-free 实现 + * lock-free(无锁):如果多线程在同一个数据结构上操作,其中一个将在有限步数内完成操作。满足 lock-free 必定满足 obstruction-free + * wait-free(无等待):如果多线程在同一个数据结构上操作,每个线程都会在有限步数内完成操作。满足 wait-free 必定满足 lock-free,但 wait-free 很难实现,因为要保证有限步数内完成操作,就要保证操作一次通过,并且执行到某一步不能导致其他线程操作失败 +* lock-free 数据结构必须允许多线程并发访问,但它们不能做相同操作,比如一个 lock-free 的 queue 允许一个线程 push、另一个线程 pop,但不允许两个线程同时 push。此外,如果一个访问 lock-free 数据结构的线程被中途挂起,其他线程必须能完成操作而不需要等待挂起的线程 +* 使用 lock-free 数据结构主要是为了最大化并发访问,不需要阻塞。第二个原因是鲁棒性,如果线程在持有锁时死掉就会导致数据结构被永久破坏,而对 lock-free 数据结构来说,除了死掉的线程里的数据,其他的数据都不会丢失。lock-free 没有任何锁,所以一定不会出现死锁 +* 但 lock-free 可能造成更大开销,用于 lock-free 的原子操作比非原子操作慢得多,且 lock-free 数据结构中的原子操作一般比 lock-based 中的多,此外,硬件必须访问同一个原子变量以在线程间同步数据。无论 lock-free 还是 lock-based,性能方面的检查(最坏情况等待时间、平均等待时间、总体执行时间或其他方面)都是非常重要的 + +## lock-free thread-safe stack + +* 最简单的 stack 实现方式是包含头节点指针的链表。push 的过程很简单,创建一个新节点,然后让新节点的 next 指针指向当前 head,最后 head 设为新节点 +* 这里的 race condition 在于,如果两个线程同时 push,让各自的新节点的 next 指针指向当前 head,这样必然导致 head 最终设为二者之一的新节点,而另一个被丢弃 +* 解决方法是,在最后设置 head 时先进行判断,只有当前 head 与新节点的 next 相等,才将 head 设为新节点,如果不等则让 next 指向当前 head 并重新判断。而这个操作必须是原子的,因此就需要使用 [compare_exchange_weak](https://en.cppreference.com/w/cpp/atomic/atomic/compare_exchange),不需要使用 [compare_exchange_strong](https://en.cppreference.com/w/cpp/atomic/atomic/compare_exchange),因为 [compare_exchange_weak](https://en.cppreference.com/w/cpp/atomic/atomic/compare_exchange) 在相等时可能替换失败,但替换失败也会返回 false,放在循环里带来的效果是一样的,而 [compare_exchange_weak](https://en.cppreference.com/w/cpp/atomic/atomic/compare_exchange) 在一些机器架构上可以产生比 [compare_exchange_strong](https://en.cppreference.com/w/cpp/atomic/atomic/compare_exchange) 更优化的代码 + +```cpp +#include + +template +class LockFreeStack { + public: + void push(const T& x) { + Node* t = new Node(x); + t->next = head_.load(); + while (!head_.compare_exchange_weak(t->next, t)) { + } + } + + private: + struct Node { + T v; + Node* next = nullptr; + Node(const T& x) : v(x) {} + }; + + private: + std::atomic head_; +}; +``` + +* pop 的过程很简单,先存储当前头节点指针,再将头节点设为下一节点,最后返回存储的头节点并删除指针。这里的 race condition 在于,如果两个线程同时 pop,如果一个已经删除了头节点,另一个线程读取头节点的下一节点就访问了空悬指针 +* 先绕开删除指针这一步,考虑前几步的实现 + +```cpp +template +void LockFreeStack::pop(T& res) { + Node* t = head_.load(); // 未考虑头节点为空指针的情况 + while (!head_.compare_exchange_weak(t, t->next)) { + } + res = t->v; +} +``` + +* 传引用来保存结果的原因是,如果直接返回值,返回前一定会先移除元素,如果拷贝返回值时抛出异常,移除的元素就丢失了。但传引用的问题是,如果其他线程移除了节点,被移除的节点不能被解引用,当前线程就无法安全地拷贝数据。因此,如果想安全地返回值,应该返回智能指针 + +```cpp +#include +#include + +template +class LockFreeStack { + public: + void push(const T& x) { + Node* t = new Node(x); + t->next = head_.load(); + while (!head_.compare_exchange_weak(t->next, t)) { + } + } + + std::shared_ptr pop() { // 还未考虑释放原来的头节点指针 + Node* t = head_.load(); + while (t && !head_.compare_exchange_weak(t, t->next)) { + } + return t ? t->v : nullptr; + } + + private: + struct Node { + std::shared_ptr v; + Node* next = nullptr; + Node(const T& x) : v(std::make_shared(x)) {} + }; + + private: + std::atomic head_; +}; +``` + +* 释放被移除的节点的难点在于,一个线程在释放内存时,无法得知其他线程是否持有要释放的指针 +* 只要没有其他线程调用 pop,就能安全释放,因此可以用一个计数器来记录调用 pop 的线程数,计数不为 1 时,先把节点添加到待删除节点列表中,计数为 1 则安全释放 + +```cpp +#include +#include + +template +class LockFreeStack { + public: + void push(const T& x) { + Node* t = new Node(x); + t->next = head_.load(); + while (!head_.compare_exchange_weak(t->next, t)) { + } + } + + std::shared_ptr pop() { + ++pop_cnt_; + Node* t = head_.load(); + while (t && !head_.compare_exchange_weak(t, t->next)) { + } + std::shared_ptr res; + if (t) { + res.swap(t->v); + } + try_delete(t); + return res; + } + + private: + struct Node { + std::shared_ptr v; + Node* next = nullptr; + Node(const T& x) : v(std::make_shared(x)) {} + }; + + private: + static void delete_list(Node* head) { + while (head) { + Node* t = head->next; + delete head; + head = t; + } + } + + void append_to_delete_list(Node* first, Node* last) { + last->next = to_delete_list_; + // 确保 last->next 为 to_delete_list_,再设置 first 为新的头节点 + while (!to_delete_list_.compare_exchange_weak(last->next, first)) { + } + } + + void append_to_delete_list(Node* head) { + Node* last = head; + while (Node* t = last->next) { + last = t; + } + append_to_delete_list(head, last); + } + + void try_delete(Node* head) { + if (pop_cnt_ == 0) { + return; + } + if (pop_cnt_ > 1) { + append_to_delete_list(head, head); + --pop_cnt_; + return; + } + Node* t = to_delete_list_.exchange(nullptr); + if (--pop_cnt_ == 0) { + delete_list(t); + } else if (t) { + append_to_delete_list(t); + } + delete head; + } + + private: + std::atomic head_; + std::atomic pop_cnt_; + std::atomic to_delete_list_; +}; +``` + +* 如果要释放所有节点,必须有一个时刻计数器为 0。在高负载的情况下,往往不会存在这样的时刻,从而导致待删除节点的列表无限增长 + +### Hazard Pointer(风险指针) + +* 另一个释放的思路是,在线程访问节点时,设置一个保存了线程 ID 和该节点的风险指针。用一个全局数组保存所有线程的风险指针,释放节点时,如果数组中不存在包含该节点的风险指针,则可以直接释放,否则将节点添加到待删除列表中。风险指针实现如下 + +```cpp +#include +#include +#include + +static constexpr std::size_t MaxSize = 100; + +struct HazardPointer { + std::atomic id; + std::atomic p; +}; + +static HazardPointer HazardPointers[MaxSize]; + +class HazardPointerHelper { + public: + HazardPointerHelper() { + for (auto& x : HazardPointers) { + std::thread::id default_id; + if (x.id.compare_exchange_strong(default_id, + std::this_thread::get_id())) { + hazard_pointer = &x; // 取一个未设置过的风险指针 + break; + } + } + if (!hazard_pointer) { + throw std::runtime_error("No hazard pointers available"); + } + } + + ~HazardPointerHelper() { + hazard_pointer->p.store(nullptr); + hazard_pointer->id.store(std::thread::id{}); + } + + HazardPointerHelper(const HazardPointerHelper&) = delete; + + HazardPointerHelper operator=(const HazardPointerHelper&) = delete; + + std::atomic& get() { return hazard_pointer->p; } + + private: + HazardPointer* hazard_pointer = nullptr; +}; + +std::atomic& hazard_pointer_for_this_thread() { + static thread_local HazardPointerHelper t; + return t.get(); +} + +bool is_existing(void* p) { + for (auto& x : HazardPointers) { + if (x.p.load() == p) { + return true; + } + } + return false; +} +``` + +* 使用风险指针 + +```cpp +#include +#include +#include + +#include "hazard_pointer.hpp" + +template +class LockFreeStack { + public: + void push(const T& x) { + Node* t = new Node(x); + t->next = head_.load(); + while (!head_.compare_exchange_weak(t->next, t)) { + } + } + + std::shared_ptr pop() { + std::atomic& hazard_pointer = hazard_pointer_for_this_thread(); + Node* t = head_.load(); + do { // 外循环确保 t 为最新的头节点,循环结束后将头节点设为下一节点 + Node* t2; + do { // 循环至风险指针保存当前最新的头节点 + t2 = t; + hazard_pointer.store(t); + t = head_.load(); + } while (t != t2); + } while (t && !head_.compare_exchange_strong(t, t->next)); + hazard_pointer.store(nullptr); + std::shared_ptr res; + if (t) { + res.swap(t->v); + if (is_existing(t)) { + append_to_delete_list(new DataToDelete{t}); + } else { + delete t; + } + try_delete(); + } + return res; + } + + private: + struct Node { + std::shared_ptr v; + Node* next = nullptr; + Node(const T& x) : v(std::make_shared(x)) {} + }; + + struct DataToDelete { + template + DataToDelete(T* p) + : data(p), deleter([](void* p) { delete static_cast(p); }) {} + + ~DataToDelete() { deleter(data); } + + void* data = nullptr; + std::function deleter; + DataToDelete* next = nullptr; + }; + + private: + void append_to_delete_list(DataToDelete* t) { + t->next = to_delete_list_.load(); + while (!to_delete_list_.compare_exchange_weak(t->next, t)) { + } + } + + void try_delete() { + DataToDelete* cur = to_delete_list_.exchange(nullptr); + while (cur) { + DataToDelete* t = cur->next; + if (!is_existing(cur->data)) { + delete cur; + } else { + append_to_delete_list(new DataToDelete{cur}); + } + cur = t; + } + } + + private: + std::atomic head_; + std::atomic pop_cnt_; + std::atomic to_delete_list_; +}; +``` + +* 风险指针实现简单并达到了安全释放的目的,但每次删除节点前后都要遍历数组并原子访问内部指针来检查,增加了很多开销 +* 无锁内存回收技术领域十分活跃,大公司都会申请自己的专利,风险指针包含在 IBM 提交的专利申请中,在 GPL 协议下允许免费使用 + +### 引用计数 + +* 另一个方案是使用引用计数记录访问每个节点的线程数量,[std::shared_ptr](https://en.cppreference.com/w/cpp/memory/shared_ptr) 的操作是原子的,但要检查是否 lock-free + +```cpp +std::shared_ptr p(new int(42)); +assert(std::atomic_is_lock_free(&p)); +``` + +* 如果是,则可以用于实现 lock-free stack + +```cpp +#include +#include + +template +class LockFreeStack { + public: + ~LockFreeStack() { + while (pop()) { + } + } + + void push(const T& x) { + auto t = std::make_shared(x); + t->next = std::atomic_load(&head_); + while (!std::atomic_compare_exchange_weak(&head_, &t->next, t)) { + } + } + + std::shared_ptr pop() { + std::shared_ptr t = std::atomic_load(&head_); + while (t && !std::atomic_compare_exchange_weak(&head_, &t, t->next)) { + } + if (t) { + std::atomic_store(&t->next, nullptr); + return t->v; + } + return nullptr; + } + + private: + struct Node { + std::shared_ptr v; + std::shared_ptr next; + Node(const T& x) : v(std::make_shared(x)) {} + }; + + private: + std::shared_ptr head_; +}; +``` + +* C++20 支持 [std::atomic\](https://en.cppreference.com/w/cpp/memory/shared_ptr/atomic2) + +```cpp +#include +#include + +template +class LockFreeStack { + public: + ~LockFreeStack() { + while (pop()) { + } + } + + void push(const T& x) { + auto t = std::make_shared(x); + t->next = head_.load(); + while (!head_.compare_exchange_weak(t->next, t)) { + } + } + + std::shared_ptr pop() { + std::shared_ptr t = head_.load(); + while (t && !head_.compare_exchange_weak(t, t->next.load())) { + } + if (t) { + t->next = std::shared_ptr(); + return t->v; + } + return nullptr; + } + + private: + struct Node { + std::shared_ptr v; + std::atomic> next; + Node(const T& x) : v(std::make_shared(x)) {} + }; + + private: + std::atomic> head_; +}; +``` + +* 但 VS2022 上测试发现 [std::atomic\](https://en.cppreference.com/w/cpp/memory/shared_ptr/atomic2) 并非 lock-free + +```cpp +assert(!std::atomic>{}.is_lock_free()); +``` + +* 更通用的方法是手动管理引用计数,为每个节点设置内外部两个引用计数,两者之和就是节点的引用计数,外部计数默认为 1,访问对象时递增外部计数并递减内部计数,访问结束后则不再需要外部计数,将外部计数减 2 并加到内部计数上 + +```cpp +#include +#include + +template +class LockFreeStack { + public: + ~LockFreeStack() { + while (pop()) { + } + } + + void push(const T& x) { + ReferenceCount t; + t.p = new Node(x); + t.external_cnt = 1; + t.p->next = head_.load(); + while (!head_.compare_exchange_weak(t.p->next, t)) { + } + } + + std::shared_ptr pop() { + ReferenceCount t = head_.load(); + while (true) { + increase_count(t); // 外部计数递增表示该节点正被使用 + Node* p = t.p; // 因此可以安全地访问 + if (!p) { + return nullptr; + } + if (head_.compare_exchange_strong(t, p->next)) { + std::shared_ptr res; + res.swap(p->v); + // 将外部计数减 2 后加到内部计数,减 2 是因为, + // 节点被删除减 1,该线程无法再次访问此节点再减 1 + const int cnt = t.external_cnt - 2; + if (p->inner_cnt.fetch_add(cnt) == -cnt) { + delete p; // 内外部计数和为 0 + } + return res; + } + if (p->inner_cnt.fetch_sub(1) == 1) { + delete p; // 内部计数为 0 + } + } + } + + private: + struct Node; + + struct ReferenceCount { + int external_cnt; + Node* p; + }; + + struct Node { + std::shared_ptr v; + std::atomic inner_cnt = 0; + ReferenceCount next; + Node(const T& x) : v(std::make_shared(x)) {} + }; + + void increase_count(ReferenceCount& old_cnt) { + ReferenceCount new_cnt; + do { + new_cnt = old_cnt; + ++new_cnt.external_cnt; // 访问 head_ 时递增外部计数,表示该节点正被使用 + } while (!head_.compare_exchange_strong(old_cnt, new_cnt)); + old_cnt.external_cnt = new_cnt.external_cnt; + } + + private: + std::atomic head_; +}; +``` + +* 不指定内存序则默认使用开销最大的 `std::memory_order_seq_cst`,下面根据操作间的依赖关系优化为最小内存序 + +```cpp +#include +#include + +template +class LockFreeStack { + public: + ~LockFreeStack() { + while (pop()) { + } + } + + void push(const T& x) { + ReferenceCount t; + t.p = new Node(x); + t.external_cnt = 1; + // 下面比较中 release 保证之前的语句都先执行,因此 load 可以使用 relaxed + t.p->next = head_.load(std::memory_order_relaxed); + while (!head_.compare_exchange_weak(t.p->next, t, std::memory_order_release, + std::memory_order_relaxed)) { + } + } + + std::shared_ptr pop() { + ReferenceCount t = head_.load(std::memory_order_relaxed); + while (true) { + increase_count(t); // acquire + Node* p = t.p; + if (!p) { + return nullptr; + } + if (head_.compare_exchange_strong(t, p->next, + std::memory_order_relaxed)) { + std::shared_ptr res; + res.swap(p->v); + // 将外部计数减 2 后加到内部计数,减 2 是因为, + // 节点被删除减 1,该线程无法再次访问此节点再减 1 + const int cnt = t.external_cnt - 2; + // swap 要先于 delete,因此使用 release + if (p->inner_cnt.fetch_add(cnt, std::memory_order_release) == -cnt) { + delete p; // 内外部计数和为 0 + } + return res; + } + if (p->inner_cnt.fetch_sub(1, std::memory_order_relaxed) == 1) { + p->inner_cnt.load(std::memory_order_acquire); // 只是用 acquire 来同步 + // acquire 保证 delete 在之后执行 + delete p; // 内部计数为 0 + } + } + } + + private: + struct Node; + + struct ReferenceCount { + int external_cnt; + Node* p = nullptr; + }; + + struct Node { + std::shared_ptr v; + std::atomic inner_cnt = 0; + ReferenceCount next; + Node(const T& x) : v(std::make_shared(x)) {} + }; + + void increase_count(ReferenceCount& old_cnt) { + ReferenceCount new_cnt; + do { // 比较失败不改变当前值,并可以继续循环,因此可以选择 relaxed + new_cnt = old_cnt; + ++new_cnt.external_cnt; // 访问 head_ 时递增外部计数,表示该节点正被使用 + } while (!head_.compare_exchange_strong(old_cnt, new_cnt, + std::memory_order_acquire, + std::memory_order_relaxed)); + old_cnt.external_cnt = new_cnt.external_cnt; + } + + private: + std::atomic head_; +}; +``` diff --git a/docs/07_designing_concurrent_code.md b/docs/07_designing_concurrent_code.md new file mode 100644 index 0000000..35cf29d --- /dev/null +++ b/docs/07_designing_concurrent_code.md @@ -0,0 +1,949 @@ +## 线程间的工作划分 + +* 为了提高线程利用率并最小化开销,必须决定要使用的线程数量,并为每个线程合理分配任务 + +### 开始处理之前的线程间数据划分 + +* 简单算法最容易并行化,比如要并行化 [std::for_each](https://en.cppreference.com/w/cpp/algorithm/for_each),把元素划分到不同的线程上执行即可。如何划分才能获取最优性能,取决于数据结构的细节,这里用一个最简单的划分为例,每 N 个元素分配给一个线程,每个线程不需要与其他线程通信,直到独立完成各自的处理任务 + +![](images/7-1.png) + +* 如果使用过 [MPI](https://www.mpi-forum.org/) 或 [OpenMP](https://www.openmp.org/),会很熟悉这个结构,即把一个任务划分成一系列并行任务,工作线程独立完成任务,最后 reduce 合并结果。不过对 for_each 来说,最后的 reduce 实际不需要执行操作,但对其他需要合并结果的并行算法来说,最后一步很重要 +* 尽管这个技术很强大,但不是万能的,有时数据不能灵活划分,只有在处理数据时划分才明显,最能明显体现这点的就是递归算法,比如快速排序 + +### 递归划分数据 + +* 要并行化快速排序,无法直接划分数据,因为只有处理之后才知道某一项应该置于基数的哪一边。因此,很容易想到的是使用递归,其中的递归调用完全独立,各自处理不同的元素集,十分适合并发执行 + +![](images/7-2.png) + +* 如果数据集很大,为每个递归生成新线程就会生成大量线程,如果线程过多就会影响性能。因此需要严格控制线程数,不过这个问题可以直接抛给 [std::async](https://en.cppreference.com/w/cpp/thread/async) + +```cpp +#include +#include +#include +#include + +template +std::list parallel_quick_sort(std::list v) { + if (v.empty()) { + return {}; + } + std::list res; + res.splice(res.begin(), v, v.begin()); + auto it = std::partition(v.begin(), v.end(), + [&](const T& x) { return x < res.front(); }); + std::list low; + low.splice(low.end(), v, v.begin(), it); + std::future> l( + std::async(¶llel_quick_sort, std::move(low))); + auto r(parallel_quick_sort(std::move(v))); + res.splice(res.end(), r); + res.splice(res.begin(), l.get()); + return res; +} + +int main() { + std::list input = {5, 1, 9, 2, 9, 100, 8}; + std::list expected = {1, 2, 5, 8, 9, 9, 100}; + assert(parallel_quick_sort(input) == expected); +} +``` + +* 也可以通过 [hardware_concurrency](https://en.cppreference.com/w/cpp/thread/thread/hardware_concurrency) 得知硬件可支持的线程数,再自己管理线程数。下面是一个使用 stack 存储已排序数据的并行快速排序 + +```cpp +#include +#include +#include +#include +#include +#include +#include + +#include "concurrent_stack.hpp" + +template +class Sorter { + public: + Sorter() : max_thread_count(std::thread::hardware_concurrency() - 1) {} + + ~Sorter() { + end_of_data = true; + for (auto& x : threads) { + if (x.joinable()) { + x.join(); + } + } + } + + std::list do_sort(std::list& v) { + if (v.empty()) { + return {}; + } + std::list res; + res.splice(res.begin(), v, v.begin()); + auto it = std::partition(v.begin(), v.end(), + [&](const T& x) { return x < res.front(); }); + ChunkToSort low; + low.data.splice(low.data.end(), v, v.begin(), it); + std::future> l = low.promise.get_future(); + chunks.push(std::move(low)); + if (threads.size() < max_thread_count) { + threads.emplace_back(&Sorter::sort_thread, this); + } + auto r{do_sort(v)}; + res.splice(res.end(), r); + while (l.wait_for(std::chrono::seconds(0)) != std::future_status::ready) { + try_sort_chunk(); + } + res.splice(res.begin(), l.get()); + return res; + } + + private: + struct ChunkToSort { + std::list data; + std::promise> promise; + }; + + private: + void sort_chunk(const std::shared_ptr& chunk) { + chunk->promise.set_value(do_sort(chunk->data)); + } + + void try_sort_chunk() { + std::shared_ptr chunk = chunks.pop(); + if (chunk) { + sort_chunk(chunk); + } + } + + void sort_thread() { + while (!end_of_data) { + try_sort_chunk(); + std::this_thread::yield(); + } + } + + private: + ConcurrentStack chunks; + std::vector threads; + const std::size_t max_thread_count; + std::atomic end_of_data = false; +}; + +template +std::list parallel_quick_sort(std::list v) { + if (v.empty()) { + return {}; + } + return Sorter{}.do_sort(v); +} +``` + +### 基于任务划分 + +* 如果数据动态生成或来自外部输入,上述划分方式都不适用,此时应该基于任务而非基于数据来划分。一种基于任务的划分方式是让线程针对性处理任务,对同一数据进行不同的操作,而不是都做相同的工作。这样线程是独立的,每个线程只需要负责完成总任务的某一部分。这就是 SoC(separation of concerns,关注点分离)设计原则 +* 单线程中,如果有多个任务需要执行,只能依次执行任务,任务需要保存完成状态,并周期性地返回控制流给主循环。如果循环中添加了很多任务,就会导致程序变慢,对于一个用户发起的事件可能很久才会响应 +* 这就是使用线程的原因,如果每个任务分离在线程上,保存状态和返回控制流给主循环这些事都抛给了操作系统,此时只需要关注任务本身,并且任务还可以并发运行,这样用户也能及时得到响应 +* 但现实不一定这么顺利。如果任务都是独立的,线程之间不需要通信,那就很简单了。然而,这些后台运行的任务经常需要处理用户请求,因此就需要在完成时更新用户接口,以通知用户。此外,用户还可能想取消任务,这样就需要用户接口发送一条通知后台任务终止的消息。这些情况都要求周全的考虑和设计,以及合适的同步 +* 虽然如此,但关注点仍然是分离的。用户接口线程线程仍处理用户接口,只是可能在被其他线程请求时要更新接口。同理,后台任务线程仍然关注自己的任务,只是允许被其他线程请求终止 +* 多线程不是一定要 SoC,比如线程间有很多共享数据,或者需要互相等待。对于这样存在过多通信的线程,应该先找出通信的原因,如果所有的通信都关联同一个问题,合并成一个单线程来处理可能更好一些 +* 基于任务划分不要求完全隔离,如果多个输入数据集合适用相同顺序的操作,可以把这个操作序列划分为多个子阶段来分配给每个线程,当一个线程完成操作后就把数据放进队列,供下一线程使用,这就是 pipeline。这也是另一种划分数据的方式,适用于操作开始前输入数据不是完全已知的情况,比如来自网络的数据或者扫描文件系统以识别要处理的文件 +* 对于序列中耗时的操作,pipeline 就能提高响应速度。比如,如果操作包含 4 步,每步 5 秒,处理完一个数据就要 20 秒,如果有 4 个包含整个操作的线程,虽然每 20 秒能处理 4 个数据,但每个数据仍要 20 秒处理。使用 pipeline,每个线程只处理一步,对于第一个数据需要 20 秒处理,之后处理每个数据都只需要 5 秒 + +```cpp +// 非 pipeline:每 20 秒 4 个数据(每个数据仍要 20 秒) +线程A:-1- -1- -1- -1- -5- -5- -5- -5- +线程B:-2- -2- -2- -2- -6- -6- -6- -6- +线程C:-3- -3- -3- -3- -7- -7- -7- -7- +线程D:-4- -4- -4- -4- -8- -8- -8- -8- + +// pipeline:第一个数据 20 秒,之后每个 5 秒 +线程A:-1- -2- -3- -4- -5- -6- -7- -8- +线程B:--- -1- -2- -3- -4- -5- -6- -7- +线程C:--- --- -1- -2- -3- -4- -5- -6- +线程D:--- --- --- -1- -2- -3- -4- -5- +``` + +* 以视频解码为例,每 4 秒 120 帧,第一秒达到 120 帧,卡顿 3 秒后播放下一个 120 帧,这样远不如稳定的每秒 30 帧 + +## 影响并发代码性能的因素 + +### 处理器数量 + +* 处理器数量是影响多线程程序性能的首要因素,一个并发程序在不同环境下的表现迥异,而开发者的环境和用户很可能不同,比如开发环境是双核或四核系统,但用户是任意多核或单核,因此必须谨慎考虑可能的影响并对其测试 +* 单个 16 核、4 个四核、16 个单核是近似的,都能并发运行 16 个线程,要利用好这点,开发的程序必须至少用上 16 个线程。如果少于 16 个,就会浪费处理器性能(不考虑系统运行其他程序的情况),另一方面,如果多于 16 个,就会让处理器浪费时间在切换线程上,这种情况就是 oversubscription +* 使用 [hardware_concurrency](https://en.cppreference.com/w/cpp/thread/thread/hardware_concurrency) 可以获取硬件支持的线程数,但要注意它不会考虑已运行在系统上的其他线程,如果多个线程都用它给出的线程数,就会导致巨大的 oversubscription。这个问题可以抛给 [std::async](https://en.cppreference.com/w/cpp/thread/async),它会适度处理并安排所有调用。这个问题也能用线程池解决 +* 随着处理器数量增加,另一个影响性能的问题也随之而来,即多处理器尝试访问同一数据 + +### 乒乓缓存(cache ping-pong) + +* 如果两个线程在不同处理器上并发执行,读取同一数据一般不会带来问题,数据将拷贝到它们的 cache,处理器可以同时处理。但如果一个线程修改数据,这个修改传给其他核的 cache 就需要花费时间,从而可能导致第二个处理器停止以等待改变传到内存硬件(取决于两个线程上的操作和这个操作使用的内存序)。从 CPU 指令的角度来看,这个操作慢到惊人,等价于数百个独立指令(具体取决于硬件的物理结构) + +```cpp +std::atomic n(0); + +void f() { // 任何线程都能调用 + // 每次n自增,处理器都要确保 cache 中的拷贝是最新的 + // 修改值后再告知其他处理器 + // fetch_add 是读改写操作,每次都要检索最新值 + // 如果另一线程在另一处理器运行此代码 + // n 的数据就要在两个处理器之间来回传递 + // 这样 n 增加时两个处理器的 cache 才能有最新值 + while (n.fetch_add(1, std::memory_order_relaxed) < 100000000) { + task(); // 如果很快完成或者有很多处理器运行此代码,处理器就要互相等待 + // 一个处理器在更新值,另一个更新值的处理器就要等待 + // 直到第一个更新完成并把改变传过来 + // 这种情况就是 high contention + // 反之处理器很少要互相等待的情况就是 low contention + // 在类似这样的循环中,n 的数据在 cache 之间来回传递 + // 这就是 cache ping-pong + } +} +``` + +* 如果处理器由于等待 cache 转移而挂起,就只能干等着而不能做任何工作。上例的情况可能不常见,但有一些和上例没有本质区别的常见情况,比如在循环中获取 mutex + +```cpp +std::mutex m; + +void f() { + while (true) { + std::lock_guard l(m); // 现在需要来回传递的是 m + if (done_processing(data)) { + break; + } + } +} +``` + +* 要避免乒乓缓存,就要尽量减少多个线程对同一内存位置的竞争。但即使一个特定内存位置只能被一个线程访问,仍然可能存在乒乓缓存,原因就是伪共享 + +### 伪共享(false sharing) + +* 处理器 cache 不是独立的,而是以 cache line 作为最小单位,一般为 32 或 64 字节,因此小数据可能位于同一 cache line。有时这是好事,如果一个线程访问的数据都位于同一 cache line,性能会比分散在多个 cache line 好。但如果 cache line 中的数据项不相关,需要被多个线程访问,就会导致性能问题 +* 假如有一个 int 数组,一组线程频繁访问和更新其中的数据。通常 int 大小不超过一个 cache line,因此一个 cache line 可以存储多个数据项,此时即使每个线程只访问自己需要的数据,cache 硬件也会造成乒乓缓存。比如访问 0 号数据的线程要更新数据,cache line 的所有权就要被转移到运行这个线程的处理器 +* 数据可能不共享,但 cache line 是共享的,这就是伪共享。这个问题的解决方案是,构造数据,让能被同一线程访问的数据项位于内存中的临近位置,让能被不同线程访问的数据在内存中相距很远。C++17 提供了 [std::hardware_destructive_interference_size](https://en.cppreference.com/w/cpp/thread/hardware_destructive_interference_size) 来指定当前编译目标伪共享的最大连续字节数,只要数据间隔大于此字节数就可以避免伪共享 + +### data proximity + +* 造成伪共享的原因是两个线程访问的数据过于接近,相应的,直接影响单线程的性能则是数据布局。如果单线程访问的数据分散在内存中,就类似位于不同的 cache line,如果在内存中十分靠近,就类似位于同一 cache line。如果数据是分散的,就需要从内存加载更多的 cache line 到处理器 cache,这就会增加内存访问延迟并降低性能 +* 如果数据是分散的,一个包含当前线程数据的 cache line 很可能会包含非当前线程的数据,极端情况下,cache 中将包含很多不需要的数据,这就会浪费宝贵的 cache 空间并增加处理器 cache miss 的概率,导致必须从主存获取数据。而这个数据可能曾在 cache 中保留过,但为了给其他数据让出空间必须从 cache 中移除 +* 这看上去只对单线程很重要,但其实对多线程也很重要,原因在于任务切换(task switching)。如果线程数超过核数,就一定会有核要运行多线程,这就增加了 cache 的压力,因为为了避免伪共享必须确保不同的线程访问不同的 cache line,当处理器切换线程时,如果数据分散,很可能会重新载入 cache line。C++17 提供了 [std::hardware_constructive_interference_size](https://en.cppreference.com/w/cpp/thread/hardware_destructive_interference_size) 来指定保证同一 cache line 的最大连续字节数,如果数据尺寸小于此字节数就能降低 cache miss 的几率 +* 如果线程数超过处理器核数,操作系统可能会调度线程,在某个时间片上给一个核,在下一个时间片上给另一个核,这就要求把第一个核的 cache 传给第二个,从而增加了时间开销。虽然操作系统一般会尽量避免这点,但如果发生了就会对性能造成影响 +* 当大量线程准备运行而非等待时,就会经常出现任务切换问题,这种处理器在任务切换上花费大量时间的情况就是 oversubscription + +### oversubscription + +* 线程经常花费时间来等待额外的 I/O、mutex 阻塞、条件变量,因此使用超过处理器核数的线程以确保没有闲置的处理器是合理的。但如果有过多的额外线程,操作系统确保为每个线程公平分配时间片,就会有沉重的任务切换负担。当一个任务重复而无限制地生成新线程,就会导致 oversubscription +* 如果生成的线程数过多的原因是数据划分,可以限制工作线程的数量。如果 oversubscription 是因为自然的工作划分,除了选择其他的划分方式,没有什么直接改善的办法。但选择合适的划分需要对目标平台有更多的了解,只有性能不可接受,而改变划分方式可以明显提高性能时才值得这样做 +* 影响多线程代码性能的因素非常多,以上只是一些有明显可见影响的主要因素,比如乒乓缓存的开销在两个单核处理器和一个双核处理器上区别很大,即使两者有相同的 CPU 类型和时钟速度 + +## 适用多线程性能的数据结构 + +* 如果有两个上千行列的矩阵相乘,现在要用多线程来优化计算。一般非稀疏矩阵可以用一个大的一维数组表示,矩阵的每行在数组中连续排列。这个计算需要三个数组,其中一个存储计算结果。为了优化性能,就要仔细考虑数据访问模式,尤其是向结果数组的写入 +* 划分方式有很多,如果行列数超过处理器数,每个线程可以计算结果的某些行或列,或者一个子矩阵 +* 访问相邻元素可以减少对 cache 的使用,以及降低伪共享的概率。如果让线程计算结果的某列,就需要依次访问左矩阵的行(最终读取整个左矩阵),并读取右矩阵某列。矩阵保存于一维数组,行是相邻的,但列不是,因此写入结果时,其他线程可能访问同一行的其他元素。为了避免伪共享,需要让每行元素所占的空间正好是 cache line 的数量 +* 如果让线程计算结果的某行,就需要读取左矩阵的某行,并依次读取右矩阵的列(最终读取整个右矩阵)。此时线程按行写入结果,由于一维数组里矩阵行是连续存储的,这个连续内存块不用被其他线程访问,比起上面按列写入结果是一个改进,伪共享只可能发生于一个结果块的最后几个元素与下一个块的前几个元素 +* 如果划分为子矩阵,可以看成先按列划分再按行划分,因此它和按列划分一样存在伪共享的可能。如果可以避免这个可能,这个划分就有一个明显的好处,即不需要读取整个源矩阵,因此计算子矩阵比计算行好一些。当然,如果性能非常重要,必须针对目标架构 profile 各种选项并检索相关领域的文献 +* 对于其他数据结构的数据访问模式进行优化时,需要考虑的本质上与优化对数组的访问类似 + * 调整线程间的数据分布,让同一线程访问的数据尽量紧密 + * 尽量减少线程所需的数据量 + * 依据 [std::hardware_destructive_interference_size](https://en.cppreference.com/w/cpp/thread/hardware_destructive_interference_size),确保不同线程访问的数据距离足够远,以避免伪共享 +* 这些用在其他数据结构上并不容易,比如二叉树很难在子树以外的任何单元中再分割,并且二叉树的节点一般是动态分配的,从而会分布在堆的不同位置上。数据位于堆的不同位置不是什么特别的问题,但确实意味着处理器需要在 cache 中保存更多东西。不过这是有益的,如果多个线程要遍历树,就都需要访问树节点,如果树节点只包含保存数据的指针,处理器只要在需要时从内存加载数据,如果数据被需要它的线程修改了,这能避免节点数据本身和提供树结构的数据之间的伪共享带来的性能问题 +* 用 mutex 保护数据也有类似问题。假如有一个类,它包含一个 mutex 和一些被保护的数据,如果 mutex 和数据在内存中很接近,这对获取 mutex 的线程是很理想的,为了修改 mutex,需要的数据可能已经跟着加载在处理器 cache 中了。但这也有一个缺点,如果其他线程尝试获取 mutex,就会需要访问那块内存 +* 互斥锁的典型实现为,一个操作在 mutex 内存位置上以尝试获取 mutex 的读改写原子操作,如果 mutex 已锁定,就接着调用操作系统内核。这个读改写操作可能会导致,持有该 mutex 的线程的 cache 中保存的数据无效。这对于 mutex 不是问题,在 mutex 解锁之前线程不会接触 mutex,但如果 mutex 与数据共享同一 cache line,另一个线程尝试获取 mutex 时,持有 mutex 的线程就会受到性能影响 +* 一个测试这种伪共享是否会带来影响的方法是,在能被并发访问的数据之间添加巨大的填充块。比如用如下方式测试 mutex 竞争问题 + +```cpp +struct ProtectedData { + std::mutex m; + // 使用超过一个 cache line 字节数的填充即可 + char padding[std::hardware_destructive_interference_size]; + // 不支持 C++17 则可以 padding[65536]; + Data data_to_protect; +}; +``` + +* 用如下方式测试数组数据伪共享,如果性能提高了就说明伪共享影响了性能,并且可以保留填充或者用其他方式重排数据访问来消除伪共享 + +```cpp +struct Data { + data_item1 d1; + data_item2 d2; + char padding[std::hardware_destructive_interference_size]; +}; + +Data some_array[256]; +``` + +## 并发设计的其他注意事项 + +* 除了上述问题,设计并发代码时还需要考虑异常安全和可扩展性。如果代码不是异常安全的,就可能导致破坏不变量或 race condition,或由于一个操作抛出异常导致程序意外终止。可扩展性指的是,性能会随着处理器核数的提升而提升,如果处理器核数是之前的 100 倍,则最理想的情况下性能也应该之前的 100 倍 + +### 并发算法的异常安全 + +* 并行算法比串行算法更注重异常问题。在串行算法中,如果一个操作抛出异常,只需要保证吞下此异常以避免资源泄漏或破坏不变量,它可以愉快地允许异常传播给调用者处理。但在并行算法中,许多操作运行在不同的线程上,异常就不允许传播,因为它在错误的调用栈上。如果新线程上的函数存在异常,程序就会终止 +* 回顾以前提到的并行版本的 [std::accumulate](https://en.cppreference.com/w/cpp/algorithm/accumulate),它就是非异常安全的,代码可能抛出异常的位置如下 + +```cpp +#include +#include +#include +#include +#include + +template +struct accumulate_block { + void operator()(Iterator first, Iterator last, T& res) { + res = std::accumulate(first, last, res); // 可能抛异常 + } +}; + +template +T parallel_accumulate(Iterator first, Iterator last, T init) { + std::size_t len = std::distance(first, last); // 此时没做任何事,抛异常无影响 + if (!len) { + return init; + } + std::size_t min_per_thread = 25; + std::size_t max_threads = (len + min_per_thread - 1) / min_per_thread; + std::size_t hardware_threads = std::thread::hardware_concurrency(); + std::size_t num_threads = + std::min(hardware_threads != 0 ? hardware_threads : 2, max_threads); + std::size_t block_size = len / num_threads; + std::vector res(num_threads); // 仍未做任何事,抛异常无影响 + std::vector threads(num_threads - 1); // 同上 + Iterator block_start = first; // 同上 + for (std::size_t i = 0; i < num_threads - 1; ++i) { + Iterator block_end = block_start; // 同上 + std::advance(block_end, block_size); + // 下面创建 std::thread,抛异常就导致析构对象,并调用 std::terminate + // 终止程序 + threads[i] = std::thread(accumulate_block{}, block_start, + block_end, std::ref(res[i])); + block_start = block_end; + } + // accumulate_block::operator() 调用的 std::accumulate + // 可能抛异常,此时抛异常造成问题同上 + accumulate_block()(block_start, last, res[num_threads - 1]); + std::for_each(threads.begin(), threads.end(), + std::mem_fn(&std::thread::join)); + // 最后调用 std::accumulate 可能抛异常,但不引发大问题,因为所有线程已 join + return std::accumulate(res.begin(), res.end(), init); +} +``` + +* 上面已经分析了所有可能抛出异常的位置,下面来处理这些问题。新线程想做的是返回计算结果,但可能抛出异常导致 [std::thread](https://en.cppreference.com/w/cpp/thread/thread) 析构,而析构没被 join 的 [std::thread](https://en.cppreference.com/w/cpp/thread/thread) 将导致程序终止。解决这个问题很简单,结合使用 [std::packaged_task](https://en.cppreference.com/w/cpp/thread/packaged_task) 和 [std::future](https://en.cppreference.com/w/cpp/thread/future),再把工作线程的异常抛出到主线程,让主线程处理即可 + +```cpp +#include +#include +#include +#include +#include +#include + +template +struct accumulate_block { + T operator()(Iterator first, Iterator last) { + return std::accumulate(first, last, T{}); + } +}; + +template +T parallel_accumulate(Iterator first, Iterator last, T init) { + std::size_t len = std::distance(first, last); + if (!len) { + return init; + } + std::size_t min_per_thread = 25; + std::size_t max_threads = (len + min_per_thread - 1) / min_per_thread; + std::size_t hardware_threads = std::thread::hardware_concurrency(); + std::size_t num_threads = + std::min(hardware_threads != 0 ? hardware_threads : 2, max_threads); + std::size_t block_size = len / num_threads; + std::vector> fts(num_threads - 1); // 改用 std::future 获取值 + std::vector threads(num_threads - 1); + Iterator block_start = first; + for (std::size_t i = 0; i < num_threads - 1; ++i) { + Iterator block_end = block_start; + std::advance(block_end, block_size); + // 用 std::packaged_task 替代直接创建 std::thread + std::packaged_task pt( + accumulate_block{}); + fts[i] = pt.get_future(); + threads[i] = std::thread(std::move(pt), block_start, block_end); + block_start = block_end; + } + T last_res = accumulate_block{}(block_start, last); + std::for_each(threads.begin(), threads.end(), + std::mem_fn(&std::thread::join)); + T res = init; + try { + for (std::size_t i = 0; i < num_threads - 1; ++i) { + res += fts[i].get(); + } + res += last_res; + } catch (...) { + for (auto& x : threads) { + if (x.joinable()) { + x.join(); + } + } + throw; + } + return res; +} +``` + +* 不过 try-catch 很难看,并且导致了重复代码(正常控制流和 catch 块都对线程执行 join),因此可以用 RAII 来处理 + +```cpp +#include +#include +#include +#include +#include +#include + +class threads_guard { + public: + explicit threads_guard(std::vector& threads) + : threads_(threads) {} + + ~threads_guard() { + for (auto& x : threads_) { + if (x.joinable()) { + x.join(); + } + } + } + + private: + std::vector& threads_; +}; + +template +struct accumulate_block { + T operator()(Iterator first, Iterator last) { + return std::accumulate(first, last, T{}); + } +}; + +template +T parallel_accumulate(Iterator first, Iterator last, T init) { + std::size_t len = std::distance(first, last); + if (!len) { + return init; + } + std::size_t min_per_thread = 25; + std::size_t max_threads = (len + min_per_thread - 1) / min_per_thread; + std::size_t hardware_threads = std::thread::hardware_concurrency(); + std::size_t num_threads = + std::min(hardware_threads != 0 ? hardware_threads : 2, max_threads); + std::size_t block_size = len / num_threads; + std::vector> fts(num_threads - 1); + std::vector threads(num_threads - 1); + threads_guard g{threads}; // threads 元素析构时自动 join + Iterator block_start = first; + for (std::size_t i = 0; i < num_threads - 1; ++i) { + Iterator block_end = block_start; + std::advance(block_end, block_size); + std::packaged_task pt( + accumulate_block{}); + fts[i] = pt.get_future(); + threads[i] = std::thread(std::move(pt), block_start, block_end); + block_start = block_end; + } + T last_res = accumulate_block{}(block_start, last); + std::for_each(threads.begin(), threads.end(), + std::mem_fn(&std::thread::join)); + T res = init; + for (std::size_t i = 0; i < num_threads - 1; ++i) { + res += fts[i].get(); + } + res += last_res; + return res; +} +``` + +* C++20 引入了能自动析构的 [std::jthread](https://en.cppreference.com/w/cpp/thread/jthread) + +```cpp +#include +#include +#include +#include +#include +#include + +template +struct accumulate_block { + T operator()(Iterator first, Iterator last) { + return std::accumulate(first, last, T{}); + } +}; + +template +T parallel_accumulate(Iterator first, Iterator last, T init) { + std::size_t len = std::distance(first, last); + if (!len) { + return init; + } + std::size_t min_per_thread = 25; + std::size_t max_threads = (len + min_per_thread - 1) / min_per_thread; + std::size_t hardware_threads = std::thread::hardware_concurrency(); + std::size_t num_threads = + std::min(hardware_threads != 0 ? hardware_threads : 2, max_threads); + std::size_t block_size = len / num_threads; + std::vector> fts(num_threads - 1); + std::vector threads(num_threads - 1); + Iterator block_start = first; + for (std::size_t i = 0; i < num_threads - 1; ++i) { + Iterator block_end = block_start; + std::advance(block_end, block_size); + std::packaged_task pt( + accumulate_block{}); + fts[i] = pt.get_future(); + threads[i] = std::jthread(std::move(pt), block_start, block_end); + block_start = block_end; + } + T last_res = accumulate_block{}(block_start, last); + std::for_each(threads.begin(), threads.end(), + std::mem_fn(&std::jthread::join)); + T res = init; + for (std::size_t i = 0; i < num_threads - 1; ++i) { + res += fts[i].get(); + } + res += last_res; + return res; +} +``` + +* 更优雅的方式是使用 [std::async](https://en.cppreference.com/w/cpp/thread/async) + +```cpp +#include +#include + +template +T parallel_accumulate(Iterator first, Iterator last, T init) { + std::size_t len = std::distance(first, last); + std::size_t max_chunk_size = 25; + if (len <= max_chunk_size) { + return std::accumulate(first, last, init); + } + Iterator mid_point = first; + std::advance(mid_point, len / 2); + std::future l = + std::async(parallel_accumulate, first, mid_point, init); + // 递归调用如果抛出异常,std::async 创建的 std::future 将在异常传播时被析构 + T r = parallel_accumulate(mid_point, last, T{}); + // 如果异步任务抛出异常,get 就会捕获异常并重新抛出 + return l.get() + r; +} +``` + +### 可扩展性与阿姆达尔定律(Amdahl’s law) + +* 可扩展性代表了程序对处理器的利用率。单线程程序就是不可扩展的,因为处理器增加完全不能提高单线程程序的性能。对于多线程程序,线程经常需要花费时间等待(等待其他线程、获取 mutex、修改条件变量、完成 I/O 操作......),一种简化看待多线程程序的方式是将其分为串行和并行部分,由此可以得到如下公式,即阿姆达尔定律 + +```cpp +S = 1 / (a + ( 1 - a ) / N) // a 为串行部分占比,N 为处理器倍数,S 为性能倍数 +// 正常情况下 S < 1 /a,最理想的情况是 a 为 0,S = N +``` + +### 用多线程隐藏延迟(lantency) + +* 如果在线程等待期间让系统做一些有用的事,就相当于隐藏了等待。如果只有和处理器单元一样多的线程,阻塞就意味着浪费 CPU 时间,因此可以利用这个时间去运行额外的线程。比如一个用 pipeline 划分工作的病毒扫描程序,一个线程检索文件系统并将文件放入队列,这是一个费时的 I/O 操作,因此同时可以让另一线程从队列获取文件名,加载并扫描文件 +* 利用空闲的 CPU 时间也可能不需要运行额外的线程。比如,如果一个线程因为等待 I/O 操作而阻塞,使用异步 I/O 就是合理的,当 I/O 操作异步运行在后台时,线程就能做有用的工作。又比如,一个线程等待另一线程执行一个操作时,与其阻塞,不如自己执行操作(如 lock-free queue)。更极端的例子是,如果线程等待一个未被任何线程启动的任务完成,这个线程可能自己执行此任务,或执行另一个未完成的任务 + +### 用并发提高响应度(responsiveness) + +* 添加线程不一定是为了确保使用所有可用的处理器,有时是为了确保及时处理外部事件,以提高系统响应度。现代 GUI 框架大多是事件驱动的,为了确保处理所有事件和消息,GUI 程序一般包含一个如下循环 + +```cpp +while (true) { + event_data event = get_event(); + if (event.type == quit) { + break; + } + process(event); +} +``` + +* 如果是单线程程序,就很难编写长期运行的任务。为了确保即使响应用户输入,就要以合理频率调用 get_event 和 process,这意味着任务要被周期性悬挂(suspend)并把控制流返回给事件循环,或者在代码中的一个适当点调用 get_event 和 process,二者任一都会复杂化任务实现 +* 通过 SoC(separation of concerns)可以把很长的任务放在一个全新的线程上,而让 GUI 线程来处理事件,线程可以通过简单的机制进行通信,而不需要混入处理事件的代码,这样即使任务耗费很长时间,用户线程也总能及时响应事件 + +```cpp +std::thread task_thread; +std::atomic task_cancelled(false); + +void gui_thread() { + while (true) { + event_data event = get_event(); + if (event.type == quit) { + break; + } + process(event); + } +} + +void task() { + while (!task_complete() && !task_cancelled) do_next_operation(); + if (task_cancelled) { + perform_cleanup(); + } else { + post_gui_event(task_complete); + } +} + +void process(const event_data& event) { + switch (event.type) { + case start_task: + task_cancelled = false; + task_thread = std::thread(task); + break; + case stop_task: + task_cancelled = true; + task_thread.join(); + break; + case task_complete: + task_thread.join(); + display_results(); + break; + default: + ... + } +} +``` + +## 实践 + +* 下面为标准库的三个算法实现并行版本,这些实现仅是为了阐述技术的运用,而不是最先进高效的实现。更先进的实现可以在学术文献或专业的多线程库(如 [Intel 的 Threading Building Blocks](https://github.com/intel/tbb)) 中找到 + +### 并行版 [std::for_each](https://en.cppreference.com/w/cpp/algorithm/for_each) + +* [std::for_each](https://en.cppreference.com/w/cpp/algorithm/for_each) 会按顺序依次作用于每个元素,而并行版不保证顺序,元素最好被并发处理,为此需要把元素划分给每个线程。实际上,并行版 [std::for_each](https://en.cppreference.com/w/cpp/algorithm/for_each) 与并行版 [std::accumulate](https://en.cppreference.com/w/cpp/algorithm/accumulate) 的实现思路基本一样:使用 [hardware_concurrency](https://en.cppreference.com/w/cpp/thread/thread/hardware_concurrency) 决定线程数,使用连续数据块避免伪共享,使用 [std::packaged_task](https://en.cppreference.com/w/cpp/thread/packaged_task) 和 [std::future](https://en.cppreference.com/w/cpp/thread/future) 在线程间传递异常 + +```cpp +#include +#include +#include +#include + +template +void parallel_for_each(Iterator first, Iterator last, Func f) { + std::size_t len = std::distance(first, last); + if (!len) { + return; + } + std::size_t min_per_thread = 25; + std::size_t max_threads = (len + min_per_thread - 1) / min_per_thread; + std::size_t hardware_threads = std::thread::hardware_concurrency(); + std::size_t num_threads = + std::min(hardware_threads != 0 ? hardware_threads : 2, max_threads); + std::size_t block_size = len / num_threads; + std::vector> fts(num_threads - 1); + std::vector threads(num_threads - 1); + Iterator block_start = first; + for (std::size_t i = 0; i < num_threads - 1; ++i) { + Iterator block_end = block_start; + std::advance(block_end, block_size); + std::packaged_task pt( + [=] { std::for_each(block_start, block_end, f); }); + fts[i] = pt.get_future(); + threads[i] = std::jthread(std::move(pt)); + block_start = block_end; + } + std::for_each(block_start, last, f); + for (std::size_t i = 0; i < num_threads - 1; ++i) { + fts[i].get(); // 只是为了传递异常 + } +} +``` + +* 也可以使用 [std::async](https://en.cppreference.com/w/cpp/thread/async) 来简化实现 + +```cpp +#include +#include + +template +void parallel_for_each(Iterator first, Iterator last, Func f) { + std::size_t len = std::distance(first, last); + if (!len) { + return; + } + std::size_t min_per_thread = 25; + if (len < 2 * min_per_thread) { + std::for_each(first, last, f); + return; + } + const Iterator mid_point = first + len / 2; + std::future l = + std::async(¶llel_for_each, first, mid_point, f); + parallel_for_each(mid_point, last, f); + l.get(); +} +``` + +### 并行版 [std::find](https://en.cppreference.com/w/cpp/algorithm/find) + +* [std::find](https://en.cppreference.com/w/cpp/algorithm/find) 的不同之处在于,只要找到目标值就应该停止继续查找。在并行版本中,一个线程找到了值,不仅自身要停止继续查找,还应该通知其他线程停止,这点可以使用一个原子变量作为标记来实现 +* 有两种可选方式来返回值和传播异常,一是使用 [std::future](https://en.cppreference.com/w/cpp/thread/future) 数组和 [std::packaged_task](https://en.cppreference.com/w/cpp/thread/packaged_task) 将返回值和异常交给主线程处理,二是使用 [std::promise](https://en.cppreference.com/w/cpp/thread/promise) 直接设置最终结果。如果想在首个异常上终止(即使没有处理完所有元素)则使用 [std::promise](https://en.cppreference.com/w/cpp/thread/promise),如果想让其他线程继续搜索则使用 [std::packaged_task](https://en.cppreference.com/w/cpp/thread/packaged_task) 保存所有异常,并在没有找到目标值时重新抛出其中一个异常。这里选择使用行为更接近 [std::find](https://en.cppreference.com/w/cpp/algorithm/find) 的 [std::promise](https://en.cppreference.com/w/cpp/thread/promise) + +```cpp +#include +#include +#include +#include +#include +#include +#include + +template +Iterator parallel_find(Iterator first, Iterator last, T match) { + struct find_element { + void operator()(Iterator begin, Iterator end, T match, + std::promise* res, std::atomic* done_flag) { + try { + for (; begin != end && !done_flag->load(); ++begin) { + if (*begin == match) { + res->set_value(begin); + done_flag->store(true); + return; + } + } + } catch (...) { + try { + res->set_exception(std::current_exception()); + done_flag->store(true); + } catch (...) { + } + } + } + }; + + std::size_t len = std::distance(first, last); + if (!len) { + return last; + } + std::size_t min_per_thread = 25; + std::size_t max_threads = (len + min_per_thread - 1) / min_per_thread; + std::size_t hardware_threads = std::thread::hardware_concurrency(); + std::size_t num_threads = + std::min(hardware_threads != 0 ? hardware_threads : 2, max_threads); + std::size_t block_size = len / num_threads; + std::promise res; + std::atomic done_flag(false); + { + std::vector threads(num_threads - 1); + Iterator block_start = first; + for (auto& x : threads) { + Iterator block_end = block_start; + std::advance(block_end, block_size); + x = std::jthread(find_element{}, block_start, block_end, match, &res, + &done_flag); + block_start = block_end; + } + find_element{}(block_start, last, match, &res, &done_flag); + } + if (!done_flag.load()) { + return last; + } + return res.get_future().get(); +} +``` + +* 也可以使用 [std::async](https://en.cppreference.com/w/cpp/thread/async) 实现 + +```cpp +#include +#include + +template +Iterator parallel_find_impl(Iterator first, Iterator last, T match, + std::atomic& done_flag) { + try { + std::size_t len = std::distance(first, last); + std::size_t min_per_thread = 25; + if (len < (2 * min_per_thread)) { + for (; first != last && !done_flag.load(); ++first) { + if (*first == match) { + done_flag = true; + return first; + } + } + return last; + } + const Iterator mid_point = first + len / 2; + std::future async_res = + std::async(¶llel_find_impl, mid_point, last, match, + std::ref(done_flag)); + const Iterator direct_res = + parallel_find_impl(first, mid_point, match, done_flag); + return direct_res == mid_point ? async_res.get() : direct_res; + } catch (...) { + done_flag = true; + throw; + } +} + +template +Iterator parallel_find(Iterator first, Iterator last, T match) { + std::atomic done_flag(false); + return parallel_find_impl(first, last, match, done_flag); +} +``` + +### 并行版 [std::partial_sum](https://en.cppreference.com/w/cpp/algorithm/partial_sum) + +* [std::partial_sum](https://en.cppreference.com/w/cpp/algorithm/partial_sum) 会依次累加元素的和(默认是加,也可以是其他二元操作) + +```cpp +#include +#include + +int main() { + std::vector v{1, 2, 3, 4}; + std::partial_sum( + v.begin(), v.end(), + std::ostream_iterator(std::cout << "hi"), // 输出到的迭代器起始位置 + std::plus{}); // 使用的二元运算符,不指定则默认累加 +} // 输出 hi13610 +``` + +* 其实现为 + +```cpp +template +OutputIt partial_sum(InputIt first, InputIt last, OutputIt d_first, + BinaryOperation op) { + if (first == last) { + return d_first; + } + typename std::iterator_traits::value_type sum = *first; + *d_first = sum; + while (++first != last) { + sum = op(std::move(sum), *first); + *++d_first = sum; + } + return ++d_first; +} +``` + +* 实现并行版本时,第一种划分方式就是传统的按块划分 + +```cpp +1 1 1 1 1 1 1 1 1 // 输入 9 个 1 +// 划分为三部分 +1 1 1 +1 1 1 +1 1 1 +// 得到三个部分的结果 +1 2 3 +1 2 3 +1 2 3 +// 将第一部分的尾元素(即 3)加到第二部分 +1 2 3 +4 5 6 +1 2 3 +// 再将第二部分的尾元素(即 6)加到第三部分 +1 2 3 +4 5 6 +7 8 9 +``` + +* 由于需要线程间同步,这个实现不容易简单地用 [std::async](https://en.cppreference.com/w/cpp/thread/async) 重写 + +```cpp +#include +#include +#include + +template +void parallel_partial_sum(Iterator first, Iterator last) { + using value_type = typename Iterator::value_type; + struct process_chunk { + void operator()(Iterator begin, Iterator last, + std::future* previous_end_value, + std::promise* end_value) { + try { + Iterator end = last; + ++end; + std::partial_sum(begin, end, begin); + if (previous_end_value) { // 不是第一个块 + value_type addend = previous_end_value->get(); + *last += addend; + if (end_value) { + end_value->set_value(*last); + } + std::for_each(begin, last, + [addend](value_type& item) { item += addend; }); + } else if (end_value) { + end_value->set_value(*last); // 是第一个块则可以为下个块更新尾元素 + } + } catch (...) { + // 如果抛出异常则存储到 + // std::promise,异常会传播给下一个块(获取这个块的尾元素时) + if (end_value) { + end_value->set_exception(std::current_exception()); + } else { + throw; // 异常最终传给最后一个块,此时再抛出异常 + } + } + } + }; + + std::size_t len = std::distance(first, last); + if (!len) { + return; + } + std::size_t min_per_thread = 25; + std::size_t max_threads = (len + min_per_thread - 1) / min_per_thread; + std::size_t hardware_threads = std::thread::hardware_concurrency(); + std::size_t num_threads = + std::min(hardware_threads != 0 ? hardware_threads : 2, max_threads); + std::size_t block_size = len / num_threads; + // end_values 存储块内尾元素值 + std::vector> end_values(num_threads - 1); + // prev_end_values 检索前一个块的尾元素 + std::vector> prev_end_values; + prev_end_values.reserve(num_threads - 1); + Iterator block_start = first; + std::vector threads(num_threads - 1); + for (std::size_t i = 0; i < num_threads - 1; ++i) { + Iterator block_last = block_start; + std::advance(block_last, block_size - 1); // 指向尾元素 + threads[i] = std::jthread(process_chunk{}, block_start, block_last, + i != 0 ? &prev_end_values[i - 1] : nullptr, + &end_values[i]); + block_start = block_last; + ++block_start; + prev_end_values.emplace_back(end_values[i].get_future()); + } + Iterator final_element = block_start; + std::advance(final_element, std::distance(block_start, last) - 1); + process_chunk{}(block_start, final_element, + num_threads > 1 ? &prev_end_values.back() : nullptr, nullptr); +} +``` + +* 如果处理器核数非常多,就没必要使用上面的方式了,因为还有并发度更高的方式,即隔一定距离计算,每轮计算完成,下一轮计算使用的距离变为之前的两倍。这种方式不再需要进一步同步,因为所有中间的结果都直接传给了下一个需要这些结果的处理器,但实际上很少有处理器可以在多条数据上同时执行同一条指令(即 SIMD),因此必须为通用情况设计代码,在每步操作上显式同步线程,比如使用 barrier 的同步机制,直到所有线程到达 barrier 时才能继续执行下一步 + +```cpp +1 1 1 1 1 1 1 1 1 // 输入 9 个 1 +// 先让距离为 1 的元素相加 +1 2 2 2 2 2 2 2 2 +// 再让距离为 2 的元素相加 +1 2 3 4 4 4 4 4 4 +// 再让距离为 4 的元素相加 +1 2 3 4 5 6 7 8 8 +// 再让距离为 8 的元素相加 +1 2 3 4 5 6 7 8 9 +``` diff --git a/docs/08_advanced_thread_management.md b/docs/08_advanced_thread_management.md new file mode 100644 index 0000000..6336d8a --- /dev/null +++ b/docs/08_advanced_thread_management.md @@ -0,0 +1,743 @@ +## 线程池 + +* 线程池一般会用一个表示线程数的参数来初始化,内部需要一个队列来存储任务。下面是一个最简单的线程池实现 + +```cpp +#include +#include +#include +#include +#include +#include + +class ThreadPool { + public: + explicit ThreadPool(std::size_t n) { + for (std::size_t i = 0; i < n; ++i) { + std::thread{[this] { + std::unique_lock l(m_); + while (true) { + if (!q_.empty()) { + auto task = std::move(q_.front()); + q_.pop(); + l.unlock(); + task(); + l.lock(); + } else if (done_) { + break; + } else { + cv_.wait(l); + } + } + }}.detach(); + } + } + + ~ThreadPool() { + { + std::lock_guard l(m_); + done_ = true; // cv_.wait 使用了 done_ 判断所以要加锁 + } + cv_.notify_all(); + } + + template + void submit(F&& f) { + { + std::lock_guard l(m_); + q_.emplace(std::forward(f)); + } + cv_.notify_one(); + } + + private: + std::mutex m_; + std::condition_variable cv_; + bool done_ = false; + std::queue> q_; +}; +``` + +* 如果想让提交的任务带参数会麻烦很多 + +```cpp +template +auto ThreadPool::submit(F&& f, Args&&... args) { + using RT = std::invoke_result_t; + // std::packaged_task 不允许拷贝构造,不能直接传入 lambda, + // 因此要借助 std::shared_ptr + auto task = std::make_shared>( + std::bind(std::forward(f), std::forward(args)...)); + // 但 std::bind 会按值拷贝实参,因此这个实现不允许任务的实参是 move-only 类型 + { + std::lock_guard l(m_); + q_.emplace([task]() { (*task)(); }); // 捕获指针以传入 std::packaged_task + } + cv_.notify_one(); + return task->get_future(); +} +``` + +* 书上实现的线程池都在死循环中使用了 [std::this_thread::yield](https://en.cppreference.com/w/cpp/thread/yield) 来转让时间片 + +```cpp +#include +#include +#include +#include + +#include "concurrent_queue.hpp" + +class ThreadPool { + public: + ThreadPool() { + std::size_t n = std::thread::hardware_concurrency(); + try { + for (std::size_t i = 0; i < n; ++i) { + threads_.emplace_back(&ThreadPool::worker_thread, this); + } + } catch (...) { + done_ = true; + for (auto& x : threads_) { + if (x.joinable()) { + x.join(); + } + } + throw; + } + } + + ~ThreadPool() { + done_ = true; + for (auto& x : threads_) { + if (x.joinable()) { + x.join(); + } + } + } + + template + void submit(F f) { + q_.push(std::function(f)); + } + + private: + void worker_thread() { + while (!done_) { + std::function task; + if (q_.try_pop(task)) { + task(); + } else { + std::this_thread::yield(); + } + } + } + + private: + std::atomic done_ = false; + ConcurrentQueue> q_; + std::vector threads_; // 要在 done_ 和 q_ 之后声明 +}; +``` + +* 这样做的问题是,如果线程池处于空闲状态,就会无限转让时间片,导致 CPU 使用率达 100%,下面是对书中的线程池的 CPU 使用率测试结果 + +![](images/8-1.png) + +* 对相同任务用之前实现的线程池的测试结果 + +![](images/8-2.png) + +* 这里还是把书上的内容列出来,下文均为书中内容 +* 这个线程池只能执行无参数无返回值的函数,并且可能出现死锁,下面希望能执行无参数但有返回值的函数。为了得到返回值,就应该把函数传递给 [std::packaged_task](https://en.cppreference.com/w/cpp/thread/packaged_task) 再加入队列,并返回 [std::packaged_task](https://en.cppreference.com/w/cpp/thread/packaged_task) 中的 [std::future](https://en.cppreference.com/w/cpp/thread/future)。由于 [std::packaged_task](https://en.cppreference.com/w/cpp/thread/packaged_task) 是 move-only 类型,而 [std::function](https://en.cppreference.com/w/cpp/utility/functional/function) 要求存储的函数实例可以拷贝构造,因此这里需要实现一个支持 move-only 类型的函数包裹类,即一个带 call 操作的类型擦除(type-erasure)类 + +```cpp +#include +#include + +class FunctionWrapper { + public: + FunctionWrapper() = default; + + FunctionWrapper(const FunctionWrapper&) = delete; + + FunctionWrapper& operator=(const FunctionWrapper&) = delete; + + FunctionWrapper(FunctionWrapper&& rhs) noexcept + : impl_(std::move(rhs.impl_)) {} + + FunctionWrapper& operator=(FunctionWrapper&& rhs) noexcept { + impl_ = std::move(rhs.impl_); + return *this; + } + + template + FunctionWrapper(F&& f) : impl_(new ImplType(std::move(f))) {} + + void operator()() const { impl_->call(); } + + private: + struct ImplBase { + virtual void call() = 0; + virtual ~ImplBase() = default; + }; + + template + struct ImplType : ImplBase { + ImplType(F&& f) noexcept : f_(std::move(f)) {} + void call() override { f_(); } + + F f_; + }; + + private: + std::unique_ptr impl_; +}; +``` + +* 用这个包裹类替代 `std::function` + +```cpp +#include +#include +#include +#include +#include + +#include "concurrent_queue.hpp" +#include "function_wrapper.hpp" + +class ThreadPool { + public: + ThreadPool() { + std::size_t n = std::thread::hardware_concurrency(); + try { + for (std::size_t i = 0; i < n; ++i) { + threads_.emplace_back(&ThreadPool::worker_thread, this); + } + } catch (...) { + done_ = true; + for (auto& x : threads_) { + if (x.joinable()) { + x.join(); + } + } + throw; + } + } + + ~ThreadPool() { + done_ = true; + for (auto& x : threads_) { + if (x.joinable()) { + x.join(); + } + } + } + + template + std::future> submit(F f) { + std::packaged_task()> task(std::move(f)); + std::future> res(task.get_future()); + q_.push(std::move(task)); + return res; + } + + private: + void worker_thread() { + while (!done_) { + FunctionWrapper task; + if (q_.try_pop(task)) { + task(); + } else { + std::this_thread::yield(); + } + } + } + + private: + std::atomic done_ = false; + ConcurrentQueue q_; + std::vector threads_; // 要在 done_ 和 q_ 之后声明 +}; +``` + +* 往线程池添加任务会增加任务队列的竞争,lock-free 队列可以避免这点但存在乒乓缓存的问题。为此需要把任务队列拆分为线程独立的本地队列和全局队列,当线程队列无任务时就去全局队列取任务 + +```cpp +#include +#include +#include +#include +#include +#include +#include + +#include "concurrent_queue.hpp" +#include "function_wrapper.hpp" + +class ThreadPool { + public: + ThreadPool() { + std::size_t n = std::thread::hardware_concurrency(); + try { + for (std::size_t i = 0; i < n; ++i) { + threads_.emplace_back(&ThreadPool::worker_thread, this); + } + } catch (...) { + done_ = true; + for (auto& x : threads_) { + if (x.joinable()) { + x.join(); + } + } + throw; + } + } + + ~ThreadPool() { + done_ = true; + for (auto& x : threads_) { + if (x.joinable()) { + x.join(); + } + } + } + + template + std::future> submit(F f) { + std::packaged_task()> task(std::move(f)); + std::future> res(task.get_future()); + if (local_queue_) { + local_queue_->push(std::move(task)); + } else { + pool_queue_.push(std::move(task)); + } + return res; + } + + private: + void worker_thread() { + local_queue_.reset(new std::queue); + while (!done_) { + FunctionWrapper task; + if (local_queue_ && !local_queue_->empty()) { + task = std::move(local_queue_->front()); + local_queue_->pop(); + task(); + } else if (pool_queue_.try_pop(task)) { + task(); + } else { + std::this_thread::yield(); + } + } + } + + private: + std::atomic done_ = false; + ConcurrentQueue pool_queue_; + inline static thread_local std::unique_ptr> + local_queue_; + std::vector threads_; +}; +``` + +* 这可以避免数据竞争,但如果任务分配不均,就会导致某个线程的本地队列中有很多任务,而其他线程无事可做,为此应该让没有工作的线程可以从其他线程获取任务 + +```cpp +#include +#include +#include +#include +#include +#include +#include +#include + +#include "concurrent_queue.hpp" +#include "function_wrapper.hpp" + +class WorkStealingQueue { + public: + WorkStealingQueue() = default; + + WorkStealingQueue(const WorkStealingQueue&) = delete; + + WorkStealingQueue& operator=(const WorkStealingQueue&) = delete; + + void push(FunctionWrapper f) { + std::lock_guard l(m_); + q_.push_front(std::move(f)); + } + + bool empty() const { + std::lock_guard l(m_); + return q_.empty(); + } + + bool try_pop(FunctionWrapper& res) { + std::lock_guard l(m_); + if (q_.empty()) { + return false; + } + res = std::move(q_.front()); + q_.pop_front(); + return true; + } + + bool try_steal(FunctionWrapper& res) { + std::lock_guard l(m_); + if (q_.empty()) { + return false; + } + res = std::move(q_.back()); + q_.pop_back(); + return true; + } + + private: + std::deque q_; + mutable std::mutex m_; +}; + +class ThreadPool { + public: + ThreadPool() { + std::size_t n = std::thread::hardware_concurrency(); + try { + for (std::size_t i = 0; i < n; ++i) { + work_stealing_queue_.emplace_back( + std::make_unique()); + threads_.emplace_back(&ThreadPool::worker_thread, this, i); + } + } catch (...) { + done_ = true; + for (auto& x : threads_) { + if (x.joinable()) { + x.join(); + } + } + throw; + } + } + + ~ThreadPool() { + done_ = true; + for (auto& x : threads_) { + if (x.joinable()) { + x.join(); + } + } + } + + template + std::future> submit(F f) { + std::packaged_task()> task(std::move(f)); + std::future> res(task.get_future()); + if (local_queue_) { + local_queue_->push(std::move(task)); + } else { + pool_queue_.push(std::move(task)); + } + return res; + } + + private: + bool pop_task_from_local_queue(FunctionWrapper& task) { + return local_queue_ && local_queue_->try_pop(task); + } + + bool pop_task_from_pool_queue(FunctionWrapper& task) { + return pool_queue_.try_pop(task); + } + + bool pop_task_from_other_thread_queue(FunctionWrapper& task) { + for (std::size_t i = 0; i < work_stealing_queue_.size(); ++i) { + std::size_t index = (index_ + i + 1) % work_stealing_queue_.size(); + if (work_stealing_queue_[index]->try_steal(task)) { + return true; + } + } + return false; + } + + void worker_thread(std::size_t index) { + index_ = index; + local_queue_ = work_stealing_queue_[index_].get(); + while (!done_) { + FunctionWrapper task; + if (pop_task_from_local_queue(task) || pop_task_from_pool_queue(task) || + pop_task_from_other_thread_queue(task)) { + task(); + } else { + std::this_thread::yield(); + } + } + } + + private: + std::atomic done_ = false; + ConcurrentQueue pool_queue_; + std::vector> work_stealing_queue_; + std::vector threads_; + + static thread_local WorkStealingQueue* local_queue_; + static thread_local std::size_t index_; +}; + +thread_local WorkStealingQueue* ThreadPool::local_queue_; +thread_local std::size_t ThreadPool::index_; +``` + +## 中断 + +* 可中断线程的简单实现 + +```cpp +class InterruptFlag { + public: + void set(); + bool is_set() const; +}; + +thread_local InterruptFlag this_thread_interrupt_flag; + +class InterruptibleThread { + public: + template + InterruptibleThread(F f) { + std::promise p; + t = std::thread([f, &p] { + p.set_value(&this_thread_interrupt_flag); + f(); + }); + flag = p.get_future().get(); + } + + void interrupt() { + if (flag) { + flag->set(); + } + } + + private: + std::thread t; + InterruptFlag* flag; +}; + +void interruption_point() { + if (this_thread_interrupt_flag.is_set()) { + throw thread_interrupted(); + } +} +``` + +* 在函数中使用 + +```cpp +void f() { + while (!done) { + interruption_point(); + process_next_item(); + } +} +``` + +* 更好的方式是用 [std::condition_variable](https://en.cppreference.com/w/cpp/thread/condition_variable) 来唤醒,而非在循环中持续运行 + +```cpp +class InterruptFlag { + public: + void set() { + b_.store(true, std::memory_order_relaxed); + std::lock_guard l(m_); + if (cv_) { + cv_->notify_all(); + } + } + + bool is_set() const { return b_.load(std::memory_order_relaxed); } + + void set_condition_variable(std::condition_variable& cv) { + std::lock_guard l(m_); + cv_ = &cv; + } + + void clear_condition_variable() { + std::lock_guard l(m_); + cv_ = nullptr; + } + + struct ClearConditionVariableOnDestruct { + ~ClearConditionVariableOnDestruct() { + this_thread_interrupt_flag.clear_condition_variable(); + } + }; + + private: + std::atomic b_; + std::condition_variable* cv_ = nullptr; + std::mutex m_; +}; + +void interruptible_wait(std::condition_variable& cv, + std::unique_lock& l) { + interruption_point(); + this_thread_interrupt_flag.set_condition_variable(cv); + // 之后的 wait_for 可能抛异常,所以需要 RAII 清除标志 + InterruptFlag::ClearConditionVariableOnDestruct guard; + interruption_point(); + // 设置线程看到中断前的等待时间上限 + cv.wait_for(l, std::chrono::milliseconds(1)); + interruption_point(); +} + +template +void interruptible_wait(std::condition_variable& cv, + std::unique_lock& l, Predicate pred) { + interruption_point(); + this_thread_interrupt_flag.set_condition_variable(cv); + InterruptFlag::ClearConditionVariableOnDestruct guard; + while (!this_thread_interrupt_flag.is_set() && !pred()) { + cv.wait_for(l, std::chrono::milliseconds(1)); + } + interruption_point(); +} +``` + +* 和 [std::condition_variable](https://en.cppreference.com/w/cpp/thread/condition_variable) 不同的是,[std::condition_variable_any](https://en.cppreference.com/w/cpp/thread/condition_variable_any) 可以使用不限于 [std::unique_lock](https://en.cppreference.com/w/cpp/thread/unique_lock) 的任何类型的锁,这意味着可以使用自定义的锁类型 + +```cpp +#include +#include +#include + +class InterruptFlag { + public: + void set() { + b_.store(true, std::memory_order_relaxed); + std::lock_guard l(m_); + if (cv_) { + cv_->notify_all(); + } else if (cv_any_) { + cv_any_->notify_all(); + } + } + + template + void wait(std::condition_variable_any& cv, Lockable& l) { + class Mutex { + public: + Mutex(InterruptFlag* self, std::condition_variable_any& cv, Lockable& l) + : self_(self), lock_(l) { + self_->m_.lock(); + self_->cv_any_ = &cv; + } + + ~Mutex() { + self_->cv_any_ = nullptr; + self_->m_.unlock(); + } + + void lock() { std::lock(self_->m_, lock_); } + + void unlock() { + lock_.unlock(); + self_->m_.unlock(); + } + + private: + InterruptFlag* self_; + Lockable& lock_; + }; + + Mutex m(this, cv, l); + interruption_point(); + cv.wait(m); + interruption_point(); + } + // rest as before + + private: + std::atomic b_; + std::condition_variable* cv_ = nullptr; + std::condition_variable_any* cv_any_ = nullptr; + std::mutex m_; +}; + +template +void interruptible_wait(std::condition_variable_any& cv, Lockable& l) { + this_thread_interrupt_flag.wait(cv, l); +} +``` + +* 对于其他阻塞调用(比如 mutex、future)的中断,一般也可以像对 [std::condition_variable](https://en.cppreference.com/w/cpp/thread/condition_variable) 一样设置超时时间,因为不访问内部 mutex 或 future 无法在未满足等待的条件时中断等待 + +```cpp +template +void interruptible_wait(std::future& ft) { + while (!this_thread_interrupt_flag.is_set()) { + if (ft.wait_for(std::chrono::milliseconds(1)) == + std::future_status::ready) { + break; + } + } + interruption_point(); +} +``` + +* 从被中断的线程角度来看,中断就是一个 `thread_interrupted` 异常。因此检查出中断后,可以像异常一样对其进行处理 + +```cpp +internal_thread = std::thread{[f, &p] { + p.set_value(&this_thread_interrupt_flag); + try { + f(); + } catch (const thread_interrupted&) { + // 异常传入 std::thread 的析构函数时将调用 std::terminate + // 为了防止程序终止就要捕获异常 + } +}}; +``` + +* 假如有一个桌面搜索程序,除了与用户交互,程序还需要监控文件系统的状态,以识别任何更改并更新其索引。为了避免影响 GUI 的响应性,这个处理通常会交给一个后台线程,后台线程需要运行于程序的整个生命周期。这样的程序通常只在机器关闭时退出,而在其他情况下关闭程序,就需要井然有序地关闭后台线程,一个关闭方式就是中断 + +```cpp +std::mutex config_mutex; +std::vector background_threads; + +void background_thread(int disk_id) { + while (true) { + interruption_point(); + fs_change fsc = get_fs_changes(disk_id); + if (fsc.has_changes()) { + update_index(fsc); + } + } +} + +void start_background_processing() { + background_threads.emplace_back(background_thread, disk_1); + background_threads.emplace_back(background_thread, disk_2); +} + +int main() { + start_background_processing(); + process_gui_until_exit(); + std::unique_lock l(config_mutex); + for (auto& x : background_threads) { + x.interrupt(); + } + // 中断所有线程后再join + for (auto& x : background_threads) { + if (x.joinable()) { + x.join(); + } + } + // 不直接在一个循环里中断并 join 的目的是为了并发, + // 因为中断不会立即完成,它们必须进入下一个中断点, + // 再在退出前必要地调用析构和异常处理的代码, + // 如果对每个线程都中断后立即 join,就会造成中断线程的等待, + // 即使它还可以做一些有用的工作,比如中断其他线程 +} +``` diff --git a/docs/09_parallel_algorithm.md b/docs/09_parallel_algorithm.md new file mode 100644 index 0000000..0953deb --- /dev/null +++ b/docs/09_parallel_algorithm.md @@ -0,0 +1,244 @@ +## [执行策略(execution policy)](https://en.cppreference.com/w/cpp/algorithm/execution_policy_tag_t) + +* C++17 对标准库算法重载了并行版本,区别是多了一个指定执行策略的参数 + +```cpp +std::vector v; +std::sort(std::execution::par, v.begin(), v.end()); +``` + +* [std::execution::par](https://en.cppreference.com/w/cpp/algorithm/execution_policy_tag) 表示允许多线程并行执行此算法,注意这是一个权限(permission)而非强制要求(requirement),此算法依然可以被单线程执行 +* 另外,如果指定了执行策略,算法复杂度的要求也更宽松,因为并行算法为了利用好系统的并行性通常要做更多工作。比如把工作划分给 100 个处理器,即使总工作是原来的两倍,也仍然能获得原来的五十倍的性能 +* [\](https://en.cppreference.com/w/cpp/header/execution) 中指定了如下执行策略类 + +```cpp +std::execution::sequenced_policy +std::execution::parallel_policy +std::execution::parallel_unsequenced_policy +std::execution::unsequenced_policy // C++20 +``` + +* 并指定了对应的全局对象 + +```cpp +std::execution::seq +std::execution::par +std::execution::par_unseq +std::execution::unseq // C++20 +``` + +* 如果使用执行策略,算法的行为就会受执行策略影响,影响方面包括:算法复杂度、抛异常时的行为、算法步骤的执行位置(where)、方式(how)、时刻(when) +* 除了管理并行执行的调度开销,许多并行算法会执行更多的核心操作(交换、比较、使用函数对象等),这样可以减少总的实际消耗时间,从而全面提升性能。这就是算法复杂度受影响的原因,其具体改变因算法不同而异 +* 在不指定执行策略时,如下对算法的调用,抛出的异常会被传播 + +```cpp +std::for_each(v.begin(), v.end(), [](auto x) { throw my_exception(); }); +``` + +* 而指定执行策略时,如果算法执行期间抛出异常,则行为结果由执行策略决定。如果有任何未捕获的异常,执行策略将调用 [std::terminate](https://en.cppreference.com/w/cpp/error/terminate) 终止程序,唯一可能抛出异常的情况是,内部操作不能获取足够的内存资源时抛出 [std::bad_alloc](https://en.cppreference.com/w/cpp/memory/new/bad_alloc)。如下操作将调用 [std::terminate](https://en.cppreference.com/w/cpp/error/terminate) 终止程序 + +```cpp +std::for_each(std::execution::seq, v.begin(), v.end(), + [](auto x) { throw my_exception(); }); +``` + +* 不同的执行策略的执行方式也不相同。执行策略会指定执行算法步骤的代理,可以是常规线程、矢量流、GPU 线程或其他任何东西。执行策略也会指定算法步骤运行的顺序限制,比如是否要以特定顺序运行、不同算法步骤的一部分是否可以互相交错或并行运行等。下面对不同的执行策略进行详细解释 + +### [std::execution::sequenced_policy](https://en.cppreference.com/w/cpp/algorithm/execution_policy_tag_t) + +* [std::execution::sequenced_policy](https://en.cppreference.com/w/cpp/algorithm/execution_policy_tag_t) 策略要求可以不(may not)并行执行,所有操作将执行在一个线程上。但它也是执行策略,因此与其他执行策略一样会影响算法复杂度和异常行为 +* 所有执行在一个线程上的操作必须以某个确定顺序执行,因此这些操作是不能互相交错的。但不规定具体顺序,因此对于不同的函数调用可能产生不同的顺序 + +```cpp +std::vector v(1000); +int n = 0; +// 把 1-1000 存入容器,存入顺序可能是顺序也可能是乱序 +std::for_each(std::execution::seq, v.begin(), v.end(), + [&](int& x) { x = ++n; }); +``` + +* 因此 [std::execution::sequenced_policy](https://en.cppreference.com/w/cpp/algorithm/execution_policy_tag_t) 策略很少要求算法使用迭代器、值、可调用对象,它们可以自由地使用同步机制,可以依赖于同一线程上调用的操作,尽管不能依赖于这些操作的顺序 + +### [std::execution::parallel_policy](https://en.cppreference.com/w/cpp/algorithm/execution_policy_tag_t) + +* [std::execution::parallel_policy](https://en.cppreference.com/w/cpp/algorithm/execution_policy_tag_t) 策略提供了基本的跨多个线程的并行执行,操作可以执行在调用算法的线程上,或执行在由库创建的线程上,在一个给定线程上的操作必须以确定顺序执行,并且不能相互交错。同样这个顺序是未指定的,对于不同的调用可能会有不同的顺序。一个给定的操作将在一个固定的线程上运行完整个周期 +* 因此 [std::execution::parallel_policy](https://en.cppreference.com/w/cpp/algorithm/execution_policy_tag_t) 策略对于迭代器、值、可调用对象的使用就有一定要求,它们在并行调用时不能造成数据竞争,并且不能依赖于统一线程上的其他操作,或者说只能依赖于不运行在同一线程上的其他操作 +* 大多数情况都可以使用 [std::execution::parallel_policy](https://en.cppreference.com/w/cpp/algorithm/execution_policy_tag_t) 策略 + +```cpp +std::for_each(std::execution::par, v.begin(), v.end(), [](auto& x) { ++x; }); +``` + +* 只有在元素之间有特定顺序或对共享数据的访问不同步时,它才有问题 + +```cpp +std::vector v(1000); +int n = 0; +std::for_each(std::execution::par, v.begin(), v.end(), [&](int& x) { + x = ++n; +}); // 如果多个线程执行 lambda 就会对 n 产生数据竞争 +``` + +* 因此使用 [std::execution::parallel_policy](https://en.cppreference.com/w/cpp/algorithm/execution_policy_tag_t) 策略时,应该事先考虑可能出现的未定义行为。可以用 mutex 或原子变量来解决竞争问题,但这就影响了并发性。不过这个例子只是为了阐述此情况,一般使用 [std::execution::parallel_policy](https://en.cppreference.com/w/cpp/algorithm/execution_policy_tag_t) 策略时都是允许同步访问共享数据的 + +### [std::execution::parallel_unsequenced_policy](https://en.cppreference.com/w/cpp/algorithm/execution_policy_tag_t) + +* [std::execution::parallel_unsequenced_policy](https://en.cppreference.com/w/cpp/algorithm/execution_policy_tag_t) 策略提供了最大可能的并行化,代价是对算法使用的迭代器、值和可调用对象有最严格的的要求 +* 使用 [std::execution::parallel_unsequenced_policy](https://en.cppreference.com/w/cpp/algorithm/execution_policy_tag_t) 策略的算法允许以无序的方式在任意未指定的线程中执行,并且在每个线程中彼此不排序。也就是说,操作可以在单个线程上互相交错,同一线程上的第二个操作可以开始于第一个操作结束前,并且可以在线程间迁移,一个给定的操作可以开始于一个线程,运行于另一线程,而完成于第三个线程 +* 使用 [std::execution::parallel_unsequenced_policy](https://en.cppreference.com/w/cpp/algorithm/execution_policy_tag_t) 策略时,提供给算法的迭代器、值、可调用对象上的操作不能使用任何形式的同步,也不能调用与其他代码同步的任何函数。这意味着操作只能作用于相关元素,或任何基于这些元素的可访问数据,并且不能修改任何线程间或元素间的共享数据 + +## 标准库并行算法 + +* [\](https://en.cppreference.com/w/cpp/algorithm) 和 [\](https://en.cppreference.com/w/cpp/header/numeric) 中的大部分算法都重载了并行版本。[std::accumlate](https://en.cppreference.com/w/cpp/algorithm/accumulate) 没有并行版本,但 C++17 提供了 [std::reduce](https://en.cppreference.com/w/cpp/algorithm/reduce) + +```cpp +std::accumulate(v.begin(), v.end(), 0); +std::reduce(std::execution::par, v.begin(), v.end()); +``` + +* 如果常规算法有并行版的重载,则并行版对常规算法原有的所有重载都有一个对应重载版本 + +```cpp +template +void sort(RandomIt first, RandomIt last); + +template +void sort(RandomIt first, RandomIt last, Compare comp); + +// 并行版对应有两个重载 +template +void sort(ExecutionPolicy&& policy, RandomIt first, RandomIt last); + +template +void sort(ExecutionPolicy&& policy, RandomIt first, RandomIt last, + Compare comp); +``` + +* 但并行版的重载对部分算法有一些区别,如果常规版本使用的是输入迭代器(input iterator)或输出迭代器(output iterator),则并行版的重载将使用前向迭代器(forward iterator) + +```cpp +template +OutputIt copy(InputIt first, InputIt last, OutputIt d_first); + +template +ForwardIt2 copy(ExecutionPolicy&& policy, ForwardIt1 first, ForwardIt1 last, + ForwardIt2 d_first); +``` + +* 输入迭代器只能用来读取指向的值,迭代器自增后就再也无法访问之前指向的值,它一般用于从控制台或网络输入,或生成序列,比如 [std::istream_iterator](https://en.cppreference.com/w/cpp/iterator/istream_iterator)。同理,输出迭代器一般用来输出到文件,或添加值到容器,也是单向的,比如 [std::ostream_iterator](https://en.cppreference.com/w/cpp/iterator/ostream_iterator) +* 前向迭代器返回元素的引用,因此可以用于读写,它同样只能单向传递,[std::forward_list](https://en.cppreference.com/w/cpp/container/forward_list) 的迭代器就是前向迭代器,虽然它不可以回到之前指向的值,但可以存储一个指向之前元素的拷贝(比如 [std::forward_list::begin](https://en.cppreference.com/w/cpp/container/forward_list/begin))来重复利用。对于并行性来说,可以重复利用迭代器很重要。此外,前向迭代器的自增不会使其他的迭代器拷贝失效,这样就不用担心其他线程中的迭代器受影响。如果使用输入迭代器,所有线程只能共用一个迭代器,显然无法并行 +* [std::execution::par](https://en.cppreference.com/w/cpp/algorithm/execution_policy_tag) 是最常用的策略,除非实现提供了更符合需求的非标准策略。一些情况下也可以使用 [std::execution::par_unseq](https://en.cppreference.com/w/cpp/algorithm/execution_policy_tag),虽然这不保证更好的并发性,但它给了库通过重排和交错任务来提升性能的可能性,不过代价就是不能使用同步机制,要确保线程安全只能让算法本身不会让多个线程访问同一元素,并在调用该算法的外部使用同步机制来避免其他线程对数据的访问 +* 内部带同步机制只能使用 [std::execution::par](https://en.cppreference.com/w/cpp/algorithm/execution_policy_tag),如果使用 [std::execution::par_unseq](https://en.cppreference.com/w/cpp/algorithm/execution_policy_tag) 会出现未定义行为 + +```cpp +#include +#include +#include + +class A { + public: + int get() const { + std::lock_guard l(m_); + return n_; + } + + void inc() { + std::lock_guard l(m_); + ++n_; + } + + private: + mutable std::mutex m_; + int n_ = 0; +}; + +void f(std::vector& v) { + std::for_each(std::execution::par, v.begin(), v.end(), [](A& x) { x.inc(); }); +} +``` + +* 如果使用 [std::execution::par_unseq](https://en.cppreference.com/w/cpp/algorithm/execution_policy_tag) 则应该在外部使用同步机制 + +```cpp +#include +#include +#include + +class A { + public: + int get() const { return n_; } + void inc() { ++n_; } + + private: + int n_ = 0; +}; + +class B { + public: + void lock() { m_.lock(); } + void unlock() { m_.unlock(); } + std::vector& get() { return v_; } + + private: + std::mutex m_; + std::vector v_; +}; + +void f(B& x) { + std::lock_guard l(x); + auto& v = x.get(); + std::for_each(std::execution::par_unseq, v.begin(), v.end(), + [](A& x) { x.inc(); }); +} +``` + +* 下面是一个更实际的例子。假如有一个网站,访问日志有上百万条,为了方便查看数据需要对日志进行处理。对日志每行的处理是独立的工作,很适合使用并行算法 + +```cpp +struct Log { + std::string page; + time_t visit_time; + // any other fields +}; + +extern Log parse(const std::string& line); + +using Map = std::unordered_map; + +Map f(const std::vector& v) { + struct Combine { + // log、Map 两个参数有四种组合,所以需要四个重载 + Map operator()(Map lhs, Map rhs) const { + if (lhs.size() < rhs.size()) { + std::swap(lhs, rhs); + } + for (const auto& x : rhs) { + lhs[x.first] += x.second; + } + return lhs; + } + + Map operator()(Log l, Map m) const { + ++m[l.page]; + return m; + } + + Map operator()(Map m, Log l) const { + ++m[l.page]; + return m; + } + + Map operator()(Log lhs, Log rhs) const { + Map m; + ++m[lhs.page]; + ++m[rhs.page]; + return m; + } + }; + + return std::transform_reduce(std::execution::par, v.begin(), v.end(), + Map{}, // 初始值,一个空的 map + Combine{}, // 结合两个元素的二元操作 + parse); // 对每个元素执行的一元操作 +} +``` diff --git a/docs/10_testing_and_debugging_multithreaded_application.md b/docs/10_testing_and_debugging_multithreaded_application.md new file mode 100644 index 0000000..636401d --- /dev/null +++ b/docs/10_testing_and_debugging_multithreaded_application.md @@ -0,0 +1,112 @@ +## 并发相关的 bug 类型 + +* 与并发直接相关的 bug 一般可以分为两大类,一是非预期阻塞,二是 race condition +* 非预期阻塞包含以下几种情况 + * 死锁(deadlock):两个线程互相等待,导致均无法完成工作。最明显的情况是,如果负责用户界面的线程死锁,界面将失去响应。也有一些情况是,界面可以保持响应,但一些任务无法完成,比如搜索不返回结果,或者文档不被打印 + * 活锁(livelock):类似于死锁,不同的是线程不是阻塞等待,而是在忙碌于一个检查循环中,比如自旋锁。严重时,其表现的症状就和死锁一样,比如程序不进行,此外由于线程仍在运行,CPU 会处于高使用率状态。在不太严重的情况下,活锁最终会被操作系统的随机调度解决,但仍然会造成任务的长时间延迟,并且延迟期间 CPU 使用率很高 + * I/O 阻塞或其他外部输入:如果线程阻塞等待外部输入,就无法继续处理工作。因此如果一个线程执行的任务会被其他线程等待,就不要让这个线程等待外部输入 +* 许多死锁和活锁都是由于 race condition 造成的,不过很大一部分 race condition 是良性的,比如要处理任务队列的下一个任务,决定用哪个工作线程去处理是无关紧要的。造成问题的 race condtion 包含以下几种情况 + * 数据竞争(data race):数据竞争是一种特定类型的 race condtion,由于对共享内存位置的不同步的并发访问,它将导致未定义行为。数据竞争通常发生于不正确地使用原子操作来同步线程,或者不加锁访问共享数据 + * 被破坏的不变量(broken invariant):它可以表现为空悬指针(其他线程可以删除被访问的数据)、随机内存损坏(由于局部更新导致线程读取的值不一致)、双重释放(比如两个线程弹出队列的同一个数据)等。不变量的破坏是暂时的,因为它是基于值的。如果不同线程上的操作要求以一个特定顺序执行,不正确的同步就会导致 race condition,有时就会违反这个执行顺序 + * 生命周期问题(lifetime issue):这个问题可以归入 broken invariant,但这里单独提出来。这个问题表现为,线程比其访问的数据活得更长。一般这个问题发生于线程引用了超出范围的局部变量,但也不仅限于此,比如调用 [join](https://en.cppreference.com/w/cpp/thread/thread/join),要考虑异常抛出时,调用不被跳过 +* 通常可以通过调试器来确认死锁和活锁的线程以及它们争用的同步对象。对于数据竞争、不变量的破坏、生命周期问题,可见症状(如随机崩溃或不正确的输出)可以显示在代码的任何位置,代码可能重写系统其他部分使用的内存,并且很久以后才被触及,这个错误可能在程序执行的后期出现在与 bug 代码完全无关的位置。这就是共享内存的真正祸端,无论如何限制线程对数据的访问和确保正确的同步,任何线程都可以重写其他线程中的数据 + +## 定位 bug 的方法 + +### code review + +* 让其他人或自己过段时间来 code review,因为对代码不熟悉,需要思考代码的工作方式,看待的角度也不一样,更有可能发现潜在的问题。多线程代码一般有以下问题 + * 哪些数据需要被保护,以避免并发访问 + * 如何确保数据得到保护 + * 其他线程此时可能运行到代码的哪个位置 + * 这个线程持有哪些锁 + * 其他线程持有哪些锁 + * 在这个线程中完成的操作和另一个线程中完成的操作之间是否有任何排序要求,如何执行这些要求 + * 这个线程读的数据是否仍然有效,是否可能被其他线程修改过 + * 假设另一个线程在修改数据,这意味着什么,如何确保这种情况永远不会发生 + +### 测试 + +* 测试多线程程序的困难在于,具体的线程调度顺序是不确定的,对于相同的输入,得到的结果却不一定相同,结果可能有时是正确的,有时是错误的。因此存在潜在的 race condition 也不意味着总会得到失败的结果,有时可能也会成功 +* 由于重现并发相关的 bug 很困难,所以值得仔细设计测试。最好让每个测试运行最小数量的代码,这样在测试失败时可以最好地隔离出错误代码。比如测试一个并发队列,分别测试并发的 push 和 pop 的工作,就直接比测试整个队列的功能要好 +* 为了验证问题是否与并发相关,应该从测试中消除并发性。多线程中的 bug 并不意味着一定是并发相关的,如果一个问题在单线程中也总是出现,这就是一个普通的 bug,而不是并发相关的 bug。如果一个问题在单核系统中消失,而在多核或多处理器系统中总会出现,一般这就可能是一个 race condition,或同步、内存序相关的问题 +* 测试用例 + * 单线程调用 push() 或 pop(),以验证 queue 的基本功能 + * 空 queue,一个线程 push(),另一个线程 pop() + * 空 queue,多线程 push() + * 满 queue,多线程 push() + * 空 queue,多线程 pop() + * 满 queue,多线程 pop() + * 有部分数据但不够所有线程用的 queue,多线程 pop() + * 空 queue,一个线程 pop(),多线程 push() + * 满 queue,一个线程 pop(),多线程 push() + * 空 queue,多线程 pop(),多线程 push() + * 满 queue,多线程 pop(),多线程 push() +* 测试环境 + * 多线程在每种 case 中具体指多少线程 (3, 4, 1,024?) + * 是否有足够的处理器,让每个线程运行在自己的核上 + * 在哪些处理器架构上进行测试 + * 如何合理对测试中的 while 部分 suitable scheduling +* 一般满足以下条件的代码就是易于测试的,这些条件单线程和多线程中同样适用 + * 每个函数和类的责任是清晰的 + * 函数简明扼要(short and to the point) + * 测试可以完全控制被测代码所在环境 + * 执行特定操作的被测代码在系统中是紧密而非分散的 + * 代码在写下之前已被考虑过如何测试 +* 为了测试设计并发代码的一个最好方法是消除并发,如果可以把代码分解成负责线程间通信路径的部分,以及在单线程中操作通信数据的部分,就可以极大地简化问题。对于操作通信数据的部分就可以用常规的单线程技术测试,对于负责线程间通信的部分,代码小了很多,测试也更容易 + +### 多线程测试技术 + +* 第一种测试技术是压力测试,随着代码运行次数的增加,bug 出现的几率也更高,如果代码运行十亿次都通过,代码就很可能是没有问题的。如果测试是细粒度的(fine-grained),比如前面对并发队列的测试,压力测试就更可靠。如果粒度非常大,可能的组合也非常多,即使十亿次的测试的结果也不算可靠 +* 压力测试的缺点是,如果测试本来就保证了问题不会发生,那么无论测试多少次都不会出现失败的情况,这就会造成误导。比如在单核系统上测试多线程程序,race condition 和乒乓缓存的问题根本不会出现,但这不表示这个程序在多核系统上是没问题的。又比如,不同处理器架构提供了不同的同步和内存序工具,在 x86 和 x86-64 架构上,无论使用 [memory_order_relaxed](https://en.cppreference.com/w/cpp/atomic/memory_order) 还是 [memory_order_seq_cst](https://en.cppreference.com/w/cpp/atomic/memory_order) 内存序,原子 load 操作总是一样的,这意味着在 x86 架构上使用 relaxed 语义总是可行的,但如果换成细粒度内存序指令的系统(比如 SPARC)就会失败 +* 第二种测试技术是组合仿真测试(combination simulation testing),即使用一个特殊的软件来仿真真实的运行时环境。仿真软件将记录数据访问、锁定、原子操作的序列,然后使用 C++ 内存模型的规则来重复运行所有可能的操作组合,以确定 race condition 和死锁 +* 虽然这种详尽的组合测试可以保证找到设计所要检测的所有问题,但会花费大量时间,因为组合的数量随线程 数和每个线程执行的操作数呈指数增长,它最好用于单个代码片段的细粒度测试,而非用于整个程序。这种技术的另一个明显缺点是,它要求访真软件能处理代码中的操作 +* 第三种测试技术是使用专门的库。比如共享数据通常会用 mutex 保护,如果在访问数据时能检查哪些 mutex 被锁定了,就能验证线程在访问数据时是否锁定了相应的 mutex,如果没有锁定就报告失败。库实现也能记录上锁的顺序,如果另一个线程对同一个 mutex 以不同顺序上锁,这就会被记录为潜在的死锁 +* 另一种类型的库是,同步原语的实现允许测试编写者在多线程等待时,可以控制哪个线程来获得锁,或者哪个线程被 [notify_one](https://en.cppreference.com/w/cpp/thread/condition_variable/notify_one) 通知。这就允许设置特定方案,来验证代码是否在这些方案中按预期运行 +* 一些测试工具已经作为标准库实现的一部分提供了,其他的则可以基于标准库的部分手动实现 + +### 构建多线程测试代码 + +* 多线程测试代码可以分为以下几部分 + * 必须先执行的总体设置 + * 必须运行在每个线程上的线程特定的设置 + * 要并发运行在每个线程上的代码 + * 并发执行结束后的状态断言 +* 如下是对一个队列的测试代码 + +```cpp +void test_concurrent_push_and_pop_on_empty_queue() { + ConcurrentQueue q; // 总体设置:先创建一个队列 + std::promise go, push_ready, pop_ready; + std::shared_future ready(go.get_future()); + std::future push_done; + std::future pop_done; + try { + push_done = std::async( + std::launch::async, // 指定异步策略保证每个任务运行在自己的线程上 + [&q, ready, &push_ready]() { + push_ready.set_value(); + ready.wait(); + q.push(42); // 线程特定的设置:存入一个 int + }); + pop_done = std::async(std::launch::async, [&q, ready, &pop_ready]() { + pop_ready.set_value(); + ready.wait(); + return q.try_pop(); + }); + push_ready.get_future().wait(); // 等待开始测试的通知 + pop_ready.get_future().wait(); // 同上 + go.set_value(); // 通知开始真正的测试 + push_done.get(); // 获取结果 + assert(pop_done.get() == 42); // 获取结果 + assert(q.empty()); + } catch (...) { + go.set_value(); // 避免空悬指针 + throw; // 再抛出异常 + } +} +``` + +### 测试多线程代码的性能 + +* 使用并发的一个主要目的就是利用多核处理器来提高程序性能,因此测试代码来确保性能确实提升了是很重要的。性能相关的一个主要方面就是可扩展性,性能应该随着核数一起提升。在测试多线程代码性能时,最好在尽可能多的不同配置上进行测试 diff --git a/docs/_config.yml b/docs/_config.yml new file mode 100644 index 0000000..277f1f2 --- /dev/null +++ b/docs/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-cayman diff --git a/docs/images/3-1.png b/docs/images/3-1.png new file mode 100644 index 0000000000000000000000000000000000000000..fd1b0af4ef736e54f33822c7051d0a717be7ac7e GIT binary patch literal 134318 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

a^WU3BJZ+Ub>o^no4!WhgOqIU6FLHcUpqp_!JgG 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 literal 0 HcmV?d00001 diff --git a/docs/images/4-1.png b/docs/images/4-1.png new file mode 100644 index 0000000000000000000000000000000000000000..9249a18c907bf1698826a3c927fdcddd910b8f9d GIT binary patch literal 44354 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 aKDm&ZCxDq@Z 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

**|aVL(97$wK!N|C!EU=GWkN2U+La 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 yEdc;3z25Yc>FyYz)Tl9F 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 literal 0 HcmV?d00001 diff --git a/docs/images/7-1.png b/docs/images/7-1.png new file mode 100644 index 0000000000000000000000000000000000000000..6cc7225723538169e4dff93ae00444f7637d0192 GIT binary patch literal 15539 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 literal 0 HcmV?d00001 diff --git a/docs/images/8-1.png b/docs/images/8-1.png new file mode 100644 index 0000000000000000000000000000000000000000..3fcd1d9323a05ca216b9f65ddd7748002de909a3 GIT binary patch literal 23264 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 JDp9Zs{vR0qVed6GJ*3&%DTQIJJ?92s%$!;# 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 literal 0 HcmV?d00001 diff --git a/docs/images/ref-filesystem-1.png b/docs/images/ref-filesystem-1.png new file mode 100644 index 0000000000000000000000000000000000000000..6b98abdb3b4dcdc31c61406c3fd1be2ba27111c3 GIT binary patch literal 60989 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 YIdPfCIkf)wQ2*qll-`v}eDV9g0NbYay#N3J literal 0 HcmV?d00001 diff --git a/docs/images/ref-memory_management-1.png b/docs/images/ref-memory_management-1.png new file mode 100644 index 0000000000000000000000000000000000000000..8d9c00cf7c7cd0674a9dea33981519caf9182dc4 GIT binary patch literal 94041 zcmdqJc|4Wh+csQ8+mtCJGa(`*Q>JK3LZ-cCo`-51lW`l8nGni6lqPM{MuyC@D3oHG z=OOb(=ILD*-M?So`?=ry{XFk;|MmQ#K5VXQUDsOcJdg7@j&p@+-chC?Jx_Y##0d(O zTX5|YCkTk^C@{Nxp`%pLr9(nVWY@kCw+^DOx0l&ylg!if_F(PVq(MBsZ8r(60i zCr;3FLtiIVv@hUKoH%@|0#`tIm@HI9WW4yXdw963QO)+|ksR4~73DW)Z^zzJd6=cC z2#-ls<>q^PD>n2Q^X<3r(}+iz4Tf1~XGbf2({Y2S>2z82avyrB%62Kqq_f3R)@BKn z>$8kr{xoAUUz~?BoleKL&y`18Qf$N$$|mUDV|8V(mu#N5#0ZQdyXI=f{8&8RPSyW z(#oixzxv4UaNpfbup_M@#4zPOuNq9CkK=fjwZy?}W?gu++}{nSWc>h#?cZ?HI;^Qe z6K1;@iaRQB+OoqW9~G$OdtSC3?Ff zeuqQ-wDgHERA!d+!^vA_e4~wFv<@qVj-PBZlID2SQ-8^M9QqV-a~|D06!Qdnz`#DC zO0&$<(Fo3*;>^4m>!us~G(lvXp>99k-El1)epIyY+mY06)czxTV7b~Xz<9*RwxyJU z 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ɭ-m& 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 ixHJ3z6a](https://en.cppreference.com/w/cpp/header/thread) +2. [线程间共享数据(Sharing data between thread)](02_sharing_data_between_thread.html):[\](https://en.cppreference.com/w/cpp/header/mutex)、[\](https://en.cppreference.com/w/cpp/header/shared_mutex) +3. [同步并发操作(Synchronizing concurrent operation)](03_synchronizing_concurrent_operation.html):[\](https://en.cppreference.com/w/cpp/header/condition_variable)、[\](https://en.cppreference.com/w/cpp/header/semaphore)、[\](https://en.cppreference.com/w/cpp/header/barrier)、[\](https://en.cppreference.com/w/cpp/header/latch)、[\](https://en.cppreference.com/w/cpp/header/future)、[\](https://en.cppreference.com/w/cpp/header/chrono)、[\](https://en.cppreference.com/w/cpp/header/ratio) +4. [C++ 内存模型和基于原子类型的操作(The C++ memory model and operations on atomic type)](04_the_cpp_memory_model_and_operations_on_atomic_type.html):[\](https://en.cppreference.com/w/cpp/header/atomic) + +## 并发编程实践 + +5. [基于锁的并发数据结构的设计(Designing lock-based concurrent data structure)](05_designing_lock_based_concurrent_data_structure.html) +6. [无锁并发数据结构的设计(Designing lock-free concurrent data structure)](06_designing_lock_free_concurrent_data_structure.html) +7. [并发代码的设计(Designing concurrent code)](07_designing_concurrent_code.html) +8. [高级线程管理(Advanced thread management)](08_advanced_thread_management.html) +9. [并行算法(Parallel algorithm)](09_parallel_algorithm.html):[\](https://en.cppreference.com/w/cpp/header/execution) +10. [多线程应用的测试与调试(Testing and debugging multithreaded application)](10_testing_and_debugging_multithreaded_application.html) + +## 标准库相关头文件 + +|头文件|说明| +|:-:|:-:| +|[\](https://en.cppreference.com/w/cpp/header/thread)、[\](https://en.cppreference.com/w/cpp/header/stop_token)|线程| +|[\](https://en.cppreference.com/w/cpp/header/mutex)、[\](https://en.cppreference.com/w/cpp/header/shared_mutex)|锁| +|[\](https://en.cppreference.com/w/cpp/header/condition_variable)|条件变量| +|[\](https://en.cppreference.com/w/cpp/header/semaphore)|信号量| +|[\](https://en.cppreference.com/w/cpp/header/barrier)、[\](https://en.cppreference.com/w/cpp/header/latch)|屏障| +|[\](https://en.cppreference.com/w/cpp/header/future)|异步处理的结果| +|[\](https://en.cppreference.com/w/cpp/header/chrono)|时钟| +|[\](https://en.cppreference.com/w/cpp/header/ratio)|编译期有理数算数| +|[\](https://en.cppreference.com/w/cpp/header/atomic)|原子类型和原子操作| +|[\](https://en.cppreference.com/w/cpp/header/execution)|标准库算法执行策略| + +## 并发库对比 + +### [C++11 Thread](https://en.cppreference.com/w/cpp/thread) + +|特性|API| +|:-:|:-:| +|thread|[std::thread](https://en.cppreference.com/w/cpp/thread/thread)| +|mutex|[std::mutex](https://en.cppreference.com/w/cpp/thread/mutex)、[std::lock_guard](https://en.cppreference.com/w/cpp/thread/lock_guard)、[std::unique_lock](https://en.cppreference.com/w/cpp/thread/unique_lock)| +|condition variable|[std::condition_variable](https://en.cppreference.com/w/cpp/thread/condition_variable)、[std::condition_variable_any](https://en.cppreference.com/w/cpp/thread/condition_variable_any)| +|atomic|[std::atomic](https://en.cppreference.com/w/cpp/atomic/atomic)、[std::atomic_thread_fence](https://en.cppreference.com/w/cpp/atomic/atomic_thread_fence)| +|future|[std::future](https://en.cppreference.com/w/cpp/thread/future)、[std::shared_future](https://en.cppreference.com/w/cpp/thread/shared_future)| +|interruption|无| + +### [Boost Thread](https://www.boost.org/doc/libs/1_82_0/doc/html/thread.html) + +|特性|API| +|:-:|:-:| +|thread|[boost::thread](https://www.boost.org/doc/libs/1_82_0/doc/html/thread/thread_management.html#thread.thread_management.thread)| +|mutex|[boost::mutex](https://www.boost.org/doc/libs/1_82_0/doc/html/thread/synchronization.html#thread.synchronization.mutex_types.mutex)、[boost::lock_guard](https://www.boost.org/doc/libs/1_82_0/doc/html/thread/synchronization.html#thread.synchronization.lock_guard.lock_guard)、[boost::unique_lock](https://www.boost.org/doc/libs/1_82_0/doc/html/thread/synchronization.html#thread.synchronization.locks.unique_lock)| +|condition variable|[boost::condition_variable](https://www.boost.org/doc/libs/1_82_0/doc/html/thread/synchronization.html#thread.synchronization.condvar_ref.condition_variable)、[boost::condition_variable_any](https://www.boost.org/doc/libs/1_82_0/doc/html/thread/synchronization.html#thread.synchronization.condvar_ref.condition_variable_any)| +|atomic|无| +|future|[boost::future](https://www.boost.org/doc/libs/1_82_0/doc/html/thread/synchronization.html#thread.synchronization.futures.reference.unique_future)、[boost::shared_future](https://www.boost.org/doc/libs/1_82_0/doc/html/thread/synchronization.html#thread.synchronization.futures.reference.shared_future)| +|interruption|[thread::interrupt](https://www.boost.org/doc/libs/1_82_0/doc/html/thread/thread_management.html#thread.thread_management.thread.interrupt)| + +### [POSIX Thread](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/pthread.h.html) + +|特性|API| +|:-:|:-:| +|thread|[pthread_create](http://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_create.html)、[pthread_detach](http://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_detach.html#)、[pthread_join](http://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_join.html#)| +|mutex|[pthread_mutex_lock、pthread_mutex_unlock](http://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_mutex_lock.html)| +|condition variable|[pthread_cond_wait](http://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_cond_wait.html)、[pthread_cond_signal](https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_cond_signal.html)| +|atomic|无| +|future|无| +|interruption|[pthread_cancel](http://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_cancel.html)| + +### [Java Thread](https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/lang/Thread.html) + +|特性|API| +|:-:|:-:| +|thread|[java.lang.Thread](https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/lang/Thread.html)| +|mutex|[synchronized blocks](http://tutorials.jenkov.com/java-concurrency/synchronized.html)| +|condition variable|[java.lang.Object.wait](https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/lang/Object.html#wait())、[java.lang.Object.notify](https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/lang/Object.html#notify())| +|atomic|volatile 变量、[java.util.concurrent.atomic](https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/util/concurrent/atomic/package-summary.html)| +|future|[java.util.concurrent.Future](https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/util/concurrent/Future.html)| +|interruption|[java.lang.Thread.interrupt](https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/lang/Thread.html#interrupt())| +|线程安全的容器|[java.util.concurrent](https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/util/concurrent/package-summary.html) 中的容器| +|线程池|[java.util.concurrent.ThreadPoolExecutor](https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/util/concurrent/ThreadPoolExecutor.html)| diff --git a/docs/reference/IO.md b/docs/reference/IO.md new file mode 100644 index 0000000..774bac2 --- /dev/null +++ b/docs/reference/IO.md @@ -0,0 +1,51 @@ +## I/O 硬件原理 + +* I/O 设备就是可以将数据输入到计算机(如鼠标、键盘),或者可以接收计算机输出数据的外部设备(如显示器) +* I/O 设备按信息交换单位可分为两类 + * 块设备(block device):把信息存储在固定大小的块中,每个块都有自己的地址。块设备的基本特征是,传输速率快,可寻址,每个块都能独立于其他块而读写。磁盘就是最常见的块可寻址设备,无论磁盘臂当前处于什么位置,总是能寻址其他柱面并且等待所需要的磁盘块旋转到磁头下面 + * 字符设备(character device):以字符为单位发送或接收一个字符流,而不考虑任何块结构,因此传输速率较慢,不可寻址,也没有任何寻道操作,在输入/输出时常采用中断驱动方式。打印机、鼠标就是常见的字符设备 +* I/O 设备一般由机械部件和电子部件两部分组成 + * 机械部件主要用于执行具体 I/O 操作,如鼠标的按钮、键盘的按键、显示器的屏幕、硬盘的磁盘臂 + * 电子部件也称作设备控制器(device controller)或适配器(adapter),通常是主板上的芯片,或一块插入主板扩充槽的印刷电路板 +* CPU 无法直接控制机械部件,因此需要通过设备控制器作为中介来控制机械部件。设备控制器的主要功能有 + * 接收和识别 CPU 发出的命令:每个控制器有几个寄存器用于与 CPU 通信,通过写入这些寄存器,操作系统可以命令设备发送数据、接收数据、开启或关闭,或者执行其他某些操作 + * 向 CPU 报告设备的状态:通过读取这些寄存器,操作系统可以了解设备的状态,是否准备好接收一个新的命令等 + * 数据交换:除了控制寄存器外,许多设备还有一个操作系统可以读写的数据缓冲区,比如在屏幕上显示像素的常规方法是使用一个视频 RAM,这一 RAM 基本上只是一个数据缓冲区,可供程序或操作系统写入数据 + * 地址识别:为了区分设备控制器中的寄存器,需要给每个寄存器设置一个地址,控制器通过 CPU 提供的地址来判断 CPU 要访问的寄存器 +* 设备控制器中有多个寄存器,为这些寄存器编址有两种方式 + * 内存映射 I/O(memory-mapped I/O):所有设备控制器的寄存器映射到内存空间中,每个控制寄存器被分配一个唯一的内存地址,并且不会有内存被分配到这一地址 + * 寄存器独立编址:每个寄存器被分配一个 I/O 端口(port)号,所有端口号形成 I/O 端口空间(I/O port space),并且受到保护使得普通用户程序不能对其进行访问,只有操作系统可以访问。这一方案中,内存地址空间和 I/O 地址空间是不同且不相关的 + +## I/O 软件原理 + +* I/O 软件的设计有以下目标 + * 设备独立性(device independence):允许编写出的程序可以访问任意 I/O 设备而无需事先指定设备,比如读取一个文件作为输入的程序,应该能在硬盘、DVD 或 USB 盘上读取文件,无需为每一种不同的设备修改程序 + * 统一命名(uniform naming):一个文件或一个设备的名字应该是一个简单的字符串或一个整数,不应依赖于设备 + * 错误处理(error handling):一般来说,错误应该尽可能在接近硬件的层面得到处理。当控制器发现一个读错误时,如果它能够处理,就应该自己设法纠正错误。如果控制器处理不了,设备驱动程序就应当予以处理,可能只需要重读一次这块数据就正确了 + * 同步(synchronous,即阻塞)和异步(asynchronous,即中断驱动)传输:大多数物理 I/O 是异步的,比如 CPU 启动传输后便转去做其他工作,直到中断发生。如果 I/O 操作是阻塞的,用户程序就更容易编写,比如 read 系统调用之后程序将自动被挂起,直到缓冲区中的数据准备好,而正是操作系统将实际异步的操作变为了在用户程序看来是阻塞式的操作 + * 缓冲(buffering):数据离开一个设备之后通常不能直接存放到最终目的地,比如从网络上进来一个数据包时,直到将该数据包存放到某个地方,并对其进行检查,操作系统才知道要将其置于何处。缓冲涉及大量复制工作,经常对 I/O 性能有重大影响 + * 共享设备和独占设备:共享设备能同时让多个用户使用(如磁盘),独占设备则只能由单个用户独占使用(如磁带机)。独占设备的引入带来了各种问题(如死锁),操作系统必须能处理共享设备和独占设备以避免问题发生 +* I/O 有三种实现方式 + * 程序控制 I/O(programmed I/O):这是 I/O 的最简单形式。CPU 轮询设备状态,当设备准备好时,CPU 向控制器发出读指令,从 I/O 设备中读取字,再把这些字写入到存储器。这种方式的优点是实现简单,缺点是在完成全部 I/O 之前,CPU 的所有时间都被其占用,如果 CPU 有其他事情要做,轮询就导致了 CPU 利用率低 + * 中断驱动 I/O :用中断阻塞等待 I/O 的进程,CPU 在等待 I/O 设备就绪时,通过调度程序先执行其他进程。当 I/O 完成后(比如打印机打印完一个字符,准备接收下一个字符),设备控制器将向 CPU 发送一个中断信号,CPU 检测到中断信号后保存当前进程的运行环境信息,然后执行中断驱动程序来处理中断。CPU 从设备控制器读一个字的数据传送到 CPU 寄存器,再写入主存,接着 CPU 恢复其他进程的运行环境并继续执行(打印下一个字符)。中断的优点是提高了 CPU 利用率,缺点是每次只能读一个字,每次都要发生一个中断,频繁的中断处理将浪费一定的 CPU 时间 + * 使用 DMA(Direct Memory Access)的 I/O :让 DMA 控制器来完成 CPU 要做的工作,使得 CPU 可以在 I/O 期间做其他操作。有了 DMA 控制器,就不用每个字中断一次,而是减少到每个缓冲区一次。DMA 控制器通常比 CPU 慢很多,如果 CPU 在等待 DMA 中断时没有其他事情要做,采用中断驱动 I/O 甚至程序控制 I/O 也许更好 + +## I/O 软件层次 + +* I/O 软件通常组织成四个层次,从上层到底层依次为 + * 用户级 I/O 软件:实现了与用户交互的接口,为用户提供 I/O 操作相关的库函数接口,如 `printf` + * 与设备无关的操作系统软件:向用户层提供系统调用,如为 `printf` 提供 `write`,另外还要提供设备保护(设置访问权限)、缓冲、错误报告、分配与释放专用设备、建立逻辑设备名到物理设备名的映射关系等功能 + * 设备驱动程序(device driver):每个连接到计算机上的 I/O 设备都需要某些设备特定的代码来对其进行控制,这样的代码称为设备驱动程序 + * 中断处理程序:进行中断处理 + +## 盘 + +* 盘有多种多样的类型,最常用的是磁盘,它具有读写速度同样快的特点,适合作为辅助存储器(用于分页、文件系统等) +* 磁盘被组织成柱面,每一个柱面包含若干磁道,磁道数与垂直堆叠的磁头个数相同,磁道又被分为若干扇区,通过 `(柱面号, 盘面号, 扇区号)` 即可定位一个磁盘块 +* 磁盘臂调度算法有 + * 先来先服务算法(First-Come First-Served,FCFS):按照请求接收顺序完成请求,优点是公平简单易实现,缺点是平均寻道时间较长 + * 最短寻道时间优先算法(Shortest Seek Time First,SSTF):下一次处理,磁头向所有请求中距离最近的位置移动。缺点是可能出现饥饿现象 + * 扫描算法(SCAN):也叫电梯算法(elevator algorithm),磁头持续向一个方向移动,直到到达最内侧或最外侧时才改变方向。优点是平均寻道时间较短,不会产生饥饿现象 + * LOOK 调度算法:对扫描算法稍作优化,如果磁头移动方向上已没有需要处理的请求,则直接改变方向 + * 循环扫描算法(C-SCAN):SCAN 算法对于各个位置磁道的响应频率不平均,靠近磁盘两侧的可能更快被下一次访问。为了解决这个问题,C-SCAN 算法的原理是,只在一个移动方向上处理请求,磁头返回时不处理任何请求 + * C-LOOK:只在一个移动方向上处理请求,如果该方向之后没有要处理的请求,则磁头返回,并且只需要返回到第一个有请求的位置 diff --git a/docs/reference/deadlocks.md b/docs/reference/deadlocks.md new file mode 100644 index 0000000..f78f88d --- /dev/null +++ b/docs/reference/deadlocks.md @@ -0,0 +1,135 @@ +## 资源死锁(resource deadlock) + +* 资源分为两类 + * 可抢占资源(preemptable resource):可以从拥有它的进程中抢占,而不会产生任何副作用,如存储器 + * 不可抢占资源(nonpreemptable resource):在不引起相关的计算失败的情况下,无法把它从占有它的进程处抢占过来,如光盘刻录机 +* 死锁主要关心不可抢占资源 +* 如果一个进程集合中,每个进程都在等待集合中的其他进程才能引发的事件,则该进程集合就是死锁的。通常这个事件是其他进程释放自身占有的资源,这种死锁称为资源死锁,这是最常见的死锁类型,但不是唯一的类型 +* 发生资源死锁的四个必要条件是 + * 互斥条件:每个资源要么分配给一个进程,要么是可用的 + * 占有和等待条件:已得到某个资源的进程可以再请求新的资源,并且不会释放已有资源 + * 不可抢占条件:已分配给一个进程的资源不能被强制抢占,只能被占有它的进程显式释放 + * 环路等待条件:死锁发生时,系统中必然有多个进程组成一条环路,环路中的每个进程都在等待下一个进程所占有的资源 + +## 鸵鸟算法 + +* 最简单的解决方法是,把头埋到沙子里,假装根本没有问题发生。不同人对该方法的看法也不同,数学家认为这种方法完全不可接受,无论代价多大都应该彻底防止死锁发生,工程师认为要根据死锁发生的频率、严重程度、系统崩溃次数来决定,如果死锁每五年发生一次,而系统每个月都会因故障崩溃一次,就没有必要用损失性能和可用性的代价去防止死锁 + +## 死锁检测和死锁恢复 + +* 第二种技术是死锁检测和恢复,使用这种技术时,系统不阻止死锁的产生,而是允许死锁发生,在检测到死锁发生后再恢复 +* 用 E 表示现有资源向量(exisiting resource vector),A 表示可用资源向量(available resource vector),用 C 表示当前分配矩阵(current allocation matrix),用 R 表示请求矩阵(request matrix),死锁检测的算法是 + * 在 R 中查找是否存在某一行(即一个进程)小于等于 A + * 如果找到这样一行,就将 C 中相同行数的行(即该进程的已分配资源)加到 A 中,然后标记该进程,再转到上一步 + * 如果不存在这样一行,则算法终止。算法结束时,所有没标记过的进程都是死锁进程 +* 死锁恢复方法有:抢占、回滚、终止进程 + +## 死锁避免 + +* 如果当前状态下没有死锁发生,并且存在某种调度次序能使每个进程都运行完毕,则称该状态是安全的 +* 对于目前有 3 个空闲资源的如下状态,先分配 2 个资源给 B,B 运行完释放 4 个资源,此时有 5 个空闲资源,接着 5 个资源全分配给 C,C 运行结束后将有 9 个空闲资源,最后将 9 个资源全分配给 A 即可。按 BCA 的分配顺序可以使得所有进程都能完成,因此这个状态是安全的 + +|进程|已分配资源|最大需求| +|:-:|:-:|:-:| +|A|3|9| +|B|2|4| +|C|2|7| + +* 空闲资源数为 2 时的如下状态就是不安全状态。首先只能先运行 B,B 运行结束后共有 4 个空闲资源,无法再运行 A 或 C + +|进程|已分配资源|最大需求| +|:-:|:-:|:-:| +|A|4|9| +|B|2|4| +|C|2|7| + +* 安全状态和不安全状态的区别是:从安全状态出发,系统可以保证所有进程都能完成,而从不安全状态出发就没有这样的保证 +* Dijkstra 提出了一种避免死锁的调度算法,称为银行家算法(banker's algorithm),方法是对每一个请求进行检查,如果满足这一请求会到达安全状态,则满足该请求,否则推迟对该请求的满足 +* 之前安全状态的例子考虑的就是单个资源的银行家算法,下面考虑多个资源的银行家算法 +* 已分配资源 + +|进程|资源1|资源2|资源3|资源4| +|:-:|:-:|:-:|:-:|:-:| +|A|3|0|1|1| +|B|0|1|0|0| +|C|1|1|1|0| +|D|1|1|0|1| +|E|0|0|0|0| + +* 仍需要的资源 + +|进程|资源1|资源2|资源3|资源4| +|:-:|:-:|:-:|:-:|:-:| +|A|1|1|0|0| +|B|0|1|1|2| +|C|3|1|0|0| +|D|0|0|1|0| +|E|2|1|1|0| + +* 对应的当前分配矩阵 C 和请求矩阵 R 为 + +```cpp +C R +3011 1100 +0100 0112 +1110 3100 +1101 0010 +0000 2110 +``` + +* 用三个向量表示现有资源 E、已分配资源 P、可用资源 A,计算分配矩阵 C 的每列和得到 `P = (5322)`,以 `E = (6342)` 为例,`A = E - P = (1020)` +* 检测一个状态是否安全的算法是 + * 查找一个使用可用资源即可运行的进程,如果找不到则系统就会死锁 + * 如果找到,则假设该进程获取所需资源并运行结束,将该进程标记为终止,再将其资源加到 A 上 + * 重复上述两步,如果最后所有进程都被标记为终止,则初始状态是安全的 +* 对于这个例子 + * 进程 D 仍需要的资源为 `(0010)`,均小于 `(1020)`,因此运行 D,D 最初的已分配资源为 `(1101)`,因此结束后 `A = (1020) + (1101) = (2121)` + * 进程 A 仍需要的资源为 `(1100)`,均小于运行 `(2121)`,运行 A(此时 E 也满足条件,也可以运行 E),A 最初的已分配资源为 `(3011)`,结束后 `A = (2121) + (3011) = (5132)` + * 运行 B,结束后 `A = (5132) + (0100) = (5232)` + * 运行 C,结束后 `A = (5232) + (1110) = (6342)` + * 运行 E,结束后 `A = (6342) + (0000) = (6342)` + * 所有进程都运行结束,因此这个例子的状态是安全的 + +## 死锁预防 + +* 死锁避免本质上来说是不可能的,因为它需要获取未来的请求,而这些请求是不可知的 +* 死锁发生时,四个条件必须同时成立,因此破坏其中条件即可预防发生死锁 + * 破坏互斥条件:如果资源不被一个进程独占,就一定不会发生死锁。实际情况中,如果允许两个进程同时使用打印机就会造成混乱,解决这个问题的方法是假脱机打印机技术(spooling printer) + * 破坏占有并等待条件:禁止已持有资源的进程再等待其他资源即可。一种实现方法是,规定所有进程在开始执行前请求所需的全部资源。这种方法的问题是,很多进程在运行时才知道需要多少资源,实际上如果进程知道需要多少资源就可以使用银行家算法。另一种方法是,当进程请求资源时,先暂时释放其占有的资源,再尝试一次获取所需的全部资源 + * 破坏不可抢占条件:这种方法是可能的 + * 破坏环路等待条件:对资源编号,请求必须按编号升序提出,但问题在于,几乎找不出一种使每个人都满意的编号次序 + +## 通信死锁(communication deadlock) + +* 除了最常见的资源死锁,还有通信死锁。通信死锁发生在通信系统(如网络)中,比如进程 A 向进程 B 发送请求信息并阻塞至 B 回复,如果 A 发送的信息丢失,就会导致 A 和 B 均阻塞,从而导致死锁 +* 通信死锁可以通过超时来解决,发送者在发送信息时启动计时器,如果计时器在回复到达前停止,则发送者可以认为信息已丢失,并重新发送 + +## 活锁(livelock) + +* 活锁不会导致进程阻塞,甚至可以说进程正在活动,因此不是死锁,但实际上进程不会继续往下执行,因此可以称为活锁 + +```cpp +void process_A() { + acquire_lock(&resource_1); + while (!try_lock(&resource_2)) { // 进程 A 尝试获取资源 2 失败 + release_lock(&resource_1); // 先释放资源 1,一段时间后再尝试获取资源 2 + wait_fixed_time(); // 若 B 此时也在等待,则两者都让出了资源但对方都未获取 + acquire_lock(&resource_1); // 两者各自拿回资源,则下次获取对方资源仍会失败 + } // 若此过程一直重复就是活锁 + use_both_resources(); + release_lock(&resource_2); + release_lock(&resource_1); +} + +void process_B() { + acquire_lock(&resource_2); + while (!try_lock(&resource_1)) { + release_lock(&resource_2); + wait_fixed_time(); + acquire_lock(&resource_2); + } + use_both_resources(); + release_lock(&resource_1); + release_lock(&resource_2); +} +``` diff --git a/docs/reference/file_systems.md b/docs/reference/file_systems.md new file mode 100644 index 0000000..c08a664 --- /dev/null +++ b/docs/reference/file_systems.md @@ -0,0 +1,184 @@ +* 进程运行时,可以在自己的地址空间存储信息,但这样保存信息的问题是 + * 对于一些程序,如银行系统,这样的存储空间太小 + * 进程终止时,保存的信息就丢失了 + * 经常需要多个进程访问同一信息,这要求信息独立于任何一个进程 +* 因此,长期存储信息有三个基本要求 + * 能够存储大量信息 + * 使用信息的进程终止时,信息仍存在 + * 允许多个进程并发访问信息 +* 理论上,磁盘(magnetic disk)就能解决长期存储的问题,但实际上,有许多操作不便于实现 + * 如何找到信息 + * 如何防止一个用户读取另一个用户的数据 + * 如何知道哪些块是空闲的 +* 为了解决这个问题,引入文件的概念,它是一个建模于磁盘的抽象概念 +* 文件由操作系统管理,文件的构造、命名、访问、使用、保护、实现、管理方法是操作系统设计的主要内容,操作系统中处理文件的部分称为文件系统(file system) + +## 文件 + +### 文件命名 + +* 各个系统中的文件命名规则不同,现代操作系统都允许用 1 到 8 个字母组成的字符串作为合法的文件名,通常也允许有数字和一些特殊字符 +* 一般操作系统支持文件名用圆点分隔为两部分,如 `main.cpp`,圆点后的部分称为文件扩展名(file extension)。UNIX 中,文件扩展名只是一种约定,Windows 中的扩展名则有特别意义,用户或进程可以在操作系统中注册扩展名,并规定哪个程序拥有该扩展名(即双击该文件则启动此程序并运行该文件) + +### 文件结构 + +* 文件可以有多种构造方式 + * 常见的一种构造方式是无结构的单字节序列,操作系统见到的就是字节,文件内容的任何含义只在用户程序中解释,UNIX 和 Windows 都采用这种方法。这为操作系统提供了最大的灵活性,用户可以向文件中加入任何内容,以任何形式命名,操作系统不提供帮助也不进行阻碍 + * 第二种构造方式是固定长度记录的序列,这种方式的中心思想是,读操作返回一个记录,写操作重写或追加一个记录。几十年前,80 列的穿孔卡片是主流时,很多大型机的操作系统使用的就是这种方式,文件由 80 个字符的记录组成,文件系统建立在这种文件基础上 + * 第三种构造方式是用一棵记录树构成文件,记录的固定位置有一个键,树按键排序,从而可以对键进行快速查找,这种方式被广泛用于处理商业数据的大型计算机 + +### 文件类型 + +* 操作系统一般支持多种文件类型,UNIX 和 Windows 都有普通文件(regular file)和目录(directory),此外 UNIX 还有字符特殊文件(character special file)和块特殊文件(block special file) +* 普通文件一般分为 ASCII 文件和二进制文件 + * ASCII 文件由多行正文组成,每行用回车符或换行符或两者(如 MS-DOS)结束,其最大优势是可以显示、打印、编辑,如果很多程序都用 ASCII 文件作为输入和输出,就很容易把一个程序的输出作为另一个程序的输入 + * 二进制文件打印出来是充满乱码的表,通常二进制文件有一定的内部结构,使用该文件的程序才了解这种结构。比如 UNIX 存档文件,每个文件以模块头开始,其中记录了名称、创建日期、所有者、保护码、文件大小,该模块头与可执行文件一样都是二进制数字,打印输出它们毫无意义 + +### 文件访问 + +* 早期操作系统只有顺序访问(sequential access)一种文件访问方式,进程可以从头按顺序读取文件的字节,不能跳过某一些内容。在存储介质是磁带而不是磁盘时,顺序访问文件是很方便的 +* 用磁盘存储文件时,就能以任何次序读取文件的字节,能被这种方式访问的文件称为随机访问文件(random access file)。对许多程序来说,随机访问文件必不可少,比如数据库系统,查找一条记录时,不需要先读出之前的成千上万条记录 + +### 文件属性 + +* 除了文件名和数据,操作系统还会保存文件相关的信息,如创建日期、文件大小等,这些附加信息称为文件属性(attribute)或元数据(metadata)。不同系统中的文件属性差别很大 + +### 文件操作 + +* 使用文件是为了存储信息并方便以后检索,不同的操作系统提供了不同的方式,常见的文件相关的系统调用有 `create`、`delete`、`open`、`close`、`read`、`write`、`append`、`seek`、`get attributes`、`set attributes`、`rename` + +## 目录 + +* 目录系统的最简单形式是单层目录系统,即一个目录中包含所有文件,这个目录通常称为根目录,其优势是简单,且能快速定位文件,常用于简单的嵌入式装置,如电话、数码相机 +* 现在的用户通常有成千上万的文件,用单层目录寻找文件就很困难了,这就需要层次结构(即一个目录树),几乎所有现代文件系统使用的都是层次目录系统。用目录树组织文件系统时,常用绝对路径名(absolute path name)或相对路径名(relative path name)来指明文件名 +* UNIX 中常见的目录操作的系统调用有 `create`、`delete`(只能删除空目录)、`opendir`、`closedir`、`readdir`、`rename`、`link`、`unlink` + +## 文件系统的实现 + +### 文件系统布局 + +* 文件系统存放在磁盘上。多数磁盘划分为一个或多个分区,每个分区中有一个独立的文件系统 +* 磁盘的 0 号扇区称为主引导记录(Master Boot Record,MBR),用来引导计算机 +* MBR 的结尾是分区表,该表给出了每个分区的起始地址和结束地址。表中的一个分区被标记为活动分区,计算机被引导时,BIOS 读入并执行 MBR,MBR 做的第一件事就是确定活动分区,读入第一个块,即引导块(boot block),并执行 +* 除了引导块,磁盘分区的布局通常随文件系统的不同而变化,一个可能的文件系统布局如下 + +``` +|-----------------整个磁盘-----------------| + 分区表 磁盘分区 + ↓ ↙ ↙ ↘ ↘ + __________________________________________ +|MBR||||________|________|________|________| + / \ + / \ + +|引导块|超级块|空闲空间管理|i节点|根目录|文件和目录| +``` + +### 文件的实现 + +* 文件存储实现的关键是记录文件用到了哪些磁盘块,不同的操作系统的实现方式不同 +* 最简单的方式是连续分配,每个文件作为一连串连续数据块存储在磁盘上,比如块大小为 1 KB 的磁盘上,50 KB 的文件要分配 50 个连续的块。每个文件都要从一个新的块开始,上一个文件末尾块可能会存在部分被浪费的空间 +* 连续分配的优势是实现简单,只需要为每个文件记录第一块的磁盘地址和使用的块数,另外读操作性能较好,单个操作就可以读出整个文件 +* 缺点是删除文件会在磁盘中留下断断续续的空闲块。压缩磁盘代价太高,不可行。维护一个空闲块链表,但创建新文件时,为了选择选择合适的空闲区,必须先给出文件的最终大小,如果用户要创建一个文档然后录入,用户是无法给出最终大小的。但这在 CD-ROM 中是可行的,因为所有文件的大小都事先定好了,并且后续使用也不会被改变 +* 第二种方式是链式分配,这样不会因为磁盘碎片而浪费存储空间,但随机访问很慢,每次要访问一个块时,都必须从第一个块开始。此外,指向下一个块的指针占用了一些字节,每个磁盘块存储数据的字节数不再是 2 的整数次幂,虽然这个问题不是非常严重,但也会降低系统的运行效率,因为程序一般以长度为 2 的整数次幂来读写磁盘块 +* 第三种方式是把链式分配的指针放到内存的一个表中,这个表称为文件分配表(File Allocation Table,FAT),这样就解决了大小不正常带来的问题,但如果表项过多,比如 1 TB 的磁盘和 1 KB 的块,FAT 有 10 亿项,每项至少占 3 字节,这就占了 3 GB 内存,因此 FAT 在大型磁盘中不实用 +* 最后一种方式是为每个文件赋予一个 i 节点(index-node)的数据结构,其中列出了文件属性和文件块的磁盘地址。给定 i 节点就能找到文件的所有块,这种方式相对于 FAT 的优势是,只有在文件打开时,其 i 节点才在内存中,最终需要的内存与同时打开的最大文件数成正比 + +### 目录的实现 + +* 读文件时必须先打开文件,打开文件时,操作系统利用路径名找到目录项,目录项中提供了查找文件磁盘块所需要的信息。这些信息与系统有关,信息可能是整个文件的磁盘地址(对于连续分配的系统)、第一块的编号(链式分配)、i 节点号。文件属性存放的位置可以是目录项或者 i 节点 +* 现代操作系统一般都支持长度可变的长文件名。最简单的实现方式是,给文件名一个长度限制,如 255 个字符,并为每个文件名保留该长度的空间,这种方式简单但浪费了大量目录空间 +* 第二种方式是,每个目录项中开头有一个记录目录项长度的固定部分,接着是文件属性、任意长度的文件名。缺点和连续分配的磁盘碎片问题一样,移除一个个文件后会留下断断续续的空隙。由于整个目录在内存中,只有对目录进行紧凑操作才能节省空间。另一个问题是一个目录项可能会分布在多个页面上,读取文件名时可能发生缺页中断 +* 第三种方式是,使目录项有固定长度,将文件名放在目录后面的堆上,并管理这个堆,这样移除一个目录项后,下一个进来的目录项总可以填满这个空隙 +* 线性查找文件名要从头到尾搜索目录,对于非常长的目录,一个优化方式是在每个目录中使用散列表来映射文件名和对应的目录项 + +### 共享文件 + +* 几个用户在同一个项目中工作时常需要共享文件。对于如下文件系统,B 与 C 有一个共享文件,B 的目录与该文件的联系称为一个链接(link)。这样,文件系统本身是一个有向无环图(Derected Acyclic Graph,DAG)而不是一棵树,代价是维护变得复杂 + +![](../images/ref-filesystem-1.png) + +* 共享文件的问题是,如果目录中包含磁盘地址,链接文件时必须将 C 目录中的磁盘地址复制到 B 目录中,如果 B(或 C)往文件中添加内容,新数据块只会列入 B(或 C)的用户目录中,C(或 B)对此改变是不知道的,这就违背了共享的目的 +* 解决这个问题的第一个方法是,磁盘块不列入目录,而是列入一个与文件关联的小型数据结构,目录将指向这个小型数据结构。这是 UNIX 的做法,小型数据结构就是 i 节点 +* 这种方法的缺点是,B 链接该共享文件时,i 节点记录的文件所有者仍是 C,只是将 i 节点的链接计数加 1,以让系统知道该文件有多少个指向它的目录项。如果 C 之后删除了这个文件,B 就有一个指向无效的 i 节点的目录项。如果这个 i 节点之后分配给另一个文件,B 的链接将指向一个错误的文件。系统可以通过 i 节点的计数知道文件被引用,但无法找到所有目录项并删除,也不可能把目录项指针存储在 i 节点中,因为可能有无数个这样的目录 +* 第二个方法是符号链接(symbolic linking),让系统建立一个 LINK 类型的文件,把该文件放在 B 目录下,使得 B 与 C 的一个文件存在链接。LINK 文件中包含了要链接的文件的路径名,B 读该链接文件时,操作系统发现是 LINK 类型,则找到其链接文件的路径并读取 +* 符号链接在文件被删除后,通过路径名查找文件将失败,因此不会有第一种方法的问题。符号链接的问题在于需要额外开销,必须读取包含路径的文件,然后逐步扫描路径直到找到 i 节点,这些操作可能需要很多次额外的磁盘访问 +* 此外,所有方式的链接都存在的一个问题是,文件有多个路径,如果查找文件,将多次定位到被链接的文件,如果一个程序的功能是查找某个文件并复制,就可能导致多次复制同一文件 + +### 日志结构文件系统(Log-structured File System,LFS) + +* 设计 LFS 的主要原因是,CPU 运行速度越来越快,RAM 内存变得更大,磁盘高速缓存迅速增加,不需要磁盘访问操作,就可能满足直接来自高速缓存的大部分读请求,由此可以推断,未来的磁盘访问多数是写操作,且写操作往往是零碎的,提前读机制并不能获得更好的性能 +* 因此 LFS 的设计者决定重新实现一种 UNIX 文件系统,即使面对一个由大部分为零碎的随机写操作组成的任务,也能够充分利用磁盘带宽 +* 基本思路是,将整个磁盘结构化为一个日志,最初所有写操作都缓冲在内存中,每隔一段时间或有特殊需要时,被缓冲在内存中未执行的写操作被放到一个单独的段中,作为日志末尾的一个邻接段被写入磁盘 +* 但磁盘空间不是无限大的,这种做法最终将导致日志占满整个磁盘,此时就无法再写入新的段。为了解决这个问题,LFS 有一个清理线程,该线程周期性扫描日志进行磁盘压缩。整个磁盘成为一个大的环形缓冲区,写线程将新的段写到前面,清理线程将旧的段从后面移走 +* LFS 在处理大量零碎写操作时的性能比 UNIX 好一个数量级,在处理读和大块写操作时的性能也不比 UNIX 差,甚至更好 + +### 日志文件系统 + +* 由于 LFS 和现有的文件系统不相匹配,所以还未被广泛使用,但其内在的一个思想,即面对出错的鲁棒性,可以被其他文件系统借鉴。这个基本想法是,保存一个用于记录系统下一步要做什么的日志。当系统在完成任务前崩溃时,重新启动后,就能通过查看日志获取崩溃前计划完成的任务。这样的文件系统被称为日志文件系统,并已被实际使用,比如微软的 NTFS、Linux ext3、RerserFS,OS X 将日志文件系统作为可选项提供 + +### 虚拟文件系统(Virtual File System,VFS) + +* 同一台计算机或同一个操作系统中,可以有多个不同的文件系统 +* Windows 有一个主要的 NTFS 文件系统,但也有一个包含 FAT-32 或 FAT-16 的驱动器或分区,此外还可能有 CD-ROM 或者 DVD(每一个包含特定文件系统),Windows 通过指定盘符来处理不同的文件系统,进程打开文件时,盘符是显式或隐式存在的,Windows 由此可知向哪个文件系统传递请求,不需要将不同的文件系统整合为统一模式 +* 所有现代的 UNIX 尝试将多种文件系统整合到一个统一的结构中。一个 Linux 系统可以用 ext2 作为根文件系统,ext3 分区装载在 `/usr` 下,采用 RerserFS 的文件系统的硬盘装载在 `/home` 下,ISO 9660 的 CD-ROM 临时装载在 `/mnt` 下。用户视角中,只有一个文件系统层级,但实际上是对用户和进程不可见的多种不相容的文件系统 +* 但是多种文件系统的存在在实际应用中是明确可见的,以前大多 UNIX 操作系统都使用 VFS 概念尝试将多种文件系统统一成一个有序结构,其核心思想是抽象出所有文件系统共有的部分为单独一层,这一层通过调用底层的实际文件系统来具体管理数据 +* UNIX 中,所有文件相关的系统调用最初都指向 VFS,这些来自用户进程的调用都是标准的 POSIX 系统调用,VFS 对用户进程提供的上层接口就是 POSIX 接口。VFS 也有一个对于实际文件系统的下层接口,即 VFS 接口,当创造一个新的文件系统和 VFS 一起工作时,新系统的设计者必须确定它提供 VFS 所需要的功能调用 + +``` +-------------------------------- +用户进程 +-------------------------------- +| +| POSIX 接口 +↓ +-------------------------------- +VFS +-------------------------------- +| | | +| | | VFS 接口 +↓ ↓ ↓ +-------------------------------- +FS1 FS2 FS3 实际文件系统 +-------------------------------- +↑ ↑ ↑ +| | | +↓ ↓ ↓ +-------------------------------- +高速缓冲区 +-------------------------------- +``` + +## 文件系统管理和优化 + +### 磁盘空间管理 + +* 几乎所有文件系统都将文件分割成固定大小的块存储,各块之间不一定相邻。块的大小是一个需要考虑的问题,块太小则文件块数越多,需要更多次的寻道与旋转延迟才能读出它们,从而降低了性能。块太大,则文件的最后一个块存在空间浪费。从历史观点上来说,一般设将块大小为 1 到 4 KB,但随着现在磁盘超过了 1 TB,磁盘空间已经不再短缺了,将块的大小提升到 64 KB 并接受一些浪费比较好 +* 选定块大小后,下一个问题是如何记录空闲块。有两种方法被广泛使用,一是链表,二是位图 +* 为了防止占用太多磁盘空间,多用户操作系统通常提供了强制性磁盘配额机制,系统管理员为每个用户分配拥有文件和块的最大数量,操作系统确保每个用户不超过得到的配额 + +### 文件系统备份 + +* 磁盘转储到磁带上有两种方案 + * 物理转储:从磁盘的第 0 块开始,将全部的磁盘块按序输出到磁带上,直到最后一块复制完毕 + * 逻辑转储:从一个或几个指定的目录开始,递归地转储其自给定日期后有所更改的全部文件和目录 + +### 文件系统的一致性 + +* 很多文件系统读取磁盘块,修改后再写回磁盘。如果在写回完成前系统崩溃,文件系统可能处于不一致状态。为此,很多计算机都有一个检查文件系统一致性的实用程序,比如 UNIX 的 fsck、Windows 的 scandisk,系统启动时,特别是崩溃后的重启,可以运行该程序 +* 一致性检查分两种 + * 块的一致性检查:程序构造两张表,每张表为每个块设立一个计数器,第一张表记录块在文件中的出现次数,第二张记录块在空闲区的出现次数。如果文件系统一致,最终每一个块在其中一张表中的计数器为 1,如果一个块在两张表中的计数器都为 0,则称为块丢失 + * 文件的一致性检查:原理同上,区别是一个文件(而非一个块)对应一个计数器。注意,由于存在硬链接,一个文件可能出现在多个目录中。而遇到符号链接是不计数的,不会对目标文件的计数器加 1 + +### 文件系统性能 + +* 访问磁盘比访问内存慢很多,如果只需要一个字,内存访问可以比磁盘访问快百万数量级,因此许多文件系统采用了各种优化措施来改善性能 +* 最常用的减少磁盘访问次数的技术是块高速缓存(block cache)或缓冲区高速缓存(buffer cache),它们逻辑上属于磁盘,但实际上保存在内存中 +* 第二个明显提高性能的技术是块提前读,在需要用到块之前先将块提前写入高速缓存,从而提高命中率。块提前读只适用于顺序读取的文件,如果请求文件系统在某个文件中生成一个块,文件系统将潜在地检查高速缓存,如果下一个块不在缓存中,则为下一个块安排一个预读 +* 另一个重要技术是把可能顺序访问的块放在一起,最好是在同一个柱面上,从而减少磁盘臂的移动次数。这个技术仅当磁盘中装有磁盘臂时才有意义,现在固态硬盘(SSD)越来越流行,而它们不带移动部件。固态硬盘采用了和闪存同样的制造技术,使得随机访问与顺序访问在传输速度上已经较为接近,传统硬盘的诸多问题就消失了,但也有一些新问题,比如每一块只可写入有限次数,使用时要十分小心以达到均匀分散磨损的目的 + +### 磁盘碎片整理 + +* 随着不断创建与删除文件,磁盘会逐渐产生许多碎片,创建一个新文件时,其使用的块会散布在整个磁盘上,造成性能降低 +* 一个恢复方式是,移动文件使其相邻,把空闲区放到一个或多个大的连续区域内。Windows 有一个 defrag 程序,就是用于完成这项工作的,Windows 用户应该定期使用它。Linux 文件系统由于其选择磁盘块的方式,在磁盘碎片整理上一般不会遇到 Windows 那样的困难,因此很少需要手动整理磁盘碎片 +* 固态硬盘不受磁盘碎片的影响,对其做磁盘碎片整理不仅没有提高性能,反而磨损了硬盘,缩短了使用寿命 diff --git a/docs/reference/memory_management.md b/docs/reference/memory_management.md new file mode 100644 index 0000000..04a4e83 --- /dev/null +++ b/docs/reference/memory_management.md @@ -0,0 +1,243 @@ +## 无存储器抽象 + +* 早期计算机没有存储器抽象,每个程序都直接访问物理内存 + +```asm +MOV REGISTER1, 1000 ;将位置1000的物理内存中的内容移到 REGISTER1 中 +``` + +* 因此那时呈现给程序员的存储器模型就是简单的物理内存:从 0 到某个上限的地址集合,每个地址对应一个可容纳一定数目(通常是 8 个)二进制位的存储单元 +* 这种情况下,在内存中同时运行两个程序是不可能的,如果一个程序在 2000 的位置写入一个新值,就会擦掉另一个程序在相同位置上的内容,因此无法同时运行两个程序,这两个程序会立刻崩溃 +* 为了运行多个程序,一个解决方法是,操作系统把当前内存中所有内容保存到磁盘,然后把下一个程序读入到内存中再运行即可。同一时刻,只要内存中只有一个程序,就不会发生冲突 +* 但这种方法有一个重要的缺陷,即重定位(即逻辑地址到物理地址的转换)问题。假设有两个程序,第一个程序在 0 处的指令是 `JMP 24`,第二个程序在 0 处的指令是 `JMP 28`,当第一个程序运行一段时间后再运行第二个程序,第二个程序会跳到第一个程序 28 处的指令。由于对内存地址的不正确访问,程序立刻崩溃 +* 一个补救方法是静态重定位,即装入时将逻辑地址转换为物理地址。当一个程序被装载到地址 16384 时,常数 16384 被加到每一个程序地址上。虽然这个机制在不出错误的情况下可行,但不是一种通用的解决方法,同时会减慢装载速度,并且它要求所有的可执行程序提供额外的信息,以区分哪些内存字中存有可重定位的地址,哪些没有 +* 虽然直接引用物理地址对大型计算机、小型计算机、台式计算机、笔记本都已经成为了历史,但在嵌入式系统、智能卡系统中,缺少存储器抽象的情况仍然很常见。像收音机、洗衣机、微波炉都是采用访问绝对内存地址的寻址方式,其中的程序都是事先确定的,用户不能在其上运行自己的软件,因此它们可以正常工作 +* 总之,把物理地址暴露给进程带来的严重问题有: + * 如果用户程序可以寻址内存的每个字节,就可以轻易破坏操作系统 + * 想要运行多个程序很困难 + +## 一种存储器抽象:地址空间 + +* 要使多个程序同时存在于内存中并且互不影响,需要解决保护(进程只能访问自己的内存)和重定位两个问题。对前者的一个原始的解决方法是,给内存标记上一个保护键,并且比较执行进程的键和其访问的每个内存字的保护键,比如进程能访问的空间是 0-100,CPU 标记此范围,然后在访问内存时检查是否为该进程可访问空间。不过这种方法并没有解决重定位问题 +* 更好的方法是创造一个新的存储器抽象:地址空间。地址空间是一个进程可用于寻址内存的一套地址集合,每个进程都有一个自己的地址空间,并且这个地址空间独立于其他进程的地址空间(除了一些情况下进程需要共享地址空间) +* 地址空间的概念非常通用,比如 7 位数字的电话号码的地址空间是 `0 000 000` 到 `9 999 999`,x86 的 I/O 端口的地址空间是 `0` 到 `16383`,IPv4 的地址空间是 `0` 到 `2 ^ 32 - 1`。地址空间也可以是非数字的,比如以 `.com` 结尾的网络域名的集合 +* 比较难的是给每个程序一个独有的地址空间,使得两个程序的相同地址(如地址 28)对应不同的物理地址 +* 一个简单的方法是使用动态重定位,即运行时将逻辑地址转换为物理地址。把每个进程的地址空间映射到物理内存的不同部分,当一个进程运行时,程序的起始物理地址装载到基址寄存器(又称重定位寄存器),程序的长度装载到界限寄存器(又称限长寄存器)。进程访问内存,CPU 在把地址发送到内存总线前会自动把基址加到进程发出的地址值上,同时检查程序提供的地址是否超出了界限寄存器中的值,如果超出了就会产生错误并终止访问。对于之前的例子,比如第二个程序的 `JMP 28`,CPU 会将其解释为 `JMP 16412` +* 使用基址寄存器和界限寄存器重定位的缺点是,每次访问内存都需要进行加法和比较运算,比较运算可以很快,但加法运算由于进位传递时间的问题,在没有使用特殊电路的情况下会显得很慢 +* 但物理内存是有限的,把所有进程一直保存在内存中需要巨大的内存,内存不足就无法支持这点。处理内存超载有两种通用方法,最简单的是交换(swapping)技术,即把进程完整调入内存运行一段时间,然后把它存回磁盘,这样空闲进程主要存储在磁盘上,不运行就不会占用内存。另一种方法是虚拟内存(virtual memory),它能使程序只有一部分调入内存的情况下运行 +* 交换可能在内存中产生多个空闲区(hole)。把进程尽可能靠近,将这些小的间隙合并成一大块,这种技术称为内存紧缩(memory compaction)。通常不进行这个操作,因为它需要耗费大量 CPU 时间 +* 如果进程的数据段可以增长(比如从堆中动态分配内存),进程与空闲区相邻,则可以把空闲区分配给进程使其增大。如果进程之间紧紧相邻,就需要把要增长的进程移到内存中一个足够大的区域,或者把一个或多个进程交换出去以生成足够大的空闲区。如果进程在内存中不能增长,并且磁盘上的交换区已满,则这个进程只能挂起直到有空间空闲,或者结束 +* 如果大部分进程在运行时需要增长,为了减少因内存区不够而引起的进程交换和移动开销,一种方法是在换入或移动进程时额外分配一些预留内存 +* 动态分配内存时,操作系统必须对其进行管理,一般跟踪内存使用情况有两种方法:位图和空闲区链表 +* 使用位图法时,把内存划分成分配单元(每个单元小到几个字节或大到几千字节),用位图中的一位来记录每个分配单元的使用情况,比如 0 表示空闲 1 表示占用(或者相反)。分配单元越小,位图越大,不过即使 4 个字节大小的分配单元,32 位的内存只需要 1 位位图,位图只占用了 `1 / 32` 的内存 +* 位图法的主要问题是,在决定把一个占 `k` 个分配单元的进程调入内存时,存储管理器必须搜索位图,在位图中找出有 `k` 个连续 0 的串,这个查找操作很耗时,因为在位图中该串可能跨越字的边界 +* 另一个记录内存使用情况的方法是,维护一个记录已分配内存段和空闲内存段的链表,链表中的一个节点包含一个进程或者两个进程间的一块空闲区 +* 使用链表法时,为进程分配内存的最简单的算法是首次适配(first fit)算法,存储管理器沿链表搜索,直到找到一个足够大的空闲区,然后将空闲区分为两部分,一部分为要分配的大小,供进程使用,另一部分形成新的空闲区 +* 对首次适配算法进行小修改可以得到下次适配(next fit)算法,区别是在每次找到合适的空闲区时记录位置,这样下次就可以从上次结束的地方开始搜索。Bays 的仿真程序证明下次适配算法性能略低于首次适配算法 +* 另一个著名并广泛使用的算法是最佳适配(best fit)算法,搜索整个链表,找到能容纳进程的最小空闲区。因为每次都要搜索整个链表,所以它比首次适配算法慢。有些令人意外的是,它比前两种算法浪费更多的内存,因为它会产生大量无用的小空闲区。为了避免分裂出很多非常小的空闲区,可以考虑最差适配(worst fit)算法,即总是分配最大的可用空闲区,但仿真程序表明这也不是一个好方法 +* 一个提高算法速度的方式是,为进程和空闲区分别维护链表,代价是增加复杂度和内存释放速度变慢,因为必须将回收的段从进程链表删除并插入到空闲区链表 +* 如果分别维护进程和空闲区的链表,就可以对空闲区链表按大小排序,以提高最佳适配算法的速度,比如按从小到大排序,第一个合适的空间就是最小的空闲区,就是最佳适配。排序后,首次适配算法与最佳适配算法一样快,下次适配算法无意义 +* 单独维护空闲区链表时可以做一个小优化,利用空闲区存储信息,每个空闲区的第一个字就是空闲区大小,第二个字指向下一空闲区 +* 另一种分配算法是快速分配(quick fit)算法,它为常用大小的空闲区维护单独的链表,比如链表第一项是 4 KB 大小空闲区的链表头指针,第二项是 8 KB 大小空闲区的链表头指针,以此类推。像 21 KB 的空闲区,既可以放在 20 KB 的链表中,也可以放在一个专门存放特殊大小的链表中。这种算法查找指定大小的空闲区很快,但同样存在的缺点是,进程终止或换出时,寻找它的相邻块并查找是否可以合并的过程非常费时,如果不合并,内存将很快分裂出大量无法利用的小空闲区 + +## 虚拟内存 + +* 当程序大到内存无法容纳时,交换技术就有所缺陷,一个典型 SATA 磁盘的峰值传输率高达每秒几百兆,交换一个 1 GB 的程序就需要好几秒 +* 程序大于内存的问题在一些应用领域早就存在了,比如模拟宇宙的创建就要花费大量内存。20 世纪 60 年代的解决方案是,将程序分割为多个覆盖区(overlay)。程序开始运行时,将覆盖管理模块装入内存,该模块立刻装入并运行第一个覆盖区,执行完成后,第一个覆盖区通知管理模块装入下一个覆盖区 +* 程序员必须把程序分割成多个片段,这个工作非常费时枯燥,并且易出错。不久后有了虚拟内存(virtual memory)的方法,这些工作都可以交给计算机去做 +* 虚拟内存的基本思想是,程序的地址空间被分割成多个页(page),每一页有连续的地址范围。这些页被映射到物理内存,但并不是所有页必须在内存中才能运行程序。当程序引用到一部分物理内存中的地址空间时,由硬件执行必要的映射。当程序引用到一部分不在物理内存中的地址空间时,由操作系统负责将缺失的部分装入物理内存并重新执行失败的指令 + +## 分页(paging) + +* 大部分虚拟内存系统都使用了分页技术 +* 由程序产生的地址称为虚拟地址(virtual address) + +```asm +MOV REG, 1000 ;将地址为 1000 的内存单元的内容复制到 REG,1000 是虚拟地址 +``` + +* 虚拟地址构成了虚拟地址空间(virtual address space)。在没有虚拟内存的计算机上,系统直接将虚拟地址送到内存总线上,读写操作使用相同地址的物理内存字。在使用虚拟内存时,虚拟地址被送到内存管理单元(Memory Management Unit,MMU),MMU 把虚拟地址映射为物理内存地址 +* 页表给出虚拟地址与物理内存地址之间的映射关系 +* 虚拟地址空间按固定大小划分为页面(page),物理内存中对应的单元称为页框(page frame),页面和页框的大小通常相同,页表说明了每个页面对应的页框。RAM 和磁盘之间的交换总是以整个页面为单元进行的 + +![](../images/ref-memory_management-1.png) + +* 对应 64 KB 的虚拟地址空间和 32 KB 的物理内存,可以得到 16 个页面和 8 个页框 +* 比如执行指令访问地址 0 时 + +```asm +MOV REG, 0 +``` + +* 虚拟地址 0 被送到 MMU,MMU 发现其位于页面 0(0 - 4095),根据映射结果,页面 0 对应页框 2(8192 - 12287),于是 MMU 将地址转换为 8192,并把地址 8192 送到总线上。内存并不需要知道 MMU 做的事,只看到一个访问地址 8192 的请求并执行 +* 当虚拟地址空间比物理内存大时,就会存在未被映射的页面。当程序执行指令访问未映射的页面 + +```asm +MOV REG, 32780 ;位于页面 8(从 32768 开始) +``` + +* MMU 发现该页面未被映射,于是使 CPU 陷入(traps)到操作系统,这称为缺页中断(page fault)。操作系统找到一个很少使用的页框并把其内容写入磁盘,比如找到页面 1 对应的页框 1。将页面 1 标记为未映射,再把页面 8 映射到这个页框 1,然后重新启动访问指令,此时虚拟地址 32780 就可以映射到物理地址 4108(4096 + 32780 - 32768) +* 页面大小一般是 2 的整数次幂。比如页面大小为 4 KB,即 `2 ^ 12`,对于一个 16 位的虚拟地址,即可用前 4 位表示页面的页号,后 12 位表示偏移量。比如虚拟地址 `8192`,二进制为 `0010 0000 0000 0100`,`0010` 即为页号,`0000 0000 0100` 即为偏移,因此 `8192` 位于页号 `2` 偏移 `4` 的位置 +* 页表中,查找页号 `2` 对应的页框号为 `6`,把页框号 `110` 复制到输出寄存器的高 3 位,后 12 位保持不变,`110 0000 0000 0100` 即为物理地址 +* 除了页框号,页表还会有一些其他的位 + * 有效位,如果该位为 1 则说明存在映射,如果为 0,则访问该页面将引起缺页中断 + * 保护(protection)位,指出一个页允许的访问方式,比如用一个位表示,0 表示读写,1 表示只读 + * 修改(modified)位,记录页面使用情况,写入页面后由硬件自动设置修改位,该位也称为脏位(dirty bit),在重新分配页框时很有用,比如一个页是脏的(已被修改过),则必须把它写回磁盘,是干净的则可以直接丢弃 + * 访问(referenced)位,在页面被访问时设置,主要用来帮助操作系统在发生缺页中断时选择要淘汰的页面 + * 禁止高速缓存位,该位对于映射到设备寄存器而非常规内存的页面十分重要,比如操作系统持续等待 I/O 设备的响应,必须保证硬件读取的数据来自设备而非高速缓存 + +## 加速分页过程 + +* 在任何分页系统中都需要考虑两个问题 + * 虚拟地址到物理地址的映射必须非常快:每次访问内存都要进行映射,所有的指令最终都来自内存,并且很多指令也会访问内存中的操作数,因此每条指令进行一两次或更多页表访问是必要的。如果指令一条指令要 1 ns,页表查询必须在 0.2 ns 内完成,以避免映射成为主要瓶颈 + * 如果虚拟地址空间很大,页表也会很大:现代计算机至少使用 32 位虚拟地址,假设页面大小为 4 KB,32 位的地址空间将有 100 万页,页表也必然有 100 万条表项。每个进程都有自己的虚拟地址空间,都需要自己的页表,于是需要为进程分配非常多的连续页框 +* 大多数程序总是对少量页面多次访问,没有必要让将整个页表保存在内存中,由此得出的一种解决方案是,设置一个转换检测缓冲区(Translation Lookaside Buffer,TLB),也称相联存储器(associate memory)或快表,将虚拟内存直接映射到物理地址,而不必再访问页表 +* TLB 通常在 MMU 中,包含少量表项,实际中很少会超过 256 个。将一个虚拟地址放入 MMU 中进行转换时,硬件先将页号与 TLB 中所有表项进行匹配,如果匹配成功且操作不违反保护位,则直接从 TLB 中取出页框号,而不再访问页表。如果匹配失败,则进行正常的页表查询,并从 TLB 淘汰一个表项,然后用新找到的页表项代替它 +* 处理巨大的虚拟地址空间有两种解决方法:多级页表和倒排页表 +* 比如 32 位地址空间中,页面大小为 4 KB,偏移量占 12 位,则页号占 20 位。将页号分组,页表项大小为 4 B,4 KB 的页面就能放 1024 个表项,于是每 1024 个页号分为一组。这样分组得到的页表为二级页表,再用一个顶级页表映射页号到二级页表的物理地址即可 +* 使用多级页表时,32 位的地址划分为 10 位的 PT1 域、10 位的 PT2 域、12 位的 Offset 域。比如对于虚拟地址 `0000 0000 0100 0000 0011 0000 0000 0100`,PT1 为 1,PT2 为 3,Offset 为 4,MMU 先访问顶级页表 1 处,得到二级页表的物理地址,由此访问二级页表 3 处,得到页框号,最后加上 Offset 即为最终的物理地址 +* 二级页表可以扩充为更多级。每级页表大小不能超过一个页面,比如 4 KB 页面,偏移为 12 位,页表项大小为 4 B,每 1024 分为一组,则每级最多 10 位,如果是 40 位,则除去 12 位,剩余可以划分为一级 8 位、二级 10 位、三级 10 位的三级页表 +* 单级页表只要进行两次访存(第一次访问页表得到物理地址,第二次访问物理地址),而每多一级页表就要多一次访存(不考虑 TLB) +* 另一种方式是倒排页表(inverted page table),让每个页框(而非页面)对应一个表项。比如对于 64 位虚拟地址,4 KB 的页,4 GB 的 RAM,一个倒排页表仅需要 `2 ^ 20` 个表项,表项记录了一个页框对应的页面(进程) +* 虽然倒排页表节省了大量空间,但从虚拟地址到物理地址的转换变得很困难,必须搜索整个倒排页表来找到页面,每一次搜索都要执行访问操作。这个问题可以通过 TLB 解决 +* 倒排页表在 64 位机器中很常见,因为 64 位机器中,即使使用大页面页表项数量也很庞大,比如对于 4 MB 页面和 64 位虚拟地址,需要的页表项目数为 `2 ^ 42` + +## 页面置换算法 + +* 发生缺页中断时,操作系统必须换出内存中的一个页面,以腾出空间。如果换出的页面在内存驻留期间被修改过,就必须把它写回磁盘以更新其在磁盘上的副本,如果未被修改过则不需要写回 +* 如果一个经常用到的页面被换出内存,短时间内它可能又被调入内存,这会带来不必要的开销。因此发生缺页中断时,如何选择要换出的页面是一个值得考虑的问题 + +### 最优页面置换算法(OPTimal replacement,OPT) + +* OPT 算法的思路很简单,从所有页面中选出下次访问时间距现在最久的淘汰 + +```cpp +432143543215 // 页面队列 +444444444222 // 页 1 + 33333333311 // 页 2 + 2111555555 // 页 3 +TTTT T TT // 是否发生缺页中断(共发生 7 次缺页中断,4 次页面置换) + | + 把 2 替换掉,因为 432 中,2 下一次被访问的时间最靠后 +``` + +* 这个算法的唯一问题在于,它是无法实现的,因为发生缺页中断时,操作系统无法得知各个页面下一次在什么时候被访问 +* 作为理论最优算法,可以用它衡量其他算法的性能。如果操作系统的页面置换性能只比最优算法差 1%,那么花费大量精力来优化算法就不是特别必要的 + +### 最近未使用页面置换算法(Not Recently Used,NRU) + +* 操作系统为每个页面设置了两个状态位,当页面被访问时设置 R 位,被修改时设置 M 位。启动进程时,所有页面的 RM 均设为 0,并且 R 被定期(比如每次时钟中断时)清零 +* 发生缺页中断时,根据 RM 位的值,可以将页面分为 4 类 + * 第 0 类:未访问未修改(R 位为 0,M 位为 0) + * 第 1 类:未访问已修改(R 位为 0,M 位为 1,看起来似乎不可能,实际可以由第 3 类转换而来) + * 第 2 类:已访问未修改(R 位为 1,M 位为 0) + * 第 3 类:已访问已修改(R 位为 1,M 位为 1,R 在清零后即变为第 1 类) +* NRU 算法随机从第 0 类中选择一个页面淘汰,如果第 0 类中没有页面则选择第 1 类,以此类推,优先选择编号最小的类 +* 这个算法的隐含思想是,淘汰一个未访问已修改页面(第 1 类),比淘汰一个频繁使用的干净页面(第 2 类)好 +* NRU 的主要优点是易理解且能有效实现,虽然性能不是最好的,但已经够用了 + +### 先进先出页面置换算法(First-In First-Out,FIFO) + +* 顾名思义,淘汰最早进入的页面 +* 操作系统维护一个内存中所有当前页面的链表,最新进入的页面放在表尾,淘汰页面就是表头页面 +* FIFO 可能淘汰常用页面,甚至可能出现分配页面数增多但缺页率反而提高的异常现象(Belady 异常),因此很少使用纯粹的 FIFO 算法 + +### 第二次机会页面置换算法(Second-Chance) + +* 对 FIFO 做一个简单的修改:检查最老页面的 R 位(访问位),如果 R 位是 0 则淘汰,如果是 1 则把 R 位清零,并把该页面放到表尾,然后继续搜索 +* 如果所有页面都被访问过,则该算法就简化为纯粹的 FIFO 算法 + +### 时钟页面置换算法(clock) + +* 第二次机会算法经常要在链表中移动页面,降低了效率且不是很有必要 +* 一个更好的办法是将所有页面保存在在一个类似钟面的环形链表中,一个表针指向最老的页面。发生缺页中断时,检查表针指向的页面,如果 R 位是 0 则淘汰该页面,并在该位置插入新页面,然后表针后移一步。如果 R 位是 1 则把 R 位清零,然后表针后移一步。如果该页已存在,不发生缺页中断,R 位是 0 则改为 1,表针不需要移动 + +### 最近最少使用页面置换算法(Least Recently Used,LRU) + +* LRU 是 OPT 的一个近似思路,在前几条指令中频繁使用的页面很可能在后几条指令中被使用,反过来说,很久没使用的页面很可能在之后的长时间内仍然不使用 +* LRU 是可实现的,但代价很高。实现 LRU 需要维护一个所有页面的链表,最常使用的位于表头,每次访问时必须更新整个链表,在链表中找到页面删除后再添加到表头 +* 有一些使用特殊硬件实现 LRU 的方法,比如要求硬件有一个 64 位计数器,它在每条指令执行完后加 1,每个页表项中有一个足够容纳这个计数器值的域。发生缺页中断时,检查所有页表项的计数值,值最小的就是最近最少使用的 +* 只有非常少的计算机有这种硬件,LRU 很优秀但很难实现 + +### 最不常用页面置换算法(Not Frequently Used,NFU) + +* NFU 是 LRU 的一个软件实现方案 +* NFU 将每个页面与一个软件计数器关联,计数器初值为 0,每次时钟中断时,操作系统扫描内存中所有页面,将每个页面的 R 位值加到计数器上,这个计数器大致跟踪了各个页面被访问的频繁程度。发生缺页中断时,则置换计数器值最小的页面 +* NFU 的问题在于,第一遍扫描中频繁使用的页面,第二遍扫描时,计数器值仍然很高。这就会导致后续扫描中,即使该页面使用次数最少,也会由于计数器值较高而不被置换 + +### 老化(aging)算法 + +* 老化算法对 NFU 做了一些改进,在 R 位加进之前先将计数器右移一位,然后把 R 位加到计数器最左端的位 + +``` +页面 +0 10000000 11000000 11100000 11110000 01111000 +1 00000000 10000000 11000000 01100000 10110000 +2 10000000 01000000 00100000 00100000 10001000 +3 00000000 00000000 10000000 01000000 00100000 +4 10000000 11000000 01100000 10110000 01011000 +5 10000000 01000000 10100000 01010000 00101000 + | | | | | + 访问页面 024 访问 014 访问 013 访问 04 访问 12 +``` + +* 发生缺页中断时,置换计数器值最小的页面,因为前面的 0 越多,说明其最近越不常被访问 +* 老化算法非常近似 LRU,但有两个区别 + * 比如最后一次访问时,如果发生缺页中断,需要置换一个页面。页面 3 和页面 5 开头都是 001,即前两次未被访问,前第三次被访问,如果前第三次是页面 5 先被访问,则 LRU 会替换页面 5,但这里无法区分两者谁先被访问,而只能替换值较小的页面 3 + * 老化算法计数器位数有限,比如这里是 8 位,只能记录过去 8 次的访问,超过该次数的记录无法得知。不过实践中,如果时钟滴答是 20 ms,8 位一般是够用的,如果一个页面 160 ms 未被访问,则很可能不重要 + +### 工作集页面置换算法 + +* 在单纯的分页系统中,刚开始启动进程时,内存中没有页面,CPU 尝试取第一条指令时就会产生一次缺页中断,使操作系统装入含第一条指令的页面。一段时间后,进程需要的大部分页面都在内存了,进程开始在较少缺页中断的情况下运行。这个策略称为请求调页(demand paging),因为页面在需要时被调入,而不是预先装入 +* 一个进程当前正在使用的页面集合称为它的工作集(Denning),如果整个工作集都被装入内存中,那么进程在运行到下一阶段之前不会产生很多缺页中断。如果内存太小无法容纳整个工作集,进程的运行过程中将产生大量缺页中断,导致运行速度变慢,因为通常执行一条指令只要几纳秒,而从磁盘读入一个页面需要十几毫秒。如果每执行几条指令就发生一次缺页中断,就称这个程序发生了颠簸(Denning) +* 请求调页策略中,每次装入一个进程都要产生大量缺页中断,速度太慢,并且 CPU 花了很多时间处理缺页中断,浪费了许多 CPU 时间,因此不少分页系统会设法跟踪工作集,以确保在进程运行前,工作集已经在内存中了,这个方法称为工作集模型(Denning),也叫预先调页(prepaging),其目的在于大大减少缺页中断率 +* 工作集是随着时间变化的,它是最近 k 次访存所访问过的页面集合。为了实现该算法,需要一种精确的方法来确定哪些页面在工作集中,为此必须预先选定 k 值。但有了工作集的定义并不意味着就能计算出工作集 +* 假设有一个长度为 k 的移位寄存器,每次访存都把寄存器左移一位,然后在最右端插入刚才访问过的页面号,寄存器中 k 个页面号的集合就是工作集。理论上,发生缺页中断时,只要读出寄存器中的内容并排序,然后删除重复的页面,结果就是工作集。但维护该寄存器并在缺页中断时处理它需要很大的开销,因此该技术从未被使用过 +* 有几种近似的方法作为替代,一种常见近似方法是,不向后查找最近 k 次的内存访问,而是查找过去一定时间内,比如过去 10 ms 访存所用到的页面集合 +* 基于工作集的页面置换算法是,找出一个不在工作集中的页面并淘汰,为此表项中至少需要包含两条信息,一是上次使用该页面的近似时间,二是 R 位(访问位) +* 处理表项时,如果 R 位是 1,则把上次使用时间改为当前实际时间。如果 R 位是 0,则可以作为置换候选者,计算生存时间(当前实际时间与上次使用时间的差),如果生存时间大于定义工作集范围的时间,则该页面在工作集外,将其置换。如果 R 为 0 且生存时间不超过定义工作集范围的时间,则该页面仍在工作集中,记录该页面。如果扫描完整个页表都没有可淘汰的,则从记录页面中选一个生存时间最长的淘汰,如果记录页面为空,即所有页面 R 位均为 1,则随机选择一个淘汰 + +### 工作集时钟(WSClock)页面置换算法 + +* 工作集算法需要扫描整个页表,比较费时,结合时钟算法的思路稍作改进,即可得到 WSClock 算法。它实现简单,性能较好,在实际工作中得到了广泛使用 + +## 分段(Segmentation) + +* 一个编译器在编译过程中会建立许多表,其中可能包括 + * 被保存起来供打印清单用的源程序正文(用于批处理系统) + * 包含变量名字和属性的符号表 + * 包含用到的所有整型量和浮点常量的表 + * 包含程序语法分析结果的语法分析树 + * 编译器内部过程调用使用的堆栈 +* 在一维地址空间中,当有多个动态增加的表时,就可能发生碰撞。一种能令程序员不用管理表扩张和收缩的方法是,在机器上提供多个互相独立的段(segment)的地址空间,段的长度可以不同,在运行时可以改变,比如堆栈段的长度在数据压入时会增长,在数据弹出时会减小 +* 每个段都构成一个独立的地址空间,在内存中占据连续空间,可以独立地增长或减小,而不会影响其他段 +* 段是按逻辑功能的划分的实体,程序员使用起来更方便,并且程序的可读性更高。此外,分段有助于共享和保护。分段系统中,可以把共享库放到一个单独的段中由各个进程共享,而不需要在每个进程的地址空间中保存一份。当组成一个程序的所有过程都被编译和链接好以后,如果一个段的过程被修改并重新编译,也不会影响到其他段,因为这个段的起始地址(基址)没有被修改 +* 要在分段的存储器中表示一个地址,必须提供一个段号(段名)和一个段内地址(段内偏移量) + +```cpp +31 ... 16 15 ... 0 // 可用 31 - 16 表示段号,15 - 0 表示段内地址 +``` + +* 每个进程需要一张段表,每个段表项记录一个段的起始位置和段的长度。段表项长度是固定的,因此段号可以是隐含的,不占存储空间。查找时,如果段号越界,则产生越界中断。如果段内地址超出段长,则产生越界中断 + +``` +K 号段的段表存放地址 = 段表起始位置 + K * 段表项长度 + +段号 基址 段长 +0 20K 3K +1 60K 2K +2 40K 5K + +如果一个逻辑地址段号为 1,段内地址为 1024 +段号 1 的段长为 2K,大于 1024,不产生越界中断 +存放地址 = 60K + 1024 = 61K +``` + +* 分段管理的缺点是,如果段长过大,则不便于分配连续空间,此外会产生外部碎片。分页管理的内存利用率高,不会产生外部碎片,只会有少量页内碎片。因此,两者结合可以互相弥补,实现段页式管理 +* 段页式系统的地址由段号、页号、页内地址(页内偏移量)组成。分段对用户可见,而分页不可见 + +```cpp +31 ... 16 15 ... 12 11 ... 0 // 可用 31 - 16 表示段号,15 - 12 表示页号,11 - 0 表示页内地址 +``` + +* 每个段表项记录页表长度、页表起始地址,通过页表起始地址找到页号,通过页号对应的页表项目找到物理地址,一共需要三次访存(如果引入以段号和页号为关键字的 TLB 且命中,则只需要一次访存)。段表项长度是固定的,段号可以是隐含的。同样,每个页表项长度固定,页号是隐含的 diff --git a/docs/reference/processes_and_threads.md b/docs/reference/processes_and_threads.md new file mode 100644 index 0000000..5119461 --- /dev/null +++ b/docs/reference/processes_and_threads.md @@ -0,0 +1,570 @@ +## 进程 + +* 在进程模型中,计算机上所有可运行的软件,通常也包括操作系统,被组织成若干顺序进程(sequential process),简称进程(process),一个进程就是就是一个正在执行程序的实例,包括程序计数器、寄存器和变量的当前值 +* 概念上来说,每个进程有自己的虚拟 CPU,但实际上真正的 CPU(假设只有一个 CPU)在各进程之间来回切换,同一时刻实际只有一个进程在运行 +* 实际只有一个物理程序计数器。每个进程运行时,它的逻辑程序计数器被装入实际的程序计数器。当进程结束时,物理程序计数器保存到内存中该进程的逻辑程序计数器中 +* 进程创建主要有四种形式 + * 系统初始化:启动系统时会创建若干进程,包括和用户交互的前台进程和停在后台的守护进程,守护进程可以通过 UNIX 的 ps 指令或 Window 的任务管理器查看 + * 运行中的程序执行创建进程的系统调用:比如启动一个程序,该程序要启动更多进程来分配任务 + * 用户请求创建一个新进程:比如用户双击图标启动程序 + * 大型机批处理作业的初始化 +* 创建进程的系统调用在 UNIX 中是 `fork`,在 Windows 中是 `CreateProcess`,进程创建后,父子进程有不同的地址空间 +* 进程终止通常也有四种形式 + * 正常退出(自愿的):比如点击浏览器的关闭图标。进程退出的系统调用在 UNIX 中是 `exit`,在 Windows 中是 `ExitProcess` + * 出错退出(自愿的):比如执行 `cc foo.c` 编译 `foo.c` 而该文件不存在 + * 严重错误(非自愿):比如执行非法指令、引用不存在的内存、除数是零,UNIX 中会希望自行处理这些错误以通知操作系统,进程会收到信号被中断而非终止 + * 被其他进程杀死(非自愿):UNIX 中是 `kill`,Windows 中是 `TerminateProcess` +* UNIX 中,进程和其所有子进程(包括其后裔)组成一个进程组,当用户发出一个键盘信号,该信号会发送给进程组所有成员 +* Windows 中没有进程层次的概念,所有进程地位相同 +* 进程阻塞有两种情况,一是正常情况,比如操作系统调度另一个进程占用 CPU,二是异常情况,比如没有足够的 CPU 可调用 +* 进程有三种状态:运行、就绪、阻塞 + +``` +运行 <-> 就绪 + ↘ ↗ + 阻塞 + +运行:该时刻实际占用 CPU +就绪:操作系统调度了其他进程运行而暂时停止 +阻塞:逻辑上不能继续运行,比如等待用户输入 +``` + +* 操作系统通过维护一张进程表(一个结构数组)来实现进程模型,每个进程占一个表项(即进程控制块,Processing Control Block)。PCB 包含了进程状态的主要信息,如程序计数器、堆栈指针、内存分配状态、所打开的文件状态、账号和调度信息、进程状态切换时必须保存的信息 +* 所有中断都从保存寄存器开始,通常会保存到当前进程的 PCB 中。一个进程在执行过程中可能中断几千次,但恢复时,被中断的进程都将返回到与中断发生前完全相同的状态 +* 发生中断后,操作系统最底层的工作过程 + * 中断硬件将程序计数器、程序状态字、寄存器压入堆栈 + * 硬件从中断向量装入新的程序计数器 + * 通过汇编保存寄存器值(因为这类操作无法用高级语言完成) + * 通过汇编设置新的堆栈 + * 运行 C 语言(假设操作系统用 C 编写)中断服务例程 + * 调用调度程序,决定接下来要运行的进程 + * C 返回到汇编 + * 通过汇编运行新进程 +* 假设一个进程等待 I/O 操作与其在内存中停留的时间比为 `p`,则 `n` 个进程都在等待(此时 CPU 空转)的概率为 `p ^ n`,CPU 利用率为 `1 - p ^ n`,因此一般(该模型只是粗略情况)I/O 时间越短、运行进程越多,CPU 利用率越高 + +``` +假如内存为 8G,操作系统和相关表格占 2G,用户程序也占 2G,内存最多容纳 3 个用户程序 +假设 80% 时间用于等待 I/O 操作 +CPU 利用率 = 1 - 0.8 ^ 3 = 49% +如果增加 8G 内存,则最多容纳 7 个用户程序 +CPU 利用率 = 1 - 0.8 ^ 7 = 79%,吞吐量提高为 79% - 49% = 30% +如果再增加 8G 内存,则最多容纳 11 个用户程序 +CPU 利用率 = 1 - 0.8 ^ 11 = 91%,吞吐量只提高了 12%,可见第一次增加内存比较划算 +``` + +## 线程 + +* 正如进程提供的抽象使得避免了对中断、定时器、上下文切换的考虑,多线程提供了一种新抽象,即并行实例共享同一地址空间和所有可用数据,这正是多进程模型(地址空间不同)无法表达的 +* 第二个需要多线程的理由是,线程更轻量,创建和撤销都更快(通常创建一个线程比创建一个进程快 10 - 100 倍) +* 第三个理由是多核 CPU 系统中,多线程为真正的并行提供了可能 +* 线程包含一个程序计数器(记录接下来要执行哪一条指令)、寄存器(保存线程当前的工作变量)、堆栈指针(记录执行历史,每个线程的堆栈有一帧,每一帧保存一个已调用但还未返回的过程,如局部变量、返回地址) +* 各线程可以访问进程地址空间的每一个内存地址,因此一个线程可以读写甚至清除另一个线程的堆栈。线程之间没有保护,因为不可能,也没必要 +* 除了共享地址空间,线程还共享同一个打开文件集、子进程、定时器及相关信号量 +* 线程可以处在运行、就绪、阻塞、终止等状态中的任何一个 +* thread_yield 允许线程自动放弃 CPU 转让给另一个线程运行,提供这个调用是因为,不同于进程,线程库不能利用时钟中断强制线程让出 CPU +* 实现线程包主要有两种方式,一是用户级线程(User-Level Thread),二是内核级线程(Kernel-Level Thread),另外也有混合实现 +* 用户级线程把整个线程包放在用户空间中,内核对其一无所知,不需要内核支持,可以在不支持线程的操作系统上实现。在用户空间管理线程时,每个进程需要有其专用的线程表(thread table),这些表和内核中的进程表类似,只不过记录的是各个线程的属性,如程序计数器、寄存器、堆栈指针和状态等。该线程表由运行时系统管理,当线程转换到就绪或阻塞状态时,在线程表中存放重启该线程所需的信息,与内核在进程表中存放进程的信息完全一样 +* 用户级线程允许进程有自己定制的调度算法,具有更好的可扩展性(因为内核级线程需要一些固定表格空间和堆栈空间),性能更好。用户级线程的切换需要少量机器指令,而内核级线程需要完整的上下文切换,修改内存映像,使高速缓存失效,这导致了若干数量级的延迟 +* 用户级线程的问题是如何实现阻塞系统调用,比如线程读取键盘,在没有按下任何按键之前不能让该线程实际进行该系统调用,因为这会停止所有线程。另一个问题是,如果一个线程开始运行,则其所在进程的其他线程就不能运行,除非运行线程自动放弃 CPU。而使用内核级线程时,线程阻塞在 I/O 上时,不需要将整个进程挂起 +* 内核级线程的线程表(和用户级线程的线程表一样,记录寄存器、状态和其他信息)存在于内核中,当一个线程希望创建一个新线程或撤销一个已有线程时,将进行一个系统调用,这个系统调用通过对线程表的更新完成创建或撤销工作 +* 当内核级线程阻塞时,内核可以运行同一进程中的另一线程,或者运行另一个进程的线程。而对于用户级线程,运行时系统始终运行其所在进程的线程,直到内核剥夺 CPU(或没有可运行的线程存在)为止 +* 在内核中创建或撤销线程的代价较大,因此内核级线程被撤销时,系统会将其标记为不可运行的,但其内核数据结构未受影响,之后必须创建新线程时就重新启动一个旧线程。用户级线程也可以这样回收,但因为管理代价很小,所以没必要 + +## 进程间通信(Inter Process Communication) + +* 对共享内存进行访问的程序片段称为临界区(critical region、critical section),如果同一时刻临界区只有一个进程,就能避免 race condition +* 单处理器系统中实现这点的简单做法是,在每个进程刚进入临界区后立即屏蔽所有中断,在即将离开时再打开中断。屏蔽中断后,时钟中断也被屏蔽。CPU 只有发生时钟中断或其他中断才会进行进程切换,这样 CPU 就不会切换到其他进程 +* 但这个方案并不好,因为把屏蔽中断的权力交给用户进程是不明智的,如果一个进程屏蔽中断后不打开,就可能导致整个系统终止。此外如果系统是多处理器,则屏蔽中断只对执行了 disable 指令的 CPU 有效,其他 CPU 仍将运行 +* 对于内核来说,更新变量或列表的几条指令期间屏蔽中断很方便,因此屏蔽中断对操作系统本身是一项很有用的技术,但对用户进程则不是一种合适的互斥机制 +* 第二种方式是一种软件方案,假设有一个共享锁变量,其初始值为 0,当进程要进入临界区时,首先测试锁,如果值为 0 则将锁设为 1 并进入临界区,如果锁的值已经为 1,则进程等待其值为 0 +* 这种方式的问题在于,如果在一个进程检查到锁为 0,并要将锁设为 1 之前,恰好另一个线程被调度运行将锁设为 1,而第一个进程恢复运行时也将把锁设为 1 并进入临界区,此时临界区就有了两个进程 +* 第三种方式是忙等待(busy waiting),用一个循环不断测试变量值,直到变量值改变才进入临界区,用于忙等待的锁称为自旋锁(spin lock)。这种方式的问题是,在循环中浪费了大量 CPU 时间,应该避免,除非等待时间非常短才有使用的理由 + +```cpp +// 进程 A +while (true) { + while (x) { + } + critical_region(); + x = true; // 允许进程 B 进入临界区 + noncritical_region(); +} + +// 进程 B +while (true) { + while (!x) { + } + critical_region(); + x = false; // 允许进程 A 进入临界区 + noncritical_region(); +} +``` + +* 第四种方式是 1981 年由 G. L. Peterson 提出的 Peterson 算法 + +```cpp +constexpr int N = 2; // 进程数量为 2 +int turn = 0; // 轮到的进程 +vector interested(N); + +void enter_region(int process) { + int other = 1 - process; // 另一进程(进程号为 0 或 1) + interested[process] = true; + turn = process; // turn 只有一个,即使两个进程调用也只有后一个赋值会保留 + while (turn == process && interested[other]) { + } +} + +void leave_region(int process) { // 调用上述函数完成后调用此函数 + interested[process] = false; +} + +// 若进程 A 调用 enter_region 则很快返回, +// 此时进程 B 调用将在 while 循环挂起, +// 直到进程 A 调用 leave_region +// 若进程 AB 同时调用 enter_region, +// turn 为后赋值者, +// 则先赋值者退出循环并调用 leave_region,后赋值者再退出循环 +``` + +* 第五种方式是一种硬件方式,需要借助 TSL 指令,即测试并加锁(test and set lock),该指令是一个原子操作,执行 TSL 指令的 CPU 将锁住内存总线以禁止其他 CPU 在指令结束前访问该内存 + +```cpp +TSL RX, LOCK // 将内存字 LOCK 读到寄存器 RX 中,然后在该内存地址写一个非零值,读写是原子操作 +``` + +* 为了使用 TSL 指令实现互斥,用一个共享变量 `LOCK` 来协调对内存的访问,其值为 0 时任何进程都能用 TSL 指令将值设为 1 并读写共享内存,操作结束时再用 move 指令将值重置为 0 + +```asm +enter_region: + TSL REGISTER, LOCK ;复制锁到寄存器并设置值为 1 + CMP REGISTER, #0 ;值是否为 0 + JNE enter_region ;不是 0 则循环 + RET ;返回,进入临界区 + +leave_region: + MOVE LOCK, #0 + RET +``` + +* 可以用 XCHG 指令替代 TSL 指令,它原子交换两个位置的内容 + +```asm +enter_region: + MOVE REGISTER, #1 ;在寄存器放一个 1 + XCHG REGISTER, LOCK ;原子交换寄存器和锁变量的内容 + CMP REGISTER, #0 ;值是否为 0 + JNE enter_region ;不是 0 则循环 + RET ;返回,进入临界区 + +leave_region: + MOVE LOCK, #0 + RET +``` + +* Peterson 算法和 TSL 或 XCHG 解法同样都有忙等待的问题,它们的本质都是在进程进入临界区时检查是否允许进入,不允许则原地等待直到允许为止 + +## 生产者-消费者问题 + +* 两个进程共享一个固定大小的缓冲区,生产者进程将消息放入缓冲区,消费者进程从缓冲区取出消息 + +```cpp +constexpr int N = 100; // 缓冲区的槽数 +int cnt = 0; // 缓冲区数据数 + +void producer() { + while (true) { + int item = produce_item(); // 生成新数据 + if (cnt == N) { + sleep(); + } + insert_item(item); // 将消息放入缓冲区 + ++cnt; // 1 + if (cnt == 1) { + wakeup(consumer); // 2 + } + } +} + +void consumer() { + while (true) { + if (!cnt) { + sleep(); // 3 + } + int item = remove_item(); // 从缓冲区取一个数据 + --cnt; + if (cnt == N - 1) { + wakeup(producer); + } + consume_item(item); // 打印数据 + } +} + +// 问题在于 cnt 的访问存在 race condition, +// 如果消费者执行到 3 处,cnt 为 0,在即将 sleep 之前, +// 生产者在此之后才执行到 1 处,此时 cnt 为 1,执行到 2 处,调用 wakeup, +// 但此时消费者还未 sleep,因此 wakeup 的信号丢失,没有实际作用, +// 接着消费者 sleep,生产者开始下一轮循环, +// 生产者下一轮循环到 1 处,cnt 为 2,到 2 处,不再调用 wakeup,消费者保持 sleep, +// 生产者继续之后的循环,并且每一轮都不会唤醒消费者, +// 最终生产者执行到 cnt 为 N 时 sleep,两个进程都将永久 sleep +``` + +## 信号量(semaphore) + +* 信号量是由 E. W. Dijkstra 于 1965 年提出的一种方法,它使用一个整型变量作为信号量,值为 0 表示没有保存下来的唤醒操作,值为正数表示唤醒操作的次数 +* 信号量有 down 和 up 两种操作,Dijkstra 在论文中称其为 P 和 V 操作(荷兰语中的 Proberen 意为尝试,Verhogen 意为增加或升高) +* down 操作检查值是否大于 0,若大于 0 则减 1 并继续,若为 0 则进程睡眠,并且此时 down 操作未结束 +* up 操作对值加 1。如果有进程在信号量上睡眠,无法完成一个先前的 down 操作,则由系统选择其中一个以允许完成其 down 操作。于是,对一个有睡眠进程的信号量执行一次 up 操作,信号量值仍为 0,但睡眠进程少了一个 +* down 操作和 up 操作中的所有操作都是原子的,一般作为系统调用实现。操作系统只要在执行测试信号量、更新信号量、使进程睡眠等操作时暂时屏蔽全部中断,这些动作只需要几条指令,所以屏蔽中断不会带来什么副作用。如果使用多个 CPU,则每个信号量应由一个一个锁保护,使用 TSL 或 XCHG 指令来确保同一时刻只有一个 CPU 对信号量进行操作 +* 注意,这里使用 TSL 或 XCHG 指令来防止多 CPU 同时访问一个信号量,与生产者或消费者用忙等待来等待对方腾出或填充缓冲区是完全不同的。信号量操作只需要几毫秒,而生产者或消费者则可能需要任意长时间 +* 使用三个信号量解决生产者-消费者问题:full 记录已充满的缓冲槽数,初值为 0;empty 记录空的缓冲槽数,初值为缓冲区中槽的数目;mutex 确保生产者和消费者不会同时访问缓冲区,初值为 1 +* 供多个进程使用的信号量初值为 1,保证同时只有一个进程可以进入临界区,这种信号量称为二元信号量(binary semaphore)。如果每个进程进入临界区前执行一个 down 操作,并在刚退出时执行一个 up 操作,就能实现互斥 + +```cpp +constexpr int N = 100; // 缓冲区的槽数 +using semaphore = int; +semaphore mutex = 1; +semaphore empty = N; // 缓冲区空槽数 +semaphore full = 0; // 缓冲区满槽数 + +void producer() { + while (true) { + int item = produce_item(); + down(&empty); + down(&mutex); + insert_item(item); + up(&mutex); + up(&full); + } +} + +void consumer() { + while (true) { + down(&full); + down(&mutex); + int item = remove_item(); + up(&mutex); + up(&empty); + consume_item(item); + } +} +``` + +* 信号量的另一个作用是实现同步(synchronization),这里 full 和 empty 保证缓冲区满时生产者停止运行,缓冲区空时消费者停止运行 + +## 互斥量(mutex) + +* 如果不需要信号量的计数功能,可以使用其称为互斥量的简化版本。互斥量仅适用于管理共享资源或一小段代码。互斥量实现简单且有效,在实现用户空间线程包时十分有用 +* 互斥量只有加锁和解锁两种状态,只需要一个二进制位表示,不过实际上一般用整型量,0 表示解锁,其他值表示加锁 +* 线程需要访问临界区时调用 mutex_lock,如果互斥量是解锁的则临界区可用,调用成功,线程可以进入临界区,否则线程被阻塞,直到临界区中的线程完成并调用 mutex_unlock。如果多个线程阻塞在该互斥量上,则随机选择一个线程并允许它获得锁 +* 用 TSL 或 XCHG 指令就可以很容易地在用户空间实现互斥量 + +```asm +mutex_lock: + TSL REGISTER, MUTEX ;将互斥量复制到寄存器,并将互斥量置为 1 + CMP REGISTER, #0 + JZE ok ;如果互斥量为 0,它被解锁,所以返回 + CALL thread_yield ;互斥量忙,调度另一个线程 + JMP mutex_lock ;稍后再试 +ok: RET + +mutex_unlock: + MOVE MUTEX, #0 ;将互斥量置0 + RET +``` + +* thread_yield 只是调用用户空间线程调度程序,运行十分快捷,这样 mutex_lock 和 mutex_unlock 都不需要任何内核调用。用户级线程通过互斥量的这个过程即可实现同步,而同步过程仅需要少量指令 + +## 管程(monitor) + +* 如果把生产者代码中的两个 down 操作交换顺序,使得 mutex 在 empty 之前减 1,就会导致死锁,因此使用信号量要十分小心。为了更易于编写正确的程序,Brinch Hansen 和 Hoare 提出了一种称为管程的高级同步原语 +* 一个管程是由过程、变量、数据结构等组成的一个集合,它们组成一个特殊的模块或软件包,进程可以在任何需要的时候调用管程中的过程,但不能在管程之外声明的过程中直接访问管程内的数据结构 +* 任一时刻管程中只能有一个活跃进程,这一特性使得管程能有效地完成互斥。管程是编程语言的组成部分,编译器知道其特殊性,进入管程时的互斥由编译器负责,通常做法是使用互斥量或二元信号量。这样就不需要程序员安排互斥,出错的可能性就小很多 +* 管程提供了互斥的简便途径,但此外还需要一种方法使得进程在无法继续运行时被阻塞,这个方法就是引入条件变量(condition variable) +* 当一个管程过程发现它无法继续运行时(如生产者发现缓冲区满),则会在某个条件变量(如 full)上执行 wait 操作,该操作将阻塞当前进程,并将另一个在管程外的进程调入管程。另一个进程可以通过对同一条件变量执行 signal 操作唤醒阻塞进程 +* 为了避免管程中有两个活跃进程,执行 signal 操作之后有两种规则。Hoare 建议让新唤醒的进程运行,挂起另一个进程。Brinch Hansen 建议执行 signal 的进程必须立即退出管程,即 signal 语句只能作为一个管程过程的最后一条语句。后者在概念上更简单,并且更容易实现。第三种方法是,让发信号者继续运行,直到其退出管程,才允许等待的进程开始运行 +* 如果一个条件变量上有若干进程正在等待,则对其执行 signal 操作之后,系统调度程序只能选择其中一个恢复运行 +* 如果一个条件变量没有等待进程,则对其执行 signal 会丢失信号,因此 wait 操作必须在 signal 之前。这与之前提到的 sleep 和 wakeup 的关键区别是,管程的自动互斥保证了在 wait 完成之前不会先 signal + +## 消息传递(message passing) + +* 管程和信号量通过共享内存解决 CPU 互斥问题,但没有提供不同机器间(比如局域网中的机器)的信息交换方法 +* 消息传递使用 send 和 receive 原语来实现进程间通信,它们像信号量而不像管程,是系统调用而非语言成分 + +```cpp +send(destination, &message); +receive(source, &message); +``` + +* send 向一个给定目标发送一条消息,receive 从一个给定源(或者任意源)接收一条消息,如果没有消息可用则接收者可能被阻塞直至有一条消息到达,或者带着一个错误码立即返回 +* 消息传递系统面临许多设计难点:比如消息可能被网络丢失,需要三次握手来确认信息到达情况;比如发送方未收到确认,因此重发消息导致接收方收到两条相同消息,接收方需要区分新老消息;比如身份认证(authentication)问题,客户端如何确认通信的是一个文件服务器还是冒充者 +* 消息传递方式可以有许多变体,一种对消息进行编址的方式是,为每个进程分配一个唯一地址,让消息按进程的地址编址。另一种方式是引入一种称为信箱(mailbox)的数据结构,用来对一定数量的消息进行缓冲。使用信箱时,send 和 receive 调用的地址参数就是信箱而非进程的地址 + +```cpp +constexpr int N = 100; + +void producer() { + message m; // 消息缓冲区 + + while (true) { + int item = produce_item(); + receive(consumer, &m); // 等待消费者发送空缓冲区 + build_message(&m, item); // 建立一个待发送的消息 + send(consumer, &m); // 发送数据项给消费者 + } +} + +void consumer() { + message m; + + for (int i = 0; i < N; ++i) { + send(producer, &m); // 发送 N 个空缓冲区 + } + + while (true) { + receive(producer, &m); // 接收包含数据项的消息 + int item = extract_item(&m); // 将数据项从消息中提取出来 + send(producer, &m); // 将空缓冲区发送回生产者 + consume_item(item); + } +} +``` + +* 使用信箱的另一种极端方法是彻底取消缓冲。采取这种方法时,如果 send 在 receive 之前执行则发送进程被阻塞,直到 receive 发生,反之亦然。执行 receive 时,消息可以直接从发送者复制到接收者,不用任何中间缓冲。这种方案常被称为会和(rendezvous),实现起来更容易,但降低了灵活性,因为发送者和接收者一定要以步步紧接的方式运行 +* 通常在并行程序设计系统中使用消息传递,一个著名的消息传递系统是消息传递接口(Message-Passing Interface,MPI),它广泛应用于科学计算 + +## 屏障(barrier) + +* 屏障是一种用于进程组的同步机制,只有所有进程就绪时才能进入下一阶段。每个阶段的结尾设置一个屏障,当一个进程到达屏障时将被阻拦,直到所有进程到达屏障为止 + +## 调度 + +* 几乎所有进程的 I/O 请求和计算都是交替突发的,如果进程花费大量时间在计算上,则称为计算密集型(compute-bound),如果大量时间花费在等待 I/O 上,则称为 I/O 密集型(I/O-bound) +* 随着 CPU 变得越来越快,更多的进程倾向为 I/O 密集型。这种现象的原因是 CPU 的改进比磁盘的改进快得多,所以未来对 I/O 密集型进程的调度处理更为重要 +* 调度的基本思想是,如果需要运行 I/O 密集型进程,就应该让它尽快得到机会,以便发出磁盘请求并保持磁盘始终忙碌 +* 根据如何处理时钟中断,可以把调度算法分为非抢占式和抢占式两类 +* 非抢占式调度算法挑选一个进程,然后让该进程运行直至阻塞,或直到该进程自动释放 CPU。即使该进程运行了几个小时也不会被强迫挂起,这样导致时钟中断发生时不会进行调度。在处理完时钟中断后,如果没有更高优先级的进程,则被中断的进程将继续运行 +* 抢占式调度算法挑选一个进程,让该进程运行某个固定时段的最大值,时段结束时将挂起该进程,并挑选另一个进程运行。抢占式调度需要在时间间隔的末端发生时钟中断,以便把 CPU 控制返回给调度程序,如果没有可用的时钟,就只能选择非抢占式调度 +* 不同的应用领域有不同的目标,也就需要不同的调度算法。环境可以划分为三种 + * 批处理:广泛用于商业领域,比如处理薪水清单、账目收入、账目支出、利息计算,批处理系统不会有用户在旁边急切等待响应,因此通常使用非抢占式算法,或对每个进程都有长时间周期的抢占式算法,这样减少了进程切换从而改进了性能 + * 交互式:必须使用抢占式算法,以避免 CPU 被一个进程霸占而拒绝为其他进程服务。服务器也归于此类,因为通常要服务多个突发的远程用户 + * 实时:有时不需要抢占,因为进程了解它们可能会长时间得不到运行,所以通常很快地完成各自工作并阻塞 + +## 调度算法的评价指标 + +* 对于批处理系统,调度算法的评价指标主要有三个 + * 吞吐量(throughout):系统单位时间内完成的作业数量,比如 10 道作业花费 100 秒,则吞吐量为 0.1 道/秒 + * 周转时间(turnaround time):一个批处理作业从提交开始到完成的统计平均时间 + * CPU 利用率:CPU 忙碌时间相对总时间的占比 +* 对于交互式系统,评价指标最重要的是最小响应时间,即从发出命令到得到响应之间的时间 +* 实时系统的特点是或多或少必须满足截止时间,多数实时系统中,可预测性十分重要,比如如果多媒体实时系统的音频进程运行错误太多,音质就会明显下降,为此实时系统的调度算法必须是高度可预测和有规律的 + +## 批处理系统中的调度 + +### 先来先服务(First-Come First-Served,FCFS) + +* 非抢占式。进程按照请求 CPU 的先后顺序调度,优点是公平,算法实现简单,不会导致进程饥饿(Starvation,等待时间对进程响应带来明显影响) + +``` +进程 到达时间 运行时间 +P1 0 7 +P2 2 4 +P3 4 1 +P4 5 4 + +先到先服务,因此调度顺序为 P1 -> P2 -> P3 -> P4 +P1 P2 P3 P4 +------- ---- - ---- + +周转时间 = 完成时间 - 到达时间 +P1 = 7 - 0 = 7 +P2 = 11 - 2 = 9 +P3 = 12 - 4 = 8 // 只运行 1,却需要等待 8,可见 FCFS 算法对短作业不利 +P4 = 16 - 5 = 11 +平均周转时间 = 8.75 + +带权周转时间 = 周转时间 / 运行时间 +P1 = 7 / 7 = 1 +P2 = 9 / 4 = 2.25 +P3 = 8 / 1 = 8 +P4 = 11 / 4 = 2.75 +平均带权周转时间 = 3.5 + +等待时间 = 周转时间 - 运行时间(不考虑等待 I/O 操作的时间) +P1 = 7 - 7 = 0 +P2 = 9 - 4 = 5 +P3 = 8 - 1 = 7 +P4 = 11 - 4 = 7 +平均等待时间 = 4.75 +``` + +### 最短作业优先(Shortest Job First,SJF) + +* 非抢占式。选择已到达的且运行时间最短的进程,运行时间相同则先到达的先运行。目标是追求最短的平均周转时间、平均带权周转时间、平均等待时间,缺点是不公平,对短作业有利,对长作业不利,如果一直有短作业到达可能导致长作业饥饿 + +``` +进程 到达时间 运行时间 +P1 0 7 +P2 2 4 +P3 4 1 +P4 5 4 + +P1 先到达,P1 运行结束时 P2、P3、P4 均到达,P3 运行时间最短先运行 +P2、P4 运行时间相同,P2 先到达,因此 P2 先于 P4 运行 + +最终调度顺序为 P1 -> P3 -> P2 -> P4 +P1 P3 P2 P4 +------- - ---- ---- + +周转时间 = 完成时间 - 到达时间 +P1 = 7 - 0 = 7 +P2 = 12 - 2 = 10 +P3 = 8 - 4 = 4 +P4 = 16 - 5 = 11 +平均周转时间 = 8 + +带权周转时间 = 周转时间 / 运行时间 +P1 = 7 / 7 = 1 +P2 = 10 / 4 = 2.5 +P3 = 4 / 1 = 4 +P4 = 11 / 4 = 2.75 +平均带权周转时间 = 2.56 + +等待时间 = 周转时间 - 运行时间(不考虑等待 I/O 操作的时间) +P1 = 7 - 7 = 0 +P2 = 10 - 4 = 6 +P3 = 4 - 1 = 3 +P4 = 11 - 4 = 7 +平均等待时间 = 4 +``` + +### 最短剩余时间优先(Shortest Remaining Time Next,SRTN) + +* SRTN 是 SJF 的抢占式版本,每当新进程加入时,调度程序总是选择剩余运行时间最短的进程运行,如果当前进程剩余运行时间比新进程长,则挂起当前进程而运行新进程 + +``` +进程 到达时间 运行时间 +P1 0 7 +P2 2 4 +P3 4 1 +P4 5 4 + +P2 到达时,P1 剩余 5,P2 为 4,运行 P2 +P3 到达时,P1 剩余 5,P2 剩余 2,P3 为 1,运行 P3 +P4 到达时,P3 运行结束,P1 剩余 5,P2 剩余 2,P4 为 4,运行 P2 +最后依次运行 P4 和 P1 + +最终调度顺序为 P1 -> P2 -> P3 -> P2 -> P4 -> P1 +P1 P2 P3 P2 P4 P1 +-- -- - -- ---- ----- + +周转时间 = 完成时间 - 到达时间 +P1 = 16 - 0 = 16 +P2 = 7 - 2 = 5 +P3 = 5 - 4 = 1 +P4 = 11 - 5 = 6 +平均周转时间 = 7 + +带权周转时间 = 周转时间 / 运行时间 +P1 = 16 / 7 = 2.29 +P2 = 5 / 4 = 1.25 +P3 = 1 / 1 = 1 +P4 = 6 / 4 = 1.5 +平均带权周转时间 = 1.51 + +等待时间 = 周转时间 - 运行时间(不考虑等待 I/O 操作的时间) +P1 = 16 - 7 = 9 +P2 = 5 - 4 = 1 +P3 = 1 - 1 = 0 +P4 = 6 - 4 = 2 +平均等待时间 = 3 +``` + +### 高响应比优先(Highest Response Ratio Next,HRRN) + +* 非抢占式。在所有已到达进程中选择响应比(`等待时间 / 运行时间 + 1`)最高的运行,综合 FCFS 和 SJF 的优点,等待时间长、运行时间短的优先,避免长作业饥饿的问题 + +``` +进程 到达时间 运行时间 +P1 0 7 +P2 2 4 +P3 4 1 +P4 5 4 + +响应比 = (等待时间 + 运行时间) / 运行时间 +P1 运行至结束,P2、P3、P4 均到达,响应比分别为 +P2 = (5 + 4) / 4 = 2.25 +P3 = (3 + 1) / 1 = 4 +P4 = (2 + 4) / 4 = 1.5 +运行 P3,P3 结束时,响应比分别为 +P2 = (6 + 4) / 4 = 2.5 +P4 = (3 + 4) / 4 = 1.75 +运行 P2,最后运行 P4 + +最终调度顺序为 P1 -> P3 -> P2 -> P4 +P1 P3 P2 P4 +------- - ---- ---- +``` + +## 交互式系统中的调度 + +### 时间片轮转调度(Round-Robin Scheduling,RR) + +* RR 是一种简单公平的抢占式调度算法,并且可以避免饥饿。每个进程被分配一个时间片(quantum)。时间片结束时,如果进程还在运行,则剥夺 CPU 并分配给另一个进程。如果进程在时间片结束前阻塞或结束,则 CPU 立即切换。RR 算法实现很容易,只需要维护一张进程队列表 + +``` +A -> B -> C -> D + +若 A 用完时间片,但仍在运行,则插入到队列尾 +B -> C -> D -> A + +若 B 用完时间片,但仍在运行,并到达一个新进程 E,则先插入新进程 +C -> D -> A -> E -> B + +若 C 用完时间片之前就结束了,则直接切换到下一个进程 +D -> A -> E -> B +``` + +* 需要考虑的是时间片的长度,假设时间片为 4 ms,上下文切换为 1 ms,则 CPU 完成 4 ms 工作后将浪费 1 ms 进行上下文切换(context switch),即浪费了 20% 的时间。但如果时间片太大,就会退化为 FCFS,导致增大响应时间。通常为了提高 CPU 效率,设置时间片时,切换开销占比应不超过 1% + +### 优先级调度 + +* 为每个进程设置优先级,在已到达进程中,选择优先级最高的运行,可以为抢占式或非抢占式 +* 比如对于操作系统来说,I/O 密集型进程的优先级应该更高。I/O 密集型继承多数时间用于等待 I/O 结束,因此需要 CPU 时应立即分配给它以便启动下一个 I/O 请求,这样就可以在另一个进程计算的同时执行 I/O 操作 +* 一种简单做法是将优先级设置为 `1 / f`,`f` 为该进程在上一时间片中的运行时间占比。比如在 50 ms 时间片中,使用 1 ms 的进程优先级为 50,使用 25 ms 的进程优先级为 2。将进程按优先级分组,再使用 RR 算法调度高优先级组中的进程 + +### 多级反馈队列调度 + +* CTSS(Compatible Time Sharing System)是最早使用优先级调度的系统之一,但存在进程切换速度太慢的问题,其设计者意识到设置较长的时间片可以减少切换次数,但长时间片又会影响到响应时间。最终的解决方法是多级反馈队列调度,它是对 FCFS、SJF、RR、优先级调度的折中权衡 +* 设置多个优先级队列,每个级别对应不同长度的时间片,比如第一级(最高级)时间片为 1,第二级为 2,第三级为 4,以此类推 +* 如果一个进程用完当前级别时间片后仍未运行完,则加入下一级队列队尾,如果已经位于最后一级则放回该级队尾 +* 高优先级队列为空时,才会调度低优先级队列,因此可能导致低优先级进程饥饿 +* 比如一个进程需要 100 个时间片,第一次分配 1 个时间片,第二次分配 2 个,接下来是 4、8、16、32、64,最后一次使用 64 中的 37 个即可结束工作,一共进行 7 次切换。如果使用 RR 算法,则需要 100 次切换 + +### 最短进程优先 + +* 关键在于如何从可运行进程中找出最短的一个 +* 一种方法是根据过去的行为进行预测。假设某终端每条命令的估计运行时间为 `T0`,测量到下一次运行时间为 `T1`,则估计时间可以修正为 `a * T0 + (1 - a) * T1`,比如设 `a` 为 `1 / 2` 可以得到序列如下 + +``` +T0 +T0/2 + T1/2 +T0/4 + T1/4 + T2/2 +T0/8 + T1/8 + T2/4 + T3/2 // T0 在此时估计时间中的占比下降到 1/8 +``` + +### 保证调度 + +* 向用户作出明确的性能保证,然后实现它。比如有 `n` 个进程运行的单用户系统中,如果所有进程等价,则每个进程获得 `1 / n` 的 CPU 时间,为了实现所作的保证,系统跟踪每个进程已使用的 CPU 时间,并计算应获得的时间,然后转向已用时间最少的进程,直到超过最接近的竞争者 + +### 彩票调度(Lottery Scheduling) + +* 保证调度的想法不错,但很难实现。彩票调度既可以给出类似预测结果,并且实现非常简单。其基本思想是为进程提供各种系统资源(如 CPU 时间)的彩票,一旦需要做出调度决策时,就随机抽出一张彩票,拥有该彩票的进程获取该资源 +* 比如系统掌握每秒 50 次的一种彩票,作为奖励每个获奖者可以获得 20 ms 的 CPU 时间 +* 可以给更重要的进程额外的彩票,以增加其获胜的机会,比如出售 100 张彩票,一个进程持有其中 20 张,则每次抽奖该进程就有 20% 的取胜机会,在较长运行时间中该进程就会得到 20% 的 CPU +* 彩票调度可以解决其他方法很难解决的问题,比如一个视频服务器上有若干提供视频流的进程,每个流的帧率不同,假设帧率分别为 10、20、25,那么给这些进程分别分配 10、20、25 张彩票,它们就会自动按照接近 10:20:25 的比例划分 CPU 的使用 + +### 公平分享调度 + +* 之前的调度关注的都是进程本身,而没有关注进程所有者。假设两个用户分别启动 9 个进程和 1 个进程,使用 RR 算法,则两者分别得到 90% 和 10% 的 CPU 时间。为了避免这种情况,在调度处理之前应该考虑进程拥有者 diff --git a/src/atm.cpp b/src/atm.cpp new file mode 100644 index 0000000..7ecf94d --- /dev/null +++ b/src/atm.cpp @@ -0,0 +1,602 @@ +#include +#include +#include +#include +#include +#include +#include + +// MessageQueue +namespace Messaging { +struct MessageBase { + virtual ~MessageBase() = default; +}; + +template +struct WrappedMessage : MessageBase { + explicit WrappedMessage(const Msg& msg) : contents(msg) {} + Msg contents; +}; + +class MessageQueue { + public: + template + void push(const T& msg) { + std::lock_guard l(m_); + q_.push(std::make_shared>(msg)); + cv_.notify_all(); + } + + std::shared_ptr wait_and_pop() { + std::unique_lock l(m_); + cv_.wait(l, [&] { return !q_.empty(); }); + auto res = q_.front(); + q_.pop(); + return res; + } + + private: + std::mutex m_; + std::condition_variable cv_; + std::queue> q_; +}; +} // namespace Messaging + +// TemplateDispatcher +namespace Messaging { +template +class TemplateDispatcher { + public: + TemplateDispatcher(const TemplateDispatcher&) = delete; + + TemplateDispatcher& operator=(const TemplateDispatcher&) = delete; + + TemplateDispatcher(TemplateDispatcher&& rhs) noexcept + : q_(rhs.q_), + prev_(rhs.prev_), + f_(std::move(rhs.f_)), + chained_(rhs.chained_) { + rhs.chained_ = true; + } + + TemplateDispatcher(MessageQueue* q, PreviousDispatcher* prev, F&& f) + : q_(q), prev_(prev), f_(std::forward(f)) { + prev->chained_ = true; + } + + template + TemplateDispatcher handle(OtherF&& f) { + return TemplateDispatcher( + q_, this, std::forward(f)); + } + + ~TemplateDispatcher() noexcept(false) { // 所有调度器都可能抛出异常 + if (!chained_) { + wait_and_dispatch(); // 析构函数中完成任务调度 + } + } + + private: + template + friend class TemplateDispatcher; // TemplateDispatcher 实例互为友元 + + void wait_and_dispatch() { + while (true) { + auto msg = q_->wait_and_pop(); + if (dispatch(msg)) { + break; // 消息被处理后则退出循环 + } + } + } + + bool dispatch(const std::shared_ptr& msg) { + if (auto wrapper = dynamic_cast*>(msg.get())) { + f_(wrapper->contents); + return true; + } + // 如果消息类型不匹配,则链接到前一个 dispatcher + return prev_->dispatch(msg); + } + + private: + MessageQueue* q_ = nullptr; + PreviousDispatcher* prev_ = nullptr; + F f_; + bool chained_ = false; +}; +} // namespace Messaging + +// Dispatcher +namespace Messaging { +class CloseQueue {}; // 用于关闭队列的消息 + +class Dispatcher { + public: + Dispatcher(const Dispatcher&) = delete; + + Dispatcher& operator=(const Dispatcher&) = delete; + + Dispatcher(Dispatcher&& rhs) noexcept : q_(rhs.q_), chained_(rhs.chained_) { + rhs.chained_ = true; + } + + explicit Dispatcher(MessageQueue* q) : q_(q) {} + + template + TemplateDispatcher handle(F&& f) { + // 用 TemplateDispatcher 处理特定类型的消息 + return TemplateDispatcher(q_, this, std::forward(f)); + } + + ~Dispatcher() noexcept(false) { // 可能抛出 CloseQueue 异常 + if (!chained_) { // 从 Receiver::wait 返回的 dispatcher 实例会马上被析构 + wait_and_dispatch(); // 析构函数中完成任务调度 + } + } + + private: + template + friend class TemplateDispatcher; + + void wait_and_dispatch() { + while (true) { + auto msg = q_->wait_and_pop(); + dispatch(msg); + } + } + + bool dispatch(const std::shared_ptr& msg) { + if (dynamic_cast*>(msg.get())) { + throw CloseQueue(); + } + return false; // 返回 false 表示消息未被处理 + } + + private: + MessageQueue* q_ = nullptr; + bool chained_ = false; +}; +} // namespace Messaging + +// Sender +namespace Messaging { +class Sender { + public: + Sender() = default; + + explicit Sender(MessageQueue* q) : q_(q) {} + + template + void send(const Msg& msg) { + if (q_) { + q_->push(msg); + } + } + + private: + MessageQueue* q_ = nullptr; +}; +} // namespace Messaging + +// Receiver +namespace Messaging { +class Receiver { + public: + operator Sender() { // 允许隐式转换为 Sender + return Sender(&q_); + } + + Dispatcher wait() { // 等待对队列的调度 + return Dispatcher(&q_); + } + + private: + MessageQueue q_; +}; +} // namespace Messaging + +// ATM 消息 +struct Withdraw { + Withdraw(const std::string& _account, unsigned _amount, + Messaging::Sender _atm_queue) + : account(_account), amount(_amount), atm_queue(_atm_queue) {} + + std::string account; + unsigned amount; + mutable Messaging::Sender atm_queue; +}; + +struct WithdrawOK {}; + +struct WithdrawDenied {}; + +struct CancelWithdrawal { + CancelWithdrawal(const std::string& _account, unsigned _amount) + : account(_account), amount(_amount) {} + + std::string account; + unsigned amount; +}; + +struct WithdrawalProcessed { + WithdrawalProcessed(const std::string& _account, unsigned _amount) + : account(_account), amount(_amount) {} + + std::string account; + unsigned amount; +}; + +struct CardInserted { + explicit CardInserted(const std::string& _account) : account(_account) {} + + std::string account; +}; + +struct DigitPressed { + explicit DigitPressed(char _digit) : digit(_digit) {} + + char digit; +}; + +struct ClearLastPressed {}; + +struct EjectCard {}; + +struct WithdrawPressed { + explicit WithdrawPressed(unsigned _amount) : amount(_amount) {} + + unsigned amount; +}; + +struct CancelPressed {}; + +struct IssueMoney { + IssueMoney(unsigned _amount) : amount(_amount) {} + + unsigned amount; +}; + +struct VerifyPIN { + VerifyPIN(const std::string& _account, const std::string& pin_, + Messaging::Sender _atm_queue) + : account(_account), pin(pin_), atm_queue(_atm_queue) {} + + std::string account; + std::string pin; + mutable Messaging::Sender atm_queue; +}; + +struct PINVerified {}; + +struct PINIncorrect {}; + +struct DisplayEnterPIN {}; + +struct DisplayEnterCard {}; + +struct DisplayInsufficientFunds {}; + +struct DisplayWithdrawalCancelled {}; + +struct DisplayPINIncorrectMessage {}; + +struct DisplayWithdrawalOptions {}; + +struct GetBalance { + GetBalance(const std::string& _account, Messaging::Sender _atm_queue) + : account(_account), atm_queue(_atm_queue) {} + + std::string account; + mutable Messaging::Sender atm_queue; +}; + +struct Balance { + explicit Balance(unsigned _amount) : amount(_amount) {} + + unsigned amount; +}; + +struct DisplayBalance { + explicit DisplayBalance(unsigned _amount) : amount(_amount) {} + + unsigned amount; +}; + +struct BalancePressed {}; + +// ATM状态机 +class ATM { + public: + ATM(const ATM&) = delete; + + ATM& operator=(const ATM&) = delete; + + ATM(Messaging::Sender bank, Messaging::Sender interface_hardware) + : bank_(bank), interface_hardware_(interface_hardware) {} + + void done() { get_sender().send(Messaging::CloseQueue()); } + + void run() { + state_ = &ATM::waiting_for_card; + try { + while (true) { + (this->*state_)(); + } + } catch (const Messaging::CloseQueue&) { + } + } + + Messaging::Sender get_sender() { return incoming_; } + + private: + void process_withdrawal() { + incoming_.wait() + .handle([&](const WithdrawOK& msg) { + interface_hardware_.send(IssueMoney(withdrawal_amount_)); + bank_.send(WithdrawalProcessed(account_, withdrawal_amount_)); + state_ = &ATM::done_processing; + }) + .handle([&](const WithdrawDenied& msg) { + interface_hardware_.send(DisplayInsufficientFunds()); + state_ = &ATM::done_processing; + }) + .handle([&](const CancelPressed& msg) { + bank_.send(CancelWithdrawal(account_, withdrawal_amount_)); + interface_hardware_.send(DisplayWithdrawalCancelled()); + state_ = &ATM::done_processing; + }); + } + + void process_balance() { + incoming_.wait() + .handle([&](const Balance& msg) { + interface_hardware_.send(DisplayBalance(msg.amount)); + state_ = &ATM::wait_for_action; + }) + .handle( + [&](const CancelPressed& msg) { state_ = &ATM::done_processing; }); + } + + void wait_for_action() { + interface_hardware_.send(DisplayWithdrawalOptions()); + incoming_.wait() + .handle([&](const WithdrawPressed& msg) { + withdrawal_amount_ = msg.amount; + bank_.send(Withdraw(account_, msg.amount, incoming_)); + state_ = &ATM::process_withdrawal; + }) + .handle([&](const BalancePressed& msg) { + bank_.send(GetBalance(account_, incoming_)); + state_ = &ATM::process_balance; + }) + .handle( + [&](const CancelPressed& msg) { state_ = &ATM::done_processing; }); + } + + void verifying_pin() { + incoming_.wait() + .handle( + [&](const PINVerified& msg) { state_ = &ATM::wait_for_action; }) + .handle([&](const PINIncorrect& msg) { + interface_hardware_.send(DisplayPINIncorrectMessage()); + state_ = &ATM::done_processing; + }) + .handle( + [&](const CancelPressed& msg) { state_ = &ATM::done_processing; }); + } + + void getting_pin() { + incoming_.wait() + .handle([&](const DigitPressed& msg) { + const unsigned pin_length = 4; + pin_ += msg.digit; + if (pin_.length() == pin_length) { + bank_.send(VerifyPIN(account_, pin_, incoming_)); + state_ = &ATM::verifying_pin; + } + }) + .handle([&](const ClearLastPressed& msg) { + if (!pin_.empty()) { + pin_.pop_back(); + } + }) + .handle( + [&](const CancelPressed& msg) { state_ = &ATM::done_processing; }); + } + + void waiting_for_card() { + interface_hardware_.send(DisplayEnterCard()); + incoming_.wait().handle([&](const CardInserted& msg) { + account_ = msg.account; + pin_ = ""; + interface_hardware_.send(DisplayEnterPIN()); + state_ = &ATM::getting_pin; + }); + } + + void done_processing() { + interface_hardware_.send(EjectCard()); + state_ = &ATM::waiting_for_card; + } + + private: + Messaging::Receiver incoming_; + Messaging::Sender bank_; + Messaging::Sender interface_hardware_; + void (ATM::*state_)(); + std::string account_; + unsigned withdrawal_amount_; + std::string pin_; +}; + +// 银行状态机 +class BankMachine { + public: + void done() { get_sender().send(Messaging::CloseQueue()); } + + void run() { + try { + while (true) { + incoming_.wait() + .handle([&](const VerifyPIN& msg) { + if (msg.pin == "6666") { // 输入密码为 6666 则通过验证 + msg.atm_queue.send(PINVerified()); + } else { // 否则发送密码错误的消息 + msg.atm_queue.send(PINIncorrect()); + } + }) + .handle([&](const Withdraw& msg) { // 取钱 + if (balance_ >= msg.amount) { + msg.atm_queue.send(WithdrawOK()); + balance_ -= msg.amount; + } else { + msg.atm_queue.send(WithdrawDenied()); + } + }) + .handle([&](const GetBalance& msg) { + msg.atm_queue.send(::Balance(balance_)); + }) + .handle([&](const WithdrawalProcessed& msg) {}) + .handle([&](const CancelWithdrawal& msg) {}); + } + } catch (const Messaging::CloseQueue&) { + } + } + + Messaging::Sender get_sender() { return incoming_; } + + private: + Messaging::Receiver incoming_; + unsigned balance_ = 199; +}; + +// 用户接口状态机 +class InterfaceMachine { + public: + void done() { get_sender().send(Messaging::CloseQueue()); } + + void run() { + try { + while (true) { + incoming_.wait() + .handle([&](const IssueMoney& msg) { + { + std::lock_guard l(m_); + std::cout << "Issuing " << msg.amount << std::endl; + } + }) + .handle( + [&](const DisplayInsufficientFunds& msg) { + { + std::lock_guard l(m_); + std::cout << "Insufficient funds" << std::endl; + } + }) + .handle([&](const DisplayEnterPIN& msg) { + { + std::lock_guard l(m_); + std::cout << "Please enter your PIN (0-9)" << std::endl; + } + }) + .handle([&](const DisplayEnterCard& msg) { + { + std::lock_guard l(m_); + std::cout << "Please enter your card (I)" << std::endl; + } + }) + .handle([&](const DisplayBalance& msg) { + { + std::lock_guard l(m_); + std::cout << "The Balance of your account is " << msg.amount + << std::endl; + } + }) + .handle( + [&](const DisplayWithdrawalOptions& msg) { + { + std::lock_guard l(m_); + std::cout << "Withdraw 50? (w)" << std::endl; + std::cout << "Display Balance? (b)" << std::endl; + std::cout << "Cancel? (c)" << std::endl; + } + }) + .handle( + [&](const DisplayWithdrawalCancelled& msg) { + { + std::lock_guard l(m_); + std::cout << "Withdrawal cancelled" << std::endl; + } + }) + .handle( + [&](const DisplayPINIncorrectMessage& msg) { + { + std::lock_guard l(m_); + std::cout << "PIN incorrect" << std::endl; + } + }) + .handle([&](const EjectCard& msg) { + { + std::lock_guard l(m_); + std::cout << "Ejecting card" << std::endl; + } + }); + } + } catch (Messaging::CloseQueue&) { + } + } + + Messaging::Sender get_sender() { return incoming_; } + + private: + Messaging::Receiver incoming_; + std::mutex m_; +}; + +int main() { + BankMachine bank; + InterfaceMachine interface_hardware; + ATM machine{bank.get_sender(), interface_hardware.get_sender()}; + std::thread bank_thread{&BankMachine::run, &bank}; + std::thread interface_thread{&InterfaceMachine::run, &interface_hardware}; + std::thread atm_thread{&ATM::run, &machine}; + Messaging::Sender atm_queue{machine.get_sender()}; + bool quit = false; + while (!quit) { + char c = getchar(); + switch (c) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + atm_queue.send(DigitPressed(c)); + break; + case 'b': // 显示余额 + atm_queue.send(BalancePressed()); + break; + case 'w': // 取钱 + atm_queue.send(WithdrawPressed(50)); + break; + case 'c': // 退卡 + atm_queue.send(CancelPressed()); + break; + case 'q': // 结束程序 + quit = true; + break; + case 'i': // 插卡 + atm_queue.send(CardInserted("downdemo")); + break; + } + } + bank.done(); + machine.done(); + interface_hardware.done(); + atm_thread.join(); + bank_thread.join(); + interface_thread.join(); +} diff --git a/src/concurrent_list.hpp b/src/concurrent_list.hpp new file mode 100644 index 0000000..20ef5c3 --- /dev/null +++ b/src/concurrent_list.hpp @@ -0,0 +1,84 @@ +#pragma once + +#include +#include +#include + +template +class ConcurrentList { + public: + ConcurrentList() = default; + + ~ConcurrentList() { + remove_if([](const Node&) { return true; }); + } + + ConcurrentList(const ConcurrentList&) = delete; + + ConcurrentList& operator=(const ConcurrentList&) = delete; + + void push_front(const T& x) { + std::unique_ptr t(new Node(x)); + std::lock_guard head_lock(head_.m); + t->next = std::move(head_.next); + head_.next = std::move(t); + } + + template + void for_each(F f) { + Node* cur = &head_; + std::unique_lock head_lock(head_.m); + while (Node* const next = cur->next.get()) { + std::unique_lock next_lock(next->m); + head_lock.unlock(); // 锁住了下一节点,因此可以释放上一节点的锁 + f(*next->data); + cur = next; // 当前节点指向下一节点 + head_lock = std::move(next_lock); // 转交下一节点锁的所有权,循环上述过程 + } + } + + template + std::shared_ptr find_first_if(F f) { + Node* cur = &head_; + std::unique_lock head_lock(head_.m); + while (Node* const next = cur->next.get()) { + std::unique_lock next_lock(next->m); + head_lock.unlock(); + if (f(*next->data)) { + return next->data; // 返回目标值,无需继续查找 + } + cur = next; + head_lock = std::move(next_lock); + } + return nullptr; + } + + template + void remove_if(F f) { + Node* cur = &head_; + std::unique_lock head_lock(head_.m); + while (Node* const next = cur->next.get()) { + std::unique_lock next_lock(next->m); + if (f(*next->data)) { // 为 true 则移除下一节点 + std::unique_ptr old_next = std::move(cur->next); + cur->next = std::move(next->next); // 下一节点设为下下节点 + next_lock.unlock(); + } else { // 否则继续转至下一节点 + head_lock.unlock(); + cur = next; + head_lock = std::move(next_lock); + } + } + } + + private: + struct Node { + std::mutex m; + std::shared_ptr data; + std::unique_ptr next; + Node() = default; + Node(const T& x) : data(std::make_shared(x)) {} + }; + + Node head_; +}; diff --git a/src/concurrent_map.hpp b/src/concurrent_map.hpp new file mode 100644 index 0000000..15114fe --- /dev/null +++ b/src/concurrent_map.hpp @@ -0,0 +1,92 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +template > +class ConcurrentMap { + public: + // 桶数默认为 19(一般用 x % 桶数作为 x 的桶索引,桶数为质数可使桶分布均匀) + ConcurrentMap(std::size_t n = 19, const Hash& h = Hash{}) + : buckets_(n), hasher_(h) { + for (auto& x : buckets_) { + x.reset(new Bucket); + } + } + + ConcurrentMap(const ConcurrentMap&) = delete; + + ConcurrentMap& operator=(const ConcurrentMap&) = delete; + + V get(const K& k, const V& default_value = V{}) const { + return get_bucket(k).get(k, default_value); + } + + void set(const K& k, const V& v) { get_bucket(k).set(k, v); } + + void erase(const K& k) { get_bucket(k).erase(k); } + + // 为了方便使用,提供一个到 std::map 的映射 + std::map to_map() const { + std::vector> locks; + for (auto& x : buckets_) { + locks.emplace_back(std::unique_lock(x->m)); + } + std::map res; + for (auto& x : buckets_) { + for (auto& y : x->data) { + res.emplace(y); + } + } + return res; + } + + private: + struct Bucket { + std::list> data; + mutable std::shared_mutex m; // 每个桶都用这个锁保护 + + V get(const K& k, const V& default_value) const { + // 没有修改任何值,异常安全 + std::shared_lock l(m); // 只读锁,可共享 + auto it = std::find_if(data.begin(), data.end(), + [&](auto& x) { return x.first == k; }); + return it == data.end() ? default_value : it->second; + } + + void set(const K& k, const V& v) { + std::unique_lock l(m); // 写,单独占用 + auto it = std::find_if(data.begin(), data.end(), + [&](auto& x) { return x.first == k; }); + if (it == data.end()) { + data.emplace_back(k, v); // emplace_back 异常安全 + } else { + it->second = v; // 赋值可能抛异常,但值是用户提供的,可放心让用户处理 + } + } + + void erase(const K& k) { + std::unique_lock l(m); // 写,单独占用 + auto it = std::find_if(data.begin(), data.end(), + [&](auto& x) { return x.first == k; }); + if (it != data.end()) { + data.erase(it); + } + } + }; + + Bucket& get_bucket(const K& k) const { // 桶数固定因此可以无锁调用 + return *buckets_[hasher_(k) % buckets_.size()]; + } + + private: + std::vector> buckets_; + Hash hasher_; +}; diff --git a/src/concurrent_queue.hpp b/src/concurrent_queue.hpp new file mode 100644 index 0000000..27fc8c9 --- /dev/null +++ b/src/concurrent_queue.hpp @@ -0,0 +1,110 @@ +#pragma once + +#include +#include +#include +#include + +template +class ConcurrentQueue { + public: + ConcurrentQueue() : head_(new Node), tail_(head_.get()) {} + + ConcurrentQueue(const ConcurrentQueue&) = delete; + + ConcurrentQueue& operator=(const ConcurrentQueue&) = delete; + + void push(T x) { + auto new_val = std::make_shared(std::move(x)); + auto new_node = std::make_unique(); + Node* new_tail_node = new_node.get(); + { + std::lock_guard l(tail_mutex_); + tail_->v = new_val; + tail_->next = std::move(new_node); + tail_ = new_tail_node; + } + cv_.notify_one(); + } + + std::shared_ptr try_pop() { + std::unique_ptr head_node = try_pop_head(); + return head_node ? head_node->v : nullptr; + } + + bool try_pop(T& res) { + std::unique_ptr head_node = try_pop_head(res); + return head_node != nullptr; + } + + std::shared_ptr wait_and_pop() { + std::unique_ptr head_node = wait_pop_head(); + return head_node->v; + } + + void wait_and_pop(T& res) { wait_pop_head(res); } + + bool empty() const { + std::lock_guard l(head_mutex_); + return head_.get() == get_tail(); + } + + private: + struct Node { + std::shared_ptr v; + std::unique_ptr next; + }; + + private: + std::unique_ptr try_pop_head() { + std::lock_guard l(head_mutex_); + if (head_.get() == get_tail()) { + return nullptr; + } + return pop_head(); + } + + std::unique_ptr try_pop_head(T& res) { + std::lock_guard l(head_mutex_); + if (head_.get() == get_tail()) { + return nullptr; + } + res = std::move(*head_->v); + return pop_head(); + } + + std::unique_ptr wait_pop_head() { + std::unique_lock l(wait_for_data()); + return pop_head(); + } + + std::unique_ptr wait_pop_head(T& res) { + std::unique_lock l(wait_for_data()); + res = std::move(*head_->v); + return pop_head(); + } + + std::unique_lock wait_for_data() { + std::unique_lock l(head_mutex_); + cv_.wait(l, [this] { return head_.get() != get_tail(); }); + return l; + } + + std::unique_ptr pop_head() { + std::unique_ptr head_node = std::move(head_); + head_ = std::move(head_node->next); + return head_node; + } + + Node* get_tail() { + std::lock_guard l(tail_mutex_); + return tail_; + } + + private: + std::unique_ptr head_; + Node* tail_ = nullptr; + std::mutex head_mutex_; + mutable std::mutex tail_mutex_; + std::condition_variable cv_; +}; diff --git a/src/concurrent_stack.hpp b/src/concurrent_stack.hpp new file mode 100644 index 0000000..d15500e --- /dev/null +++ b/src/concurrent_stack.hpp @@ -0,0 +1,57 @@ +#pragma once + +#include +#include +#include +#include +#include + +struct EmptyStack : std::exception { + const char* what() const noexcept { return "empty stack!"; } +}; + +template +class ConcurrentStack { + public: + ConcurrentStack() = default; + + ConcurrentStack(const ConcurrentStack& rhs) { + std::lock_guard l(rhs.m_); + s_ = rhs.s_; + } + + ConcurrentStack& operator=(const ConcurrentStack&) = delete; + + void push(T x) { + std::lock_guard l(m_); + s_.push(std::move(x)); + } + + bool empty() const { + std::lock_guard l(m_); + return s_.empty(); + } + + std::shared_ptr pop() { + std::lock_guard l(m_); + if (s_.empty()) { + throw EmptyStack(); + } + auto res = std::make_shared(std::move(s_.top())); + s_.pop(); + return res; + } + + void pop(T& res) { + std::lock_guard l(m_); + if (s_.empty()) { + throw EmptyStack(); + } + res = std::move(s_.top()); + s_.pop(); + } + + private: + mutable std::mutex m_; + std::stack s_; +}; diff --git a/src/hierarchical_mutex.cpp b/src/hierarchical_mutex.cpp new file mode 100644 index 0000000..ce808f8 --- /dev/null +++ b/src/hierarchical_mutex.cpp @@ -0,0 +1,90 @@ +#include +#include +#include + +class HierarchicalMutex { + public: + explicit HierarchicalMutex(int hierarchy_value) + : cur_hierarchy_(hierarchy_value), prev_hierarchy_(0) {} + + void lock() { + validate_hierarchy(); // 层级错误则抛异常 + m_.lock(); + update_hierarchy(); + } + + bool try_lock() { + validate_hierarchy(); + if (!m_.try_lock()) { + return false; + } + update_hierarchy(); + return true; + } + + void unlock() { + if (thread_hierarchy_ != cur_hierarchy_) { + throw std::logic_error("mutex hierarchy violated"); + } + thread_hierarchy_ = prev_hierarchy_; // 恢复前一线程的层级值 + m_.unlock(); + } + + private: + void validate_hierarchy() { + if (thread_hierarchy_ <= cur_hierarchy_) { + throw std::logic_error("mutex hierarchy violated"); + } + } + + void update_hierarchy() { + // 先存储当前线程的层级值(用于解锁时恢复) + prev_hierarchy_ = thread_hierarchy_; + // 再把其设为锁的层级值 + thread_hierarchy_ = cur_hierarchy_; + } + + private: + std::mutex m_; + const int cur_hierarchy_; + int prev_hierarchy_; + static thread_local int thread_hierarchy_; // 所在线程的层级值 +}; + +// static thread_local 表示存活于一个线程周期 +thread_local int HierarchicalMutex::thread_hierarchy_(INT_MAX); + +HierarchicalMutex high(10000); +HierarchicalMutex mid(6000); +HierarchicalMutex low(5000); + +void lf() { // 最低层函数 + std::lock_guard l(low); + // 调用 low.lock(),thread_hierarchy_ 为 INT_MAX, + // cur_hierarchy_ 为 5000,thread_hierarchy_ > cur_hierarchy_, + // 通过检查,上锁,prev_hierarchy_ 更新为 INT_MAX, + // thread_hierarchy_ 更新为 5000 +} // 调用 low.unlock(),thread_hierarchy_ == cur_hierarchy_, +// 通过检查,thread_hierarchy_ 恢复为 prev_hierarchy_ 保存的 INT_MAX,解锁 + +void hf() { + std::lock_guard l(high); // high.cur_hierarchy_ 为 10000 + // thread_hierarchy_ 为 10000,可以调用低层函数 + lf(); // thread_hierarchy_ 从 10000 更新为 5000 + // thread_hierarchy_ 恢复为 10000 +} // thread_hierarchy_ 恢复为 INT_MAX + +void mf() { + std::lock_guard l(mid); // thread_hierarchy_ 为 6000 + hf(); // thread_hierarchy_ < high.cur_hierarchy_,违反了层级结构,抛异常 +} + +int main() { + lf(); + hf(); + try { + mf(); + } catch (std::logic_error& ex) { + std::cout << ex.what(); + } +} diff --git a/src/lock_free_stack.hpp b/src/lock_free_stack.hpp new file mode 100644 index 0000000..2dd74c3 --- /dev/null +++ b/src/lock_free_stack.hpp @@ -0,0 +1,82 @@ +#pragma once + +#include +#include + +template +class LockFreeStack { + public: + ~LockFreeStack() { + while (pop()) { + } + } + + void push(const T& x) { + ReferenceCount t; + t.p = new Node(x); + t.external_cnt = 1; + // 下面比较中 release 保证之前的语句都先执行,因此 load 可以使用 relaxed + t.p->next = head_.load(std::memory_order_relaxed); + while (!head_.compare_exchange_weak(t.p->next, t, std::memory_order_release, + std::memory_order_relaxed)) { + } + } + + std::shared_ptr pop() { + ReferenceCount t = head_.load(std::memory_order_relaxed); + while (true) { + increase_count(t); // acquire + Node* p = t.p; + if (!p) { + return nullptr; + } + if (head_.compare_exchange_strong(t, p->next, + std::memory_order_relaxed)) { + std::shared_ptr res; + res.swap(p->v); + // 将外部计数减 2 后加到内部计数,减 2 是因为, + // 节点被删除减 1,该线程无法再次访问此节点再减 1 + const int cnt = t.external_cnt - 2; + // swap 要先于 delete,因此使用 release + if (p->inner_cnt.fetch_add(cnt, std::memory_order_release) == -cnt) { + delete p; // 内外部计数和为 0 + } + return res; + } + if (p->inner_cnt.fetch_sub(1, std::memory_order_relaxed) == 1) { + p->inner_cnt.load(std::memory_order_acquire); // 只是用 acquire 来同步 + // acquire 保证 delete 在之后执行 + delete p; // 内部计数为 0 + } + } + } + + private: + struct Node; + + struct ReferenceCount { + int external_cnt; + Node* p = nullptr; + }; + + struct Node { + std::shared_ptr v; + std::atomic inner_cnt = 0; + ReferenceCount next; + Node(const T& x) : v(std::make_shared(x)) {} + }; + + void increase_count(ReferenceCount& old_cnt) { + ReferenceCount new_cnt; + do { // 比较失败不改变当前值,并可以继续循环,因此可以选择 relaxed + new_cnt = old_cnt; + ++new_cnt.external_cnt; // 访问 head_ 时递增外部计数,表示该节点正被使用 + } while (!head_.compare_exchange_strong(old_cnt, new_cnt, + std::memory_order_acquire, + std::memory_order_relaxed)); + old_cnt.external_cnt = new_cnt.external_cnt; + } + + private: + std::atomic head_; +}; diff --git a/src/lock_free_stack_hazard_pointer.hpp b/src/lock_free_stack_hazard_pointer.hpp new file mode 100644 index 0000000..4492231 --- /dev/null +++ b/src/lock_free_stack_hazard_pointer.hpp @@ -0,0 +1,142 @@ +#pragma once + +#include +#include +#include +#include +#include + +static constexpr std::size_t MaxSize = 100; + +struct HazardPointer { + std::atomic id; + std::atomic p; +}; + +static HazardPointer HazardPointers[MaxSize]; + +class HazardPointerHelper { + public: + HazardPointerHelper() { + for (auto& x : HazardPointers) { + std::thread::id default_id; + if (x.id.compare_exchange_strong(default_id, + std::this_thread::get_id())) { + hazard_pointer = &x; // 取一个未设置过的风险指针 + break; + } + } + if (!hazard_pointer) { + throw std::runtime_error("No hazard pointers available"); + } + } + + ~HazardPointerHelper() { + hazard_pointer->p.store(nullptr); + hazard_pointer->id.store(std::thread::id{}); + } + + HazardPointerHelper(const HazardPointerHelper&) = delete; + + HazardPointerHelper operator=(const HazardPointerHelper&) = delete; + + std::atomic& get() { return hazard_pointer->p; } + + private: + HazardPointer* hazard_pointer = nullptr; +}; + +std::atomic& hazard_pointer_for_this_thread() { + static thread_local HazardPointerHelper t; + return t.get(); +} + +bool is_existing(void* p) { + for (auto& x : HazardPointers) { + if (x.p.load() == p) { + return true; + } + } + return false; +} + +template +class LockFreeStack { + public: + void push(const T& x) { + Node* t = new Node(x); + t->next = head_.load(); + while (!head_.compare_exchange_weak(t->next, t)) { + } + } + + std::shared_ptr pop() { + std::atomic& hazard_pointer = hazard_pointer_for_this_thread(); + Node* t = head_.load(); + do { // 外循环确保 t 为最新的头节点,循环结束后将头节点设为下一节点 + Node* t2; + do { // 循环至风险指针保存当前最新的头节点 + t2 = t; + hazard_pointer.store(t); + t = head_.load(); + } while (t != t2); + } while (t && !head_.compare_exchange_strong(t, t->next)); + hazard_pointer.store(nullptr); + std::shared_ptr res; + if (t) { + res.swap(t->v); + if (is_existing(t)) { + append_to_delete_list(new DataToDelete{t}); + } else { + delete t; + } + try_delete(); + } + return res; + } + + private: + struct Node { + std::shared_ptr v; + Node* next = nullptr; + Node(const T& x) : v(std::make_shared(x)) {} + }; + + struct DataToDelete { + public: + template + DataToDelete(T* p) + : data(p), deleter([](void* p) { delete static_cast(p); }) {} + + ~DataToDelete() { deleter(data); } + + void* data = nullptr; + std::function deleter; + DataToDelete* next = nullptr; + }; + + private: + void append_to_delete_list(DataToDelete* t) { + t->next = to_delete_list_.load(); + while (!to_delete_list_.compare_exchange_weak(t->next, t)) { + } + } + + void try_delete() { + DataToDelete* cur = to_delete_list_.exchange(nullptr); + while (cur) { + DataToDelete* t = cur->next; + if (!is_existing(cur->data)) { + delete cur; + } else { + append_to_delete_list(new DataToDelete{cur}); + } + cur = t; + } + } + + private: + std::atomic head_; + std::atomic pop_cnt_; + std::atomic to_delete_list_; +}; diff --git a/src/thread_poll.hpp b/src/thread_poll.hpp new file mode 100644 index 0000000..3108009 --- /dev/null +++ b/src/thread_poll.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +class ThreadPool { + public: + explicit ThreadPool(std::size_t n) { + for (std::size_t i = 0; i < n; ++i) { + std::thread{[this] { + std::unique_lock l(m_); + while (true) { + if (!q_.empty()) { + auto task = std::move(q_.front()); + q_.pop(); + l.unlock(); + task(); + l.lock(); + } else if (done_) { + break; + } else { + cv_.wait(l); + } + } + }}.detach(); + } + } + + ~ThreadPool() { + { + std::lock_guard l(m_); + done_ = true; // cv_.wait 使用了 done_ 判断所以要加锁 + } + cv_.notify_all(); + } + + template + void submit(F&& f) { + { + std::lock_guard l(m_); + q_.emplace(std::forward(f)); + } + cv_.notify_one(); + } + + private: + std::mutex m_; + std::condition_variable cv_; + bool done_ = false; + std::queue> q_; +};