Skip to content

Commit 0dfbdfb

Browse files
authored
Merge pull request #150 from duang3457/patch-4
feat: Add cpp_index
2 parents 42cbdf9 + 89e362a commit 0dfbdfb

File tree

7 files changed

+850
-5
lines changed

7 files changed

+850
-5
lines changed

app/docs/computer-science/cpp_backend/threadpool_cpp.mdx renamed to app/docs/computer-science/cpp_backend/Handwritten_pool_components/1_Handwritten_threadpool.md

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,24 @@
11
---
22
title: '手写线程池'
33
description: ""
4-
date: "2025-09-27"
4+
date: "2025-09-29"
55
tags:
66
- tag-one
77
---
88

9+
910
# 线程池
11+
12+
> 以下是调用单队列BlockingQueue的代码,使用双队列时将所有BlockingQueue改为BlockingQueuePro
13+
>
14+
15+
## 接口
16+
构造:用智能指针 unique_ptr 初始化阻塞队列,创建threads_num个线程并给每个绑定Worker函数开始执行task。(此时全部Worker阻塞在Pop()里)
17+
18+
Post: 发布任务到线程池。
19+
20+
析构:通知唤醒所有消费者,没有任务就自己下班。与生产者没关系。
21+
1022
```cpp
1123
#pragma once
1224

@@ -38,8 +50,6 @@ private:
3850
};
3951
```
4052
41-
42-
4353
```cpp
4454
#include "blockingqueue.h"
4555
#include <memory>
@@ -55,6 +65,7 @@ ThreadPool::ThreadPool(int threads_num) {
5565
}
5666
5767
// 停止线程池
68+
// 取消队列并唤醒所有阻塞线程
5869
ThreadPool::~ThreadPool() {
5970
task_queue_->Cancel();
6071
for(auto &worker : workers_) {
@@ -72,6 +83,7 @@ void ThreadPool::Post(std::function<void()> task) {
7283
void ThreadPool::Worker() {
7384
while (true) {
7485
std::function<void()> task;
86+
// 阻塞在Pop里实现
7587
if (!task_queue_->Pop(task)) {
7688
break;
7789
}
@@ -86,7 +98,7 @@ void ThreadPool::Worker() {
8698
**单队列维护:**
8799

88100
1. nonblock_(bool): 未阻塞标记。为true时,队列不会阻塞。初始(构造时)默认为阻塞状态。
89-
2. `queue_(std::queue<T>)`: 底层存储容器。
101+
2. queue_(std::queue<T>): 底层存储容器。
90102
3. mutex_(std::mutex): 保证多线程操作安全的互斥锁。
91103
4. not_empty_(std::condition_variable):用于线程前同步。
92104

@@ -147,6 +159,8 @@ private:
147159
```
148160

149161
# 阻塞队列(双队列版)
162+
![画板](https://cdn.nlark.com/yuque/0/2025/jpeg/43055607/1759131100901-946e59ae-cd19-4546-aa9d-a9ee658f0b5a.jpeg)
163+
150164
单队列中, 生产者和消费者都要竞争同一把锁。
151165

152166
双队列:
@@ -211,6 +225,8 @@ private:
211225
};
212226
```
213227
228+
229+
214230
# Test
215231
理论上双队列比单队列快,因为锁的竞争/碰撞少了。下面的实验结果也是如此:
216232
@@ -398,4 +414,3 @@ private:
398414
};
399415
```
400416

