Skip to content

Commit 396f9f2

Browse files
committed
新增一节造轮子,封装 POSIX 接口编写 mq_b::thread
1 parent 856f369 commit 396f9f2

File tree

1 file changed

+343
-0
lines changed

1 file changed

+343
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,343 @@
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

Comments
 (0)