|
| 1 | +# Linux 中封装 POSIX 接口编写 thread 类 |
| 2 | + |
| 3 | +在我们上一节内容其实就提到了 `std::thread` 的 msvc stl 的实现,本质上就是封装了 win32 的那些接口,包括其中最重要的就是利用了模板技术接受任意可调用类型,将其转发给 C 的只是接受单一函数指针的 `_beginthreadex` 去创建线程。 |
| 4 | + |
| 5 | +上一节中我们只是讲了单纯的讲了 “*模板包装C风格API进行调用*”,这一节我们就来实际一点,直接封装编写一个自己的 `std::thread`,使用 POSIX 接口。 |
| 6 | + |
| 7 | +我们将在 Ubuntu22.04 中使用 gcc11.4 开启 C++17 标准进行编写和测试。 |
| 8 | + |
| 9 | +## 实现 |
| 10 | + |
| 11 | +### 搭建框架 |
| 12 | + |
| 13 | +```cpp |
| 14 | +namespace mq_b{ |
| 15 | + class thread{ |
| 16 | + public: |
| 17 | + class id; |
| 18 | + |
| 19 | + id get_id() const noexcept; |
| 20 | + }; |
| 21 | + |
| 22 | + namespace this_thread { |
| 23 | + [[nodiscard]] thread::id get_id() noexcept; |
| 24 | + } |
| 25 | + |
| 26 | + class thread::id { |
| 27 | + public: |
| 28 | + id() noexcept = default; |
| 29 | + |
| 30 | + private: |
| 31 | + explicit id(pthread_t other_id) noexcept : Id(other_id) {} |
| 32 | + |
| 33 | + pthread_t Id; |
| 34 | + |
| 35 | + friend thread::id thread::get_id() const noexcept; |
| 36 | + friend thread::id this_thread::get_id() noexcept; |
| 37 | + friend bool operator==(thread::id left, thread::id right) noexcept; |
| 38 | + |
| 39 | + template <class Ch, class Tr> |
| 40 | + friend std::basic_ostream<Ch, Tr>& operator<<(std::basic_ostream<Ch, Tr>& str, thread::id Id); |
| 41 | + }; |
| 42 | + |
| 43 | + [[nodiscard]] inline thread::id thread::get_id() const noexcept { |
| 44 | + return thread::id{ Id }; |
| 45 | + } |
| 46 | + |
| 47 | + [[nodiscard]] inline thread::id this_thread::get_id() noexcept { |
| 48 | + return thread::id{ pthread_self() }; |
| 49 | + } |
| 50 | + |
| 51 | + template <class Ch, class Tr> |
| 52 | + std::basic_ostream<Ch, Tr>& operator<<(std::basic_ostream<Ch, Tr>& str, thread::id Id){ |
| 53 | + str << Id.Id; |
| 54 | + return str; |
| 55 | + } |
| 56 | +} |
| 57 | +``` |
| 58 | + |
| 59 | +我们先搭建一个基础的架子给各位, 定义我们自己的命名空间,定义内部类 `thread::id`,以及运算符重载。这些并不涉及什么模板技术,我们先进行编写,其实重点只是构造函数而已,剩下的其它成员函数也很简单,我们一步一步来。 |
| 60 | + |
| 61 | +另外,**POSIX 的线程函数都只需要一个句柄,其实就是无符号 int 类型就能操作**,别名是 `pthread_t` 所以我们只需要保有这样一个 id 就好了。 |
| 62 | + |
| 63 | +### 实现构造函数 |
| 64 | + |
| 65 | +```cpp |
| 66 | +template<typename Fn, typename ...Args> |
| 67 | +thread(Fn&& func, Args&&... args) { |
| 68 | + using Tuple = std::tuple<std::decay_t<Fn>, std::decay_t<Args>...>; |
| 69 | + auto Decay_copied = std::make_unique<Tuple>(std::forward<Fn>(func), std::forward<Args>(args)...); |
| 70 | + auto Invoker_proc = start<Tuple>(std::make_index_sequence<1 + sizeof...(Args)>{}); |
| 71 | + if (int result = pthread_create(&Id, nullptr, Invoker_proc, Decay_copied.get()); result == 0) { |
| 72 | + (void)Decay_copied.release(); |
| 73 | + }else{ |
| 74 | + std::cerr << "Error creating thread: " << strerror(result) << std::endl; |
| 75 | + throw std::runtime_error("Error creating thread"); |
| 76 | + } |
| 77 | +} |
| 78 | +template <typename Tuple, std::size_t... Indices> |
| 79 | +static constexpr auto start(std::index_sequence<Indices...>) noexcept { |
| 80 | + return &Invoke<Tuple, Indices...>; |
| 81 | +} |
| 82 | + |
| 83 | +template <class Tuple, std::size_t... Indices> |
| 84 | +static void* Invoke(void* RawVals) noexcept { |
| 85 | + const std::unique_ptr<Tuple> FnVals(static_cast<Tuple*>(RawVals)); |
| 86 | + Tuple& Tup = *FnVals.get(); |
| 87 | + std::invoke(std::move(std::get<Indices>(Tup))...); |
| 88 | + nullptr 0; |
| 89 | +} |
| 90 | +``` |
| 91 | +
|
| 92 | +这其实很简单,几乎是直接复制了我们上一节的内容,只是把函数改成了 `pthread_create`,然后多传了两个参数,以及修改了 Invoke 的返回类型和 return,确保类型符合 `pthread_create` 。 |
| 93 | +
|
| 94 | +### 完善其它成员函数 |
| 95 | +
|
| 96 | +然后再稍加完善那些简单的成员函数,也就是: |
| 97 | +
|
| 98 | +```cpp |
| 99 | +~thread(){ |
| 100 | + if (joinable()) |
| 101 | + std::terminate(); |
| 102 | +} |
| 103 | +
|
| 104 | +thread(const thread&) = delete; |
| 105 | +
|
| 106 | +thread& operator=(const thread&) = delete; |
| 107 | +
|
| 108 | +thread(thread&& other) noexcept : Id(std::exchange(other.Id, {})) {} |
| 109 | +
|
| 110 | +thread& operator=(thread&& t) noexcept{ |
| 111 | + if (joinable()) |
| 112 | + std::terminate(); |
| 113 | + swap(t); |
| 114 | + return *this; |
| 115 | +} |
| 116 | +
|
| 117 | +void swap(thread& t) noexcept{ |
| 118 | + std::swap(Id, t.Id); |
| 119 | +} |
| 120 | +
|
| 121 | +bool joinable() const noexcept{ |
| 122 | + return !(Id == 0); |
| 123 | +} |
| 124 | +
|
| 125 | +void join() { |
| 126 | + if (!joinable()) { |
| 127 | + throw std::runtime_error("Thread is not joinable"); |
| 128 | + } |
| 129 | + int result = pthread_join(Id, nullptr); |
| 130 | + if (result != 0) { |
| 131 | + throw std::runtime_error("Error joining thread: " + std::string(strerror(result))); |
| 132 | + } |
| 133 | + Id = {}; // Reset thread id |
| 134 | +} |
| 135 | +
|
| 136 | +void detach() { |
| 137 | + if (!joinable()) { |
| 138 | + throw std::runtime_error("Thread is not joinable or already detached"); |
| 139 | + } |
| 140 | + int result = pthread_detach(Id); |
| 141 | + if (result != 0) { |
| 142 | + throw std::runtime_error("Error detaching thread: " + std::string(strerror(result))); |
| 143 | + } |
| 144 | + Id = {}; // Reset thread id |
| 145 | +} |
| 146 | +
|
| 147 | +id get_id() const noexcept; |
| 148 | +
|
| 149 | +native_handle_type native_handle() const{ |
| 150 | + return Id; |
| 151 | +} |
| 152 | +``` |
| 153 | + |
| 154 | +我觉得无需多言,这些都十分的简单。然后就完成了,对,就是这么简单。 |
| 155 | + |
| 156 | +### 完整代码与测试 |
| 157 | + |
| 158 | +**完整实现代码**: |
| 159 | + |
| 160 | +```cpp |
| 161 | +namespace mq_b{ |
| 162 | + class thread{ |
| 163 | + public: |
| 164 | + class id; |
| 165 | + using native_handle_type = pthread_t; |
| 166 | + |
| 167 | + thread() noexcept :Id{} {} |
| 168 | + |
| 169 | + template<typename Fn, typename ...Args> |
| 170 | + thread(Fn&& func, Args&&... args) { |
| 171 | + using Tuple = std::tuple<std::decay_t<Fn>, std::decay_t<Args>...>; |
| 172 | + auto Decay_copied = std::make_unique<Tuple>(std::forward<Fn>(func), std::forward<Args>(args)...); |
| 173 | + auto Invoker_proc = start<Tuple>(std::make_index_sequence<1 + sizeof...(Args)>{}); |
| 174 | + if (int result = pthread_create(&Id, nullptr, Invoker_proc, Decay_copied.get()); result == 0) { |
| 175 | + (void)Decay_copied.release(); |
| 176 | + }else{ |
| 177 | + std::cerr << "Error creating thread: " << strerror(result) << std::endl; |
| 178 | + throw std::runtime_error("Error creating thread"); |
| 179 | + } |
| 180 | + } |
| 181 | + template <typename Tuple, std::size_t... Indices> |
| 182 | + static constexpr auto start(std::index_sequence<Indices...>) noexcept { |
| 183 | + return &Invoke<Tuple, Indices...>; |
| 184 | + } |
| 185 | + |
| 186 | + template <class Tuple, std::size_t... Indices> |
| 187 | + static void* Invoke(void* RawVals) noexcept { |
| 188 | + const std::unique_ptr<Tuple> FnVals(static_cast<Tuple*>(RawVals)); |
| 189 | + Tuple& Tup = *FnVals.get(); |
| 190 | + std::invoke(std::move(std::get<Indices>(Tup))...); |
| 191 | + return nullptr; |
| 192 | + } |
| 193 | + |
| 194 | + ~thread(){ |
| 195 | + if (joinable()) |
| 196 | + std::terminate(); |
| 197 | + } |
| 198 | + |
| 199 | + thread(const thread&) = delete; |
| 200 | + |
| 201 | + thread& operator=(const thread&) = delete; |
| 202 | + |
| 203 | + thread(thread&& other) noexcept : Id(std::exchange(other.Id, {})) {} |
| 204 | + |
| 205 | + thread& operator=(thread&& t) noexcept{ |
| 206 | + if (joinable()) |
| 207 | + std::terminate(); |
| 208 | + swap(t); |
| 209 | + return *this; |
| 210 | + } |
| 211 | + |
| 212 | + void swap(thread& t) noexcept{ |
| 213 | + std::swap(Id, t.Id); |
| 214 | + } |
| 215 | + |
| 216 | + bool joinable() const noexcept{ |
| 217 | + return !(Id == 0); |
| 218 | + } |
| 219 | + |
| 220 | + void join() { |
| 221 | + if (!joinable()) { |
| 222 | + throw std::runtime_error("Thread is not joinable"); |
| 223 | + } |
| 224 | + int result = pthread_join(Id, nullptr); |
| 225 | + if (result != 0) { |
| 226 | + throw std::runtime_error("Error joining thread: " + std::string(strerror(result))); |
| 227 | + } |
| 228 | + Id = {}; // Reset thread id |
| 229 | + } |
| 230 | + |
| 231 | + void detach() { |
| 232 | + if (!joinable()) { |
| 233 | + throw std::runtime_error("Thread is not joinable or already detached"); |
| 234 | + } |
| 235 | + int result = pthread_detach(Id); |
| 236 | + if (result != 0) { |
| 237 | + throw std::runtime_error("Error detaching thread: " + std::string(strerror(result))); |
| 238 | + } |
| 239 | + Id = {}; // Reset thread id |
| 240 | + } |
| 241 | + |
| 242 | + id get_id() const noexcept; |
| 243 | + |
| 244 | + native_handle_type native_handle() const{ |
| 245 | + return Id; |
| 246 | + } |
| 247 | + |
| 248 | + pthread_t Id; |
| 249 | + }; |
| 250 | + |
| 251 | + namespace this_thread { |
| 252 | + [[nodiscard]] thread::id get_id() noexcept; |
| 253 | + } |
| 254 | + |
| 255 | + class thread::id { |
| 256 | + public: |
| 257 | + id() noexcept = default; |
| 258 | + |
| 259 | + private: |
| 260 | + explicit id(pthread_t other_id) noexcept : Id(other_id) {} |
| 261 | + |
| 262 | + pthread_t Id; |
| 263 | + |
| 264 | + friend thread::id thread::get_id() const noexcept; |
| 265 | + friend thread::id this_thread::get_id() noexcept; |
| 266 | + friend bool operator==(thread::id left, thread::id right) noexcept; |
| 267 | + |
| 268 | + template <class Ch, class Tr> |
| 269 | + friend std::basic_ostream<Ch, Tr>& operator<<(std::basic_ostream<Ch, Tr>& str, thread::id Id); |
| 270 | + }; |
| 271 | + |
| 272 | + [[nodiscard]] inline thread::id thread::get_id() const noexcept { |
| 273 | + return thread::id{ Id }; |
| 274 | + } |
| 275 | + |
| 276 | + [[nodiscard]] inline thread::id this_thread::get_id() noexcept { |
| 277 | + return thread::id{ pthread_self() }; |
| 278 | + } |
| 279 | + |
| 280 | + template <class Ch, class Tr> |
| 281 | + std::basic_ostream<Ch, Tr>& operator<<(std::basic_ostream<Ch, Tr>& str, thread::id Id){ |
| 282 | + str << Id.Id; |
| 283 | + return str; |
| 284 | + } |
| 285 | +} |
| 286 | +``` |
| 287 | +
|
| 288 | +**标头**: |
| 289 | +
|
| 290 | +```cpp |
| 291 | +#include <iostream> |
| 292 | +#include <thread> |
| 293 | +#include <functional> |
| 294 | +#include <tuple> |
| 295 | +#include <utility> |
| 296 | +#include <cstring> |
| 297 | +#include <pthread.h> |
| 298 | +#include <unistd.h> |
| 299 | +``` |
| 300 | + |
| 301 | +**测试**: |
| 302 | + |
| 303 | +```cpp |
| 304 | +void func(int& a) { |
| 305 | + std::cout << &a << '\n'; |
| 306 | +} |
| 307 | +void func2(const int& a){ |
| 308 | + std::cout << &a << '\n'; |
| 309 | +} |
| 310 | +struct X{ |
| 311 | + void f() { std::cout << "X::f\n"; } |
| 312 | +}; |
| 313 | + |
| 314 | +int main(){ |
| 315 | + std::cout << "main thread id: " << mq_b::this_thread::get_id() << '\n'; |
| 316 | + |
| 317 | + int a = 10; |
| 318 | + std::cout << &a << '\n'; |
| 319 | + mq_b::thread t{ func,std::ref(a) }; |
| 320 | + t.join(); |
| 321 | + |
| 322 | + mq_b::thread t2{ func2,a }; |
| 323 | + t2.join(); |
| 324 | + |
| 325 | + mq_b::thread t3{ [] {std::cout << "thread id: " << mq_b::this_thread::get_id() << '\n'; } }; |
| 326 | + t3.join(); |
| 327 | + |
| 328 | + X x; |
| 329 | + mq_b::thread t4{ &X::f,&x }; |
| 330 | + t4.join(); |
| 331 | + |
| 332 | + mq_b::thread{ [] {std::cout << "👉🤣\n"; } }.detach(); |
| 333 | + sleep(1); |
| 334 | +} |
| 335 | +``` |
| 336 | +
|
| 337 | +> [运行](https://godbolt.org/z/1j48Mh89x)测试。 |
| 338 | +
|
| 339 | +## 总结 |
| 340 | +
|
| 341 | +其实这玩意没多少难度,唯一的难度就只有那个构造函数而已,剩下的代码和成员函数,甚至可以照着标准库抄一些,或者就是调用 POSIX 接口罢了。 |
| 342 | +
|
| 343 | +不过如果各位能完全理解明白,那也足以自傲,毕竟的确没多少人懂。简单是相对而言的,如果你跟着视频一直学习了前面的模板,并且有基本的并发的知识,对 `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),那么本节的内容对你,肯定不构成难度。 |
0 commit comments