Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
---
title: '手写线程池'
description: ""
date: "2025-09-27"
date: "2025-09-29"
tags:
- tag-one
---


# 线程池

> 以下是调用单队列BlockingQueue的代码,使用双队列时将所有BlockingQueue改为BlockingQueuePro
>

## 接口
构造:用智能指针 unique_ptr 初始化阻塞队列,创建threads_num个线程并给每个绑定Worker函数开始执行task。(此时全部Worker阻塞在Pop()里)

Post: 发布任务到线程池。

析构:通知唤醒所有消费者,没有任务就自己下班。与生产者没关系。

```cpp
#pragma once

Expand Down Expand Up @@ -38,8 +50,6 @@ private:
};
```



```cpp
#include "blockingqueue.h"
#include <memory>
Expand All @@ -55,6 +65,7 @@ ThreadPool::ThreadPool(int threads_num) {
}

// 停止线程池
// 取消队列并唤醒所有阻塞线程
ThreadPool::~ThreadPool() {
task_queue_->Cancel();
for(auto &worker : workers_) {
Expand All @@ -72,6 +83,7 @@ void ThreadPool::Post(std::function<void()> task) {
void ThreadPool::Worker() {
while (true) {
std::function<void()> task;
// 阻塞在Pop里实现
if (!task_queue_->Pop(task)) {
break;
}
Expand All @@ -86,7 +98,7 @@ void ThreadPool::Worker() {
**单队列维护:**

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

Expand Down Expand Up @@ -147,6 +159,8 @@ private:
```

# 阻塞队列(双队列版)
![画板](https://cdn.nlark.com/yuque/0/2025/jpeg/43055607/1759131100901-946e59ae-cd19-4546-aa9d-a9ee658f0b5a.jpeg)

单队列中, 生产者和消费者都要竞争同一把锁。

双队列:
Expand Down Expand Up @@ -211,6 +225,8 @@ private:
};
```



# Test
理论上双队列比单队列快,因为锁的竞争/碰撞少了。下面的实验结果也是如此:

Expand Down Expand Up @@ -398,4 +414,3 @@ private:
};
```


Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
---
title: '手写定长内存池'
description: ""
date: "2025-09-29"
tags:
- tag-one
---


# 手写定长内存池