401-
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
---
2+
title: '手写定长内存池'
3+
description: ""
4+
date: "2025-09-29"
5+
tags:
6+
- tag-one
7+
---
8+
9+
10+
# 手写定长内存池
11+
12+
## 设计图
13+
![画板](https://cdn.nlark.com/yuque/0/2025/jpeg/43055607/1758718719250-e6f52459-0f73-493b-8294-7b8f931da054.jpeg)
14+
15+
16+
17+
## 代码部分
18+
### 结构体定义
19+
#### 管理内存池结构的结构体mempool_s
20+
```c
21+
typedef struct mempool_s {
22+
int blocksize; // 每个内存块的size
23+
int freecount; // 剩余空的内存块数量
24+
char *free_ptr; // 指向下一空内存块
25+
char *mem; // 整块内存的头指针
26+
} mempool_t;
27+
```
28+
29+
### 对外接口
30+
#### memp_create: 内存池创建
31+
```c
32+
int memp_create(mempool_t *m, int block_size) {
33+
34+
if (!m) return -1;
35+
36+
// 1. 初始化这两个简单的int
37+
m->blocksize = block_size;
38+
m->freecount = MEM_PAGE_SIZE / block_size;
39+
40+
// 2. 开整个内存池的空间,顺便初始化m->mem
41+
m->mem = (char *)malloc(MEM_PAGE_SIZE);
42+
if (!m->mem) { // 开失败了(剩余空闲内存不够)
43+
return -2;
44+
}
45+
// 将这个空间初始化一下
46+
memset(m->mem, 0, MEM_PAGE_SIZE);
47+
48+
// 3. 初始化free_ptr
49+
m->free_ptr = m->mem;
50+
51+
// 依次初始化每个block里的"next->ptr"
52+
int i = 0;
53+
char *ptr = m->mem;
54+
for (i = 0;i < m->freecount;i ++) {
55+
56+
*(char **)ptr = ptr + block_size;
57+
ptr = ptr + block_size;
58+
}
59+
// 最后一个block的"next_ptr"指向NULL
60+
*(char **)ptr = NULL;
61+
return 0;
62+
}
63+
```
64+
65+
#### memp_alloc: 分配block
66+
```c
67+
void *memp_alloc(mempool_t *m) {
68+
// 满了
69+
if (!m || m->freecount == 0) return NULL;
70+
// 1. 获取当前下一个空闲块,作为返回值
71+
void *ptr = m->free_ptr;
72+
// 2. 更新free_ptr
73+
m->free_ptr = *(char **)ptr;
74+
// 3. 更新freecount
75+
m->freecount --;
76+
77+
return ptr;
78+
}
79+
```
80+
81+
#### memp_free: 删除指定block
82+
```c
83+
void memp_free(mempool_t *m, void *ptr) {
84+
// 相当于 ptr->next = m->free_ptr;
85+
// 头插法将要释放的block插到空闲block链表的头部,即空闲block链表又增加了一个
86+
*(char **)ptr = m->free_ptr;
87+
// 更新free_ptr这一空闲block链表头指针
88+
m->free_ptr = (char *)ptr;
89+
// 更新freecount
90+
m->freecount ++;
91+
}
92+
```
93+
94+
#### memp_destory: 删除整个内存池
95+
```c
96+
void memp_destory(mempool_t *m) {
97+
if (!m) return ;
98+
// 直接free内存池,因为内存池是整体malloc的,而不是一个一个block malloc的
99+
free(m->mem);
100+
}
101+
```
102+
103+
104+
105+
## 使用example
106+
```c
107+
int main() {
108+
mempool_t m;
109+
memp_create(&m, 32);
110+
111+
void *p1 = memp_alloc(&m);
112+
printf("memp_alloc : %p\n", p1);
113+
114+
void *p2 = memp_alloc(&m);
115+
printf("memp_alloc : %p\n", p2);
116+
117+
void *p3 = memp_alloc(&m);
118+
printf("memp_alloc : %p\n", p3);
119+
120+
memp_free(&m, p2);
121+
}
122+
```
123+
124+
输出:可以看到每个block确实是32个字节
125+
126+
![](https://cdn.nlark.com/yuque/0/2025/png/43055607/1759069995143-4548da88-8c23-463e-b9e7-0f7d8978f03b.png)
127+
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
---
2+
title: 'linux/win上的c++库'
3+
description: ""
4+
date: "2025-09-29"
5+
tags:
6+
- tag-one
7+
---
8+
9+
# linux/win上的c++库
10+
11+
12+
## 一、一个库长什么样?
13+
每个库源码的格式都不一样。但都会有:
14+
15+
1. .hpp/.h(位于include或根目录下) :用来链接的头文件,不能没有
16+
2. .cpp :实现头文件的逻辑,有的源码被称为头文件库(直接在头文件里实现了逻辑,没有.cpp实现,也不需要链接动态库或静态库)。
17+
+ 在 Linux 上:`XXX.so`(shared object动态库) 或 `XXX.a`(static静态库)
18+
+ 在 Windows 上:`XXX.lib`(静态库) 或 `XXX.dll`(动态库)
19+
20+
## 二、如何得到一个库?
21+
> 非特殊标明,都是linux环境(具体为Ubuntu)
22+
>
23+
24+
### 下载别人写的库
25+
#### win环境
26+
1. **手动从源码编译安装 :**下载或git clone一个zip/7z等压缩包,解压后得到源代码,编译得到所需库。
27+
2. **使用对应语言的包管理工具**(c++: 如vcpkg,python: 如pip,java: 如maven)下载。如vcpkg会将包下载到vcpkg/installed/下(默认)。
28+
29+
> 简单介绍一下vcpkg
30+
>
31+
> + **vcpkg** 是微软开源的跨平台 C/C++ 包管理器。
32+
> + 支持 **Windows / Linux / macOS**,但最早是为 Windows 生态开发的。
33+
> + 主要用来解决:C/C++ 第三方库获取困难、编译配置复杂的问题。
34+
>
35+
36+
#### linux环境
37+
1. **从源码编译安装 **
38+
39+
> 有些库只能源码安装,比如最新版本的 gRPC、Protobuf。
40+
>
41+
> 一般步骤是:clone -> make流程或者build.sh这种一键脚本。
42+
>
43+
44+
2. **使用系统自带的包管理器**
45+
46+
> apt, yum, dnf等
47+
>
48+
49+
3. **使用对应语言的包管理工具**
50+
51+
> vcpkg,conan等
52+
>
53+
54+
### 自己写一个库
55+
写完.cpp和.h后,可选择将其编译成静态库或动态库。
56+
57+
#### 打包为静态库
58+
```bash
59+
g++ -c mylib.cpp -o mylib.o
60+
ar rcs my_static_lib.a mylib.o // 不建议
61+
// 事实上,打包时应在目标文件名前+lib。
62+
// 这不只是约定俗成。
63+
// 使用-lmylib时,链接器会自动加前缀去找,即找libmylib.a或.so。
64+
// 如果不加lib前缀,只能写清完整路径了(不能依靠链接器自己去找了)
65+
ar rcs libmy_static_lib.a mylib.o // 建议
66+
```
67+
68+
#### 打包为动态库
69+
```bash
70+
// 简单的项目,一步到位
71+
g++ -shared -o mylib.dll mylib.cpp
72+
// 复杂的项目,先转为中间文件.o,再将.o文件转为mylib.dll
73+
g++ -c -fPIC mylib.cpp -o mylib.o
74+
g++ -shared -o my_dynamic_lib.dll mylib.o
75+
```
76+
77+
-fPIC表示生成位置独立代码(Position Independent Code),这是动态库所需要的。
78+
79+
如上,我们就得到my_dynamic_lib.dll动态库和my_static_lib.a静态库了。
80+
81+
## 三、下载的库被放在了哪里
82+
#### python的库可能放在
83+
+ 项目根目录下的venv文件夹中(用venv创建的虚拟环境,也可以叫其他名,自己创建时命名)
84+
+ conda目录下的某环境文件夹中(下载conda时指定一个存放环境的文件路径,所有由conda创建的虚拟环境都放在这里,环境名作为二级目录名)
85+
86+
#### 前端的库可能放在
87+
+ 项目根目录下的node_modules中(有npm包管理创建和管理)
88+
89+
#### Java的Spring项目(Maven包管理)可能放在
90+
+ 缓存到 ~/.m2/repository/ (本地仓库,win和linux都是)
91+
92+
#### c++的库(linux环境)
93+
+ 系统自带库:`/lib`, `/lib64`, `/usr/lib`, `/usr/lib64`
94+
+ apt/yum/dnf/pacman 包管理器库:`/usr/lib/x86_64-linux-gnu`(库文件), `/usr/include`(头文件)
95+
+ 自己编译:`/usr/local/lib`, `/usr/local/include`
96+
+ 包管理器(vcpkg/conan):用户目录下的专用路径
97+
- vcpkg:`~/vcpkg/installed/<triplet>/lib`
98+
- conan:`~/.conan/data/<package>/<version>/...`
99+
- 自己放的:`~/lib`, `~/include`
100+
101+
## 四、如何使用一个库呢?
102+
> 使用库两个步骤,一是**链接头文件**,二是**链接库实现**
103+
>
104+
105+
### 链接 头文件
106+
> 不用-I(大写i)的时候默认寻找当前文件目录
107+
>
108+
109+
1. 不改变库头文件位置,硬编码指定所有库的头文件路径在哪(每个头文件各指定一次)
110+
111+
```bash
112+
g++ -I path/to/s_lib1.h -I path/to/s_lib2.h ... -o output main.cpp
113+
```
114+
115+
2. 整合头文件移到include目录下(注意如果是三方库,不建议移动,因为一般还有其他依赖项。自己的库,建议整合头文件),移到项目下的include目录下,然后指定头文件目录为include目录(只指定一次)
116+
117+
```bash
118+
g++ -I include/ -o output main.cpp
119+
```
120+
121+
(注意指定头文件目录 + #include “path/to/lib.h”共同拼接成头文件完整路径)
122+
123+
> 是否觉得纯命令行编译太麻烦太长,后面用CMake时写进CMakeList.txt里更方便
124+
>
125+
126+
### 链接 库实现
127+
> **对于非纯头文件库,还需要连接库实现,其分为动态库和静态库**
128+
>
129+
130+
#### 链接动态库
131+
```bash
132+
g++ myapp.cpp -L /path/to/library -l mylib
133+
```
134+
135+
在运行时建议将 .dll动态库 放在与 可执行文件 同级目录下。因为其查找顺序为:
136+
137+
1. win环境(优先级高到低)
138+
+ 程序当前目录(通常是 `exe` 文件所在的目录)。
139+
+ Windows 系统目录(例如 `C:\Windows\System32`)。
140+
+ 当前用户的 `AppData` 文件夹。
141+
2. linux环境(优先级高到低)
142+
+ 运行时指定的 `LD_LIBRARY_PATH` 环境变量
143+
+ 可执行文件的 `rpath` / `runpath` 设置
144+
+ 系统配置文件 `/etc/ld.so.cache`
145+
+ ** 系统默认目录(最常用)**
146+
- `/lib`
147+
- `/usr/lib`
148+
- `/usr/local/lib`
149+
- `/lib64`(64 位系统)
150+
这些是硬编码在动态链接器里的。
151+
152+
> 也可以1. 设置PATH环境变量。2. 在代码里使用LoadLibrary("D:\libs\mylib.dll"); 显示加载DLL
153+
>
154+
155+
#### 链接静态库
156+
```bash
157+
g++ main.cpp -L /path/to/lib -l <lib_name>
158+
```
159+
160+
注意:静态库在编译过程中,只会检查头文件的可用性,不会在编译阶段检查三方库是否存在或是否链接正确。这是因为静态库的编译阶段只生成目标文件(`.o` 文件)并打包成 `.a` 文件,而不会涉及链接阶段。因此,在一个c++程序实现时,可划分为以下三个阶段:
161+
162+
+ **编译静态库时**
163+
- 只需要包含头文件(`include_directories`),不需要指定三方库的 `.a``.so` 文件。
164+
+ **编译主程序时**
165+
- 只需要包含静态库的头文件。
166+
- 不需要指定三方库的 `.a``.so` 文件。
167+
+ **主程序链接阶段**
168+
- 必须显式引入静态库和它依赖的三方库文件(如 `lthirdparty`)。
169+
170+
171+
172+
**四. 常见编译参数解释**(命令行编译方式):
173+
174+
1. -I(这是个大写i) + 库的头文件目录,告诉编译器去哪找头文件(必须)
175+
2. -L + 指定库文件所在目录
176+
3. -l(这是个小写L) + ,指定库名,搜索目标是.so或.a(Win的是.dll和.lib)(不需要写后缀,优先链接动态库)
177+
178+
-L和-l(小写L)的区别是前者指定库所在路径,后者指定该路径下具体库名
179+

0 commit comments

Comments
 (0)