From e8c93fb0007cc4e2f09561688f92497e89e1cd18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?mq=E7=99=BD?= <3326284481@qq.com> Date: Sat, 31 Aug 2024 14:31:30 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=8C=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vitepress/config.mjs | 1 + ...\274\226\345\206\231thread\347\261\273.md" | 343 ++++++++++++++++++ 2 files changed, 344 insertions(+) create mode 100644 "md/\347\254\254\344\272\214\351\203\250\345\210\206-\351\200\240\350\275\256\345\255\220/Linux\344\270\255\345\260\201\350\243\205POSIX\346\216\245\345\217\243\347\274\226\345\206\231thread\347\261\273.md" diff --git a/.vitepress/config.mjs b/.vitepress/config.mjs index 61f01f5..5c02ac3 100644 --- a/.vitepress/config.mjs +++ b/.vitepress/config.mjs @@ -54,6 +54,7 @@ export default defineConfig({ collapsed: true, items: [ { text: '使用模板包装C风格API进行调用', link: tutorial2_path + '使用模板包装C风格API进行调用' }, + { text: 'Linux中封装POSIX接口编写thread类', link: tutorial2_path + 'Linux中封装POSIX接口编写thread类' }, ] }, { text: '总结', link: tutorial_path + '12总结' }, diff --git "a/md/\347\254\254\344\272\214\351\203\250\345\210\206-\351\200\240\350\275\256\345\255\220/Linux\344\270\255\345\260\201\350\243\205POSIX\346\216\245\345\217\243\347\274\226\345\206\231thread\347\261\273.md" "b/md/\347\254\254\344\272\214\351\203\250\345\210\206-\351\200\240\350\275\256\345\255\220/Linux\344\270\255\345\260\201\350\243\205POSIX\346\216\245\345\217\243\347\274\226\345\206\231thread\347\261\273.md" new file mode 100644 index 0000000..c224c21 --- /dev/null +++ "b/md/\347\254\254\344\272\214\351\203\250\345\210\206-\351\200\240\350\275\256\345\255\220/Linux\344\270\255\345\260\201\350\243\205POSIX\346\216\245\345\217\243\347\274\226\345\206\231thread\347\261\273.md" @@ -0,0 +1,343 @@ +# Linux 中封装 POSIX 接口编写 thread 类 + +在我们上一节内容其实就提到了 `std::thread` 的 msvc stl 的实现,本质上就是封装了 win32 的那些接口,包括其中最重要的就是利用了模板技术接受任意可调用类型,将其转发给 C 的只是接受单一函数指针的 `_beginthreadex` 去创建线程。 + +上一节中我们只是讲了单纯的讲了 “*模板包装C风格API进行调用*”,这一节我们就来实际一点,直接封装编写一个自己的 `std::thread`,使用 POSIX 接口。 + +我们将在 Ubuntu22.04 中使用 gcc11.4 开启 C++17 标准进行编写和测试。 + +## 实现 + +### 搭建框架 + +```cpp +namespace mq_b{ + class thread{ + public: + class id; + + id get_id() const noexcept; + }; + + namespace this_thread { + [[nodiscard]] thread::id get_id() noexcept; + } + + class thread::id { + public: + id() noexcept = default; + + private: + explicit id(pthread_t other_id) noexcept : Id(other_id) {} + + pthread_t Id; + + friend thread::id thread::get_id() const noexcept; + friend thread::id this_thread::get_id() noexcept; + friend bool operator==(thread::id left, thread::id right) noexcept; + + template + friend std::basic_ostream& operator<<(std::basic_ostream& str, thread::id Id); + }; + + [[nodiscard]] inline thread::id thread::get_id() const noexcept { + return thread::id{ Id }; + } + + [[nodiscard]] inline thread::id this_thread::get_id() noexcept { + return thread::id{ pthread_self() }; + } + + template + std::basic_ostream& operator<<(std::basic_ostream& str, thread::id Id){ + str << Id.Id; + return str; + } +} +``` + +我们先搭建一个基础的架子给各位, 定义我们自己的命名空间,定义内部类 `thread::id`,以及运算符重载。这些并不涉及什么模板技术,我们先进行编写,其实重点只是构造函数而已,剩下的其它成员函数也很简单,我们一步一步来。 + +另外,**POSIX 的线程函数都只需要一个句柄,其实就是无符号 int 类型就能操作**,别名是 `pthread_t` 所以我们只需要保有这样一个 id 就好了。 + +### 实现构造函数 + +```cpp +template +thread(Fn&& func, Args&&... args) { + using Tuple = std::tuple, std::decay_t...>; + auto Decay_copied = std::make_unique(std::forward(func), std::forward(args)...); + auto Invoker_proc = start(std::make_index_sequence<1 + sizeof...(Args)>{}); + if (int result = pthread_create(&Id, nullptr, Invoker_proc, Decay_copied.get()); result == 0) { + (void)Decay_copied.release(); + }else{ + std::cerr << "Error creating thread: " << strerror(result) << std::endl; + throw std::runtime_error("Error creating thread"); + } +} +template +static constexpr auto start(std::index_sequence) noexcept { + return &Invoke; +} + +template +static void* Invoke(void* RawVals) noexcept { + const std::unique_ptr FnVals(static_cast(RawVals)); + Tuple& Tup = *FnVals.get(); + std::invoke(std::move(std::get(Tup))...); + nullptr 0; +} +``` + +这其实很简单,几乎是直接复制了我们上一节的内容,只是把函数改成了 `pthread_create`,然后多传了两个参数,以及修改了 Invoke 的返回类型和 return,确保类型符合 `pthread_create` 。 + +### 完善其它成员函数 + +然后再稍加完善那些简单的成员函数,也就是: + +```cpp +~thread(){ + if (joinable()) + std::terminate(); +} + +thread(const thread&) = delete; + +thread& operator=(const thread&) = delete; + +thread(thread&& other) noexcept : Id(std::exchange(other.Id, {})) {} + +thread& operator=(thread&& t) noexcept{ + if (joinable()) + std::terminate(); + swap(t); + return *this; +} + +void swap(thread& t) noexcept{ + std::swap(Id, t.Id); +} + +bool joinable() const noexcept{ + return !(Id == 0); +} + +void join() { + if (!joinable()) { + throw std::runtime_error("Thread is not joinable"); + } + int result = pthread_join(Id, nullptr); + if (result != 0) { + throw std::runtime_error("Error joining thread: " + std::string(strerror(result))); + } + Id = {}; // Reset thread id +} + +void detach() { + if (!joinable()) { + throw std::runtime_error("Thread is not joinable or already detached"); + } + int result = pthread_detach(Id); + if (result != 0) { + throw std::runtime_error("Error detaching thread: " + std::string(strerror(result))); + } + Id = {}; // Reset thread id +} + +id get_id() const noexcept; + +native_handle_type native_handle() const{ + return Id; +} +``` + +我觉得无需多言,这些都十分的简单。然后就完成了,对,就是这么简单。 + +### 完整代码与测试 + +**完整实现代码**: + +```cpp +namespace mq_b{ + class thread{ + public: + class id; + using native_handle_type = pthread_t; + + thread() noexcept :Id{} {} + + template + thread(Fn&& func, Args&&... args) { + using Tuple = std::tuple, std::decay_t...>; + auto Decay_copied = std::make_unique(std::forward(func), std::forward(args)...); + auto Invoker_proc = start(std::make_index_sequence<1 + sizeof...(Args)>{}); + if (int result = pthread_create(&Id, nullptr, Invoker_proc, Decay_copied.get()); result == 0) { + (void)Decay_copied.release(); + }else{ + std::cerr << "Error creating thread: " << strerror(result) << std::endl; + throw std::runtime_error("Error creating thread"); + } + } + template + static constexpr auto start(std::index_sequence) noexcept { + return &Invoke; + } + + template + static void* Invoke(void* RawVals) noexcept { + const std::unique_ptr FnVals(static_cast(RawVals)); + Tuple& Tup = *FnVals.get(); + std::invoke(std::move(std::get(Tup))...); + return nullptr; + } + + ~thread(){ + if (joinable()) + std::terminate(); + } + + thread(const thread&) = delete; + + thread& operator=(const thread&) = delete; + + thread(thread&& other) noexcept : Id(std::exchange(other.Id, {})) {} + + thread& operator=(thread&& t) noexcept{ + if (joinable()) + std::terminate(); + swap(t); + return *this; + } + + void swap(thread& t) noexcept{ + std::swap(Id, t.Id); + } + + bool joinable() const noexcept{ + return !(Id == 0); + } + + void join() { + if (!joinable()) { + throw std::runtime_error("Thread is not joinable"); + } + int result = pthread_join(Id, nullptr); + if (result != 0) { + throw std::runtime_error("Error joining thread: " + std::string(strerror(result))); + } + Id = {}; // Reset thread id + } + + void detach() { + if (!joinable()) { + throw std::runtime_error("Thread is not joinable or already detached"); + } + int result = pthread_detach(Id); + if (result != 0) { + throw std::runtime_error("Error detaching thread: " + std::string(strerror(result))); + } + Id = {}; // Reset thread id + } + + id get_id() const noexcept; + + native_handle_type native_handle() const{ + return Id; + } + + pthread_t Id; + }; + + namespace this_thread { + [[nodiscard]] thread::id get_id() noexcept; + } + + class thread::id { + public: + id() noexcept = default; + + private: + explicit id(pthread_t other_id) noexcept : Id(other_id) {} + + pthread_t Id; + + friend thread::id thread::get_id() const noexcept; + friend thread::id this_thread::get_id() noexcept; + friend bool operator==(thread::id left, thread::id right) noexcept; + + template + friend std::basic_ostream& operator<<(std::basic_ostream& str, thread::id Id); + }; + + [[nodiscard]] inline thread::id thread::get_id() const noexcept { + return thread::id{ Id }; + } + + [[nodiscard]] inline thread::id this_thread::get_id() noexcept { + return thread::id{ pthread_self() }; + } + + template + std::basic_ostream& operator<<(std::basic_ostream& str, thread::id Id){ + str << Id.Id; + return str; + } +} +``` + +**标头**: + +```cpp +#include +#include +#include +#include +#include +#include +#include +#include +``` + +**测试**: + +```cpp +void func(int& a) { + std::cout << &a << '\n'; +} +void func2(const int& a){ + std::cout << &a << '\n'; +} +struct X{ + void f() { std::cout << "X::f\n"; } +}; + +int main(){ + std::cout << "main thread id: " << mq_b::this_thread::get_id() << '\n'; + + int a = 10; + std::cout << &a << '\n'; + mq_b::thread t{ func,std::ref(a) }; + t.join(); + + mq_b::thread t2{ func2,a }; + t2.join(); + + mq_b::thread t3{ [] {std::cout << "thread id: " << mq_b::this_thread::get_id() << '\n'; } }; + t3.join(); + + X x; + mq_b::thread t4{ &X::f,&x }; + t4.join(); + + mq_b::thread{ [] {std::cout << "👉🤣\n"; } }.detach(); + sleep(1); +} +``` + +> [运行](https://godbolt.org/z/1j48Mh89x)测试。 + +## 总结 + +其实这玩意没多少难度,唯一的难度就只有那个构造函数而已,剩下的代码和成员函数,甚至可以照着标准库抄一些,或者就是调用 POSIX 接口罢了。 + +不过如果各位能完全理解明白,那也足以自傲,毕竟的确没多少人懂。简单是相对而言的,如果你跟着视频一直学习了前面的模板,并且有基本的并发的知识,对 `POSIX` 接口有基本的认识,以及看了前面提到的 [**《`std::thread` 的构造-源码解析》**](https://mq-b.github.io/ModernCpp-ConcurrentProgramming-Tutorial/md/%E8%AF%A6%E7%BB%86%E5%88%86%E6%9E%90/01thread%E7%9A%84%E6%9E%84%E9%80%A0%E4%B8%8E%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90.html),那么本节的内容对你,肯定不构成难度。