## 设计图
![画板](https://cdn.nlark.com/yuque/0/2025/jpeg/43055607/1758718719250-e6f52459-0f73-493b-8294-7b8f931da054.jpeg)



## 代码部分
### 结构体定义
#### 管理内存池结构的结构体mempool_s
```c
typedef struct mempool_s {
int blocksize; // 每个内存块的size
int freecount; // 剩余空的内存块数量
char *free_ptr; // 指向下一空内存块
char *mem; // 整块内存的头指针
} mempool_t;
```

### 对外接口
#### memp_create: 内存池创建
```c
int memp_create(mempool_t *m, int block_size) {

if (!m) return -1;

// 1. 初始化这两个简单的int
m->blocksize = block_size;
m->freecount = MEM_PAGE_SIZE / block_size;

// 2. 开整个内存池的空间,顺便初始化m->mem
m->mem = (char *)malloc(MEM_PAGE_SIZE);
if (!m->mem) { // 开失败了(剩余空闲内存不够)
return -2;
}
// 将这个空间初始化一下
memset(m->mem, 0, MEM_PAGE_SIZE);

// 3. 初始化free_ptr
m->free_ptr = m->mem;

// 依次初始化每个block里的"next->ptr"
int i = 0;
char *ptr = m->mem;
for (i = 0;i < m->freecount;i ++) {

*(char **)ptr = ptr + block_size;
ptr = ptr + block_size;
}
// 最后一个block的"next_ptr"指向NULL
*(char **)ptr = NULL;
return 0;
}
```

#### memp_alloc: 分配block
```c
void *memp_alloc(mempool_t *m) {
// 满了
if (!m || m->freecount == 0) return NULL;
// 1. 获取当前下一个空闲块,作为返回值
void *ptr = m->free_ptr;
// 2. 更新free_ptr
m->free_ptr = *(char **)ptr;
// 3. 更新freecount
m->freecount --;

return ptr;
}
```

#### memp_free: 删除指定block
```c
void memp_free(mempool_t *m, void *ptr) {
// 相当于 ptr->next = m->free_ptr;
// 头插法将要释放的block插到空闲block链表的头部,即空闲block链表又增加了一个
*(char **)ptr = m->free_ptr;
// 更新free_ptr这一空闲block链表头指针
m->free_ptr = (char *)ptr;
// 更新freecount
m->freecount ++;
}
```

#### memp_destory: 删除整个内存池
```c
void memp_destory(mempool_t *m) {
if (!m) return ;
// 直接free内存池,因为内存池是整体malloc的,而不是一个一个block malloc的
free(m->mem);
}
```



## 使用example
```c
int main() {
mempool_t m;
memp_create(&m, 32);

void *p1 = memp_alloc(&m);
printf("memp_alloc : %p\n", p1);

void *p2 = memp_alloc(&m);
printf("memp_alloc : %p\n", p2);

void *p3 = memp_alloc(&m);
printf("memp_alloc : %p\n", p3);

memp_free(&m, p2);
}
```

输出:可以看到每个block确实是32个字节

![](https://cdn.nlark.com/yuque/0/2025/png/43055607/1759069995143-4548da88-8c23-463e-b9e7-0f7d8978f03b.png)

179 changes: 179 additions & 0 deletions app/docs/computer-science/cpp_backend/easy_compile/1_cpp_libs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
---
title: 'linux/win上的c++库'
description: ""
date: "2025-09-29"
tags:
- tag-one
---

# linux/win上的c++库


## 一、一个库长什么样?
每个库源码的格式都不一样。但都会有:

1. .hpp/.h(位于include或根目录下) :用来链接的头文件,不能没有
2. .cpp :实现头文件的逻辑,有的源码被称为头文件库(直接在头文件里实现了逻辑,没有.cpp实现,也不需要链接动态库或静态库)。
+ 在 Linux 上:`XXX.so`(shared object动态库) 或 `XXX.a`(static静态库)
+ 在 Windows 上:`XXX.lib`(静态库) 或 `XXX.dll`(动态库)

## 二、如何得到一个库?
> 非特殊标明,都是linux环境(具体为Ubuntu)
>

### 下载别人写的库
#### win环境
1. **手动从源码编译安装 :**下载或git clone一个zip/7z等压缩包,解压后得到源代码,编译得到所需库。
2. **使用对应语言的包管理工具**(c++: 如vcpkg,python: 如pip,java: 如maven)下载。如vcpkg会将包下载到vcpkg/installed/下(默认)。

> 简单介绍一下vcpkg
>
> + **vcpkg** 是微软开源的跨平台 C/C++ 包管理器。
> + 支持 **Windows / Linux / macOS**,但最早是为 Windows 生态开发的。
> + 主要用来解决:C/C++ 第三方库获取困难、编译配置复杂的问题。
>

#### linux环境
1. **从源码编译安装 **

> 有些库只能源码安装,比如最新版本的 gRPC、Protobuf。
>
> 一般步骤是:clone -> make流程或者build.sh这种一键脚本。
>

2. **使用系统自带的包管理器**

> apt, yum, dnf等
>

3. **使用对应语言的包管理工具**

> vcpkg,conan等
>

### 自己写一个库
写完.cpp和.h后,可选择将其编译成静态库或动态库。

#### 打包为静态库
```bash
g++ -c mylib.cpp -o mylib.o
ar rcs my_static_lib.a mylib.o // 不建议
// 事实上,打包时应在目标文件名前+lib。
// 这不只是约定俗成。
// 使用-lmylib时,链接器会自动加前缀去找,即找libmylib.a或.so。
// 如果不加lib前缀,只能写清完整路径了(不能依靠链接器自己去找了)
ar rcs libmy_static_lib.a mylib.o // 建议
```

#### 打包为动态库
```bash
// 简单的项目,一步到位
g++ -shared -o mylib.dll mylib.cpp
// 复杂的项目,先转为中间文件.o,再将.o文件转为mylib.dll
g++ -c -fPIC mylib.cpp -o mylib.o
g++ -shared -o my_dynamic_lib.dll mylib.o
```

-fPIC表示生成位置独立代码(Position Independent Code),这是动态库所需要的。

如上,我们就得到my_dynamic_lib.dll动态库和my_static_lib.a静态库了。

## 三、下载的库被放在了哪里
#### python的库可能放在
+ 项目根目录下的venv文件夹中(用venv创建的虚拟环境,也可以叫其他名,自己创建时命名)
+ conda目录下的某环境文件夹中(下载conda时指定一个存放环境的文件路径,所有由conda创建的虚拟环境都放在这里,环境名作为二级目录名)

#### 前端的库可能放在
+ 项目根目录下的node_modules中(有npm包管理创建和管理)

#### Java的Spring项目(Maven包管理)可能放在
+ 缓存到 ~/.m2/repository/ (本地仓库,win和linux都是)

#### c++的库(linux环境)
+ 系统自带库:`/lib`, `/lib64`, `/usr/lib`, `/usr/lib64`
+ apt/yum/dnf/pacman 包管理器库:`/usr/lib/x86_64-linux-gnu`(库文件), `/usr/include`(头文件)
+ 自己编译:`/usr/local/lib`, `/usr/local/include`
+ 包管理器(vcpkg/conan):用户目录下的专用路径
- vcpkg:`~/vcpkg/installed/<triplet>/lib`
- conan:`~/.conan/data/<package>/<version>/...`
- 自己放的:`~/lib`, `~/include`

## 四、如何使用一个库呢?
> 使用库两个步骤,一是**链接头文件**,二是**链接库实现**
>

### 链接 头文件
> 不用-I(大写i)的时候默认寻找当前文件目录
>

1. 不改变库头文件位置,硬编码指定所有库的头文件路径在哪(每个头文件各指定一次)

```bash
g++ -I path/to/s_lib1.h -I path/to/s_lib2.h ... -o output main.cpp
```

2. 整合头文件移到include目录下(注意如果是三方库,不建议移动,因为一般还有其他依赖项。自己的库,建议整合头文件),移到项目下的include目录下,然后指定头文件目录为include目录(只指定一次)

```bash
g++ -I include/ -o output main.cpp
```

(注意指定头文件目录 + #include “path/to/lib.h”共同拼接成头文件完整路径)

> 是否觉得纯命令行编译太麻烦太长,后面用CMake时写进CMakeList.txt里更方便
>

### 链接 库实现
> **对于非纯头文件库,还需要连接库实现,其分为动态库和静态库**
>

#### 链接动态库
```bash
g++ myapp.cpp -L /path/to/library -l mylib
```

在运行时建议将 .dll动态库 放在与 可执行文件 同级目录下。因为其查找顺序为:

1. win环境(优先级高到低)
+ 程序当前目录(通常是 `exe` 文件所在的目录)。
+ Windows 系统目录(例如 `C:\Windows\System32`)。
+ 当前用户的 `AppData` 文件夹。
2. linux环境(优先级高到低)
+ 运行时指定的 `LD_LIBRARY_PATH` 环境变量
+ 可执行文件的 `rpath` / `runpath` 设置
+ 系统配置文件 `/etc/ld.so.cache`
+ ** 系统默认目录(最常用)**
- `/lib`
- `/usr/lib`
- `/usr/local/lib`
- `/lib64`(64 位系统)
这些是硬编码在动态链接器里的。

> 也可以1. 设置PATH环境变量。2. 在代码里使用LoadLibrary("D:\libs\mylib.dll"); 显示加载DLL
>

#### 链接静态库
```bash
g++ main.cpp -L /path/to/lib -l <lib_name>
```

注意:静态库在编译过程中,只会检查头文件的可用性,不会在编译阶段检查三方库是否存在或是否链接正确。这是因为静态库的编译阶段只生成目标文件(`.o` 文件)并打包成 `.a` 文件,而不会涉及链接阶段。因此,在一个c++程序实现时,可划分为以下三个阶段:

+ **编译静态库时**:
- 只需要包含头文件(`include_directories`),不需要指定三方库的 `.a` 或 `.so` 文件。
+ **编译主程序时**:
- 只需要包含静态库的头文件。
- 不需要指定三方库的 `.a` 或 `.so` 文件。
+ **主程序链接阶段**:
- 必须显式引入静态库和它依赖的三方库文件(如 `lthirdparty`)。



**四. 常见编译参数解释**(命令行编译方式):

1. -I(这是个大写i) + 库的头文件目录,告诉编译器去哪找头文件(必须)
2. -L + 指定库文件所在目录
3. -l(这是个小写L) + ,指定库名,搜索目标是.so或.a(Win的是.dll和.lib)(不需要写后缀,优先链接动态库)

-L和-l(小写L)的区别是前者指定库所在路径,后者指定该路径下具体库名

Loading