Python
- 异步编程
事件循环(Event Loop):事件循环是asyncio的核心,它负责调度和执行异步任务。当遇到await表达式时,事件循环会暂停当前协程,转而执行其他可运行的协程。
协程(Coroutine):协程是asyncio中的基本执行单元,使用async def定义。协程可以通过await关键字挂起自身,让出控制权给事件循环。
基本操作:
import asyncio
async fetch_data(url):
await asyncio.sleep(2)
return "Data"
# 主协程
async def main():
tasks = [
fetch_data("https://1.com"),
fetch_data("https://2.com"),
fetch_data("https://3.com")
]
results = await asyncio.gather(*tasks)
print(f"All results: {results}")
asyncio.run(main())
- 垃圾回收机制
最基础的:引用计数,当引用计数为0时回收对象内存
import sys
a = []
b = []
del b
print(sys.getrefcount(a)) # 2, a和sys.getrefcount参数
循环引用
x = []
y = [x]
x.append(y)
使用分代垃圾回收机制,定期检测回收不可达的对象(分代收集:新对象年轻,存货足够长则进入老年代)
gc模块:手动触发垃圾回收,检测内存泄漏(gc.set_debug(gc.DEBUG_LEAK))
- 装饰器
装饰器提供了一种动态修改或者说加强函数的能力,通过使用装饰器,你可以在原函数的基础上增加一些逻辑。
那装饰器主要可以用在哪些场景呢
日志记录:在函数执行前后自动添加日志记录,方便调试和监控程序行为。
性能测试:装饰器可以用来测量函数的执行时间,帮助识别性能瓶颈。
def simple_decorator(func): # 传入一个函数作为参数
def wrapper():
print("即将执行函数...")
func()
print("函数执行完毕。")
return wrapper
- 单例
类装饰器:存储一个class对应的实例
def singleton(cls):
_instance = {}
def _singleton(*args, **kwargs):
if cls not in _instance:
_instance[cls] = cls(*args, **kwargs)
return _instance[cls]
return _singleton
@singleton
class A(object):
a = 1 #类属性,所有实例共享(A.a)
def __init__(self, x=0):
self.x = x #实例属性
a1 = A(2)
a2 = A(3) #a2.x = 2
或者基于new方法:
import threading #线程安全
class Singleton(object):
_inst_lock = threading.Lock()
def __init__(self):
pass
def __new__(cls, *args, **kwargs):
if not hasattr(Singleton, "_instance"):
with Singleton._inst_lock:
if not hasattr(Singleton, "_instance"):
Singleton._instance = object.__new__(cls)
return Singleton._instance
obj1 = Singleton()
obj2 = Singleton()
- 不可变对象,可变对象:int、string、tuple不可变,list、dict可变
C++
- class与struct区别:使用 class 时,类中的成员默认都是 private 属性的;而使用 struct 时,结构体中的成员默认都是 public 属性的。
- 同理,class继承默认是 private 继承,而 struct 继承默认是 public 继承。
- 另外,class成员可以用模板,struct不行
- 内存管理
- 栈与堆:栈每个线程独立,没有碎片,指针向下移动,速度快;堆由程序员通过new/malloc手动管理,涉及查找内存块、分割/合并内存块、处理碎片等,速度慢。
#include <iostream>
#include <chrono>
void stackMemory() {
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < 1000000; i++) {
int arr[100]; // stack
}
auto end = std::chrono::high_resolution_clock::now();
std::cout << "Stack Time: " << std::chrono::duration_cast<std::chrono::microseconds>(end-start).count() << "microseconds" <<std::endl;
}
void heapMemory() {
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < 1000000; i++) {
int* arr = new int[100]; // heap
delete[] arr; // release
}
auto end = std::chrono::high_resolution_clock::now();
std::cout << "Heap Time: " << std::chrono::duration_cast<std::chrono::microseconds>(end-start).count() << "microseconds" <<std::endl;
}
malloc底层实现——brk与mmap:
brk是通过移动堆顶指针来实现内存分配的,速度快,通常分配量少

mmap在堆与栈之间的 “文件映射区” 创建独立的内存区域

new与malloc:new是C++的运算符,有编译类型检查,分配失败抛异常,delete释放。malloc直接与底层交互,需要自己判断申请多少内存,没有类型检查(返回void*),分配失败返回NULL,free释放
通常面向对象场景用new多一些
内存对齐
(1)空对象为1个字节。
struct A1 {};
(2) 每个元素,按照自身大小对齐;总大小按照最大元素倍数对齐
struct A2 {
char a; // 1
int b; // 4, 此时前面需要补3个字节
unsigned short c; // 2
long long d; // 8, 补6个
char e; // 1
}; //最后,补充成8的倍数,sizeof(A2) == 1 + (3) + 4 + 2 + (6) + 8 + (1) + (7) = 32
(3) 普通函数不占字节,虚函数多8个字节(虚函数表指针)
struct A3 {
void f() { // do something; }
};
struct A4 {
virtual void f() { // do something; }
};
(4) #pragma pack(N) N必须是2的幂。如果下一个元素比N大,按照N对齐;否则按照下一个元素对齐
#pragma pack(2)
struct B1 {
char a; // 1
int b; // 4, 按照2对齐。如果是pragma pack(8),按照4对齐
} //最后的倍数与N无关,按照最大元素
#pragama pack() //取消pack
(5)__attribute__((packed)) 完全取消内存对齐;__attribute__((aligned)) 16字节对齐,aligned(N)指定字节对齐(需要N大于最大的元素)
(6)如果一个结构里有某些结构体成员,则结构体成员要从其内部"最宽基本类型成员"的整数倍地址开始存储.(struct a里存有struct b,b里有char,int ,double等元素,那b应该从8的整数倍开始存储.)
(7)union:size需要满足以下两个条件,第一:size大于等于最长元素的长度;第二,size是最大类型的整数倍。
union表示几个变量共用一个内存位置,并且同一时间只能储存其中一个成员变量的值。
typedef union{
long i; //最大类型8
char j[10]; //最长元素10
int k;
}DATE; //所以是16
struct data{
int m; // 4
DATE n; //补充4个,按8存储
double l; // 8
}test; // 4+(4)+16+8 == 32
- 智能指针
unique_ptr:拷贝构造函数和赋值运算符为delete
#include <iostream>
#include <memory>
int main() {
std::unique_ptr<int> u1(new int(10));
// std::unique_ptr<int> u2 = u1; // 编译错误,不允许拷贝
std::unique_ptr<int> u3 = std::move(u1);
if (!u1) {
std::cout << "u1 is nullptr" << std::endl;
}
std::cout << *u3 << std::endl;
return 0;
}
shared_ptr:只有引用计数本身是原子的,对指针引用的对象操作不是线程安全的。从 C++20 开始,可以使用 std::atomicstd::shared_ptr 来实现对 std::shared_ptr 的原子操作,从而避免线程安全问题 。
#include <iostream>
template <typename T>
class MySharedPtr {
public:
MySharedPtr(T* ptr) m_ptr(ptr), cnt(new int(1) {}
~MySharedPtr() {release();}
MySharedPtr(const MySharedPtr& other) {
(*cnt)++;
}
MySharedPtr& operator=(const MySharedPtr& other) {
if(this != &other) {
release();
m_ptr = other.m_ptr;
cnt = other.cnt;
(*cnt)++;
}
return *this;
}
T& operator* () const {
return *m_ptr;
}
T* operator->() const {
return m_ptr;
}
int use_count() {
return *cnt;
}
private:
void release() {
(*cnt)--;
if(*cnt == 0) {
delete m_ptr;
delete cnt;
}
}
T* m_ptr;
int* cnt;
}
weak_ptr 更像是一个对对象的 “观察者”,它不拥有对象的所有权,只是对 shared_ptr 所管理的对象进行弱引用 。weak_ptr 的存在不会影响对象的引用计数,它主要用于解决 shared_ptr 之间可能出现的循环引用问题 。例如,在一个双向链表的实现中,如果节点之间使用 shared_ptr 相互引用,就会形成循环引用,导致节点无法被正确释放,而使用 weak_ptr 来表示其中一个方向的引用,就可以打破循环引用,使节点能够正常释放
虚函数
类型相关
auto、decltype(auto)
auto的问题是:忽略引用(当然可以用auto& 保留左值引用)、忽略const和volatile、数组和函数退化为指针。decltype(auto)可以保留引用、左右值和cv限定
万能引用+完美转发
一个函数需要根据左右值来区分逻辑,传参无法区分这个左右值。万能引用保留左右值,转发函数输出这个引用值。
#include <iostream>
#include <utility>
void target(int& x) {}
void target(int&& x) {}
template<typename T>
void f(T&& x) {
target(std::forward<T> (x))
}
int main() {
f(10);
int a = 10;
f(a);
return 0;
}
强制类型转换
dynamic_cast:子类到父类;父类到子类(要求父类至少有一个虚函数)。这两种情况时安全的。
static_cast在子类到父类是安全的,父类到子类不安全,需要父类指针指向子类实例
类型萃取
判断是否是某个类型(整形、指针等)、判断类型属性(const、左右值)
原理:模板偏特化机制
template<typename T>
struct isPointer {
static constexpr bool value = false;
};
template<typename T*>
struct isPointer<T*> {
static constexpr bool value = true;
};
- 手写string
#include <bits/stdc++.h>
namespace Original {
class String {
public:
// start off empty
String() {
len_ = used_ = 0;
buf_ = new char[1];
buf_[0] = '\0';
}
// free the buffer
~String() {
if(buf_){
delete[] buf_;
buf_ = nullptr;
}
}
// take a full copy
String( const String& other) {
len_ = other.len_;
used_ = other.used_;
buf_ = new char[len_ + 1];
strcpy(buf_, other.buf_);
}
// append one character
void Append( char a ) {
if(len_ < used_) {
len_ = len_ == 0? 2 : 2 * len_;
char* newbuf = new char[len_ + 1];
if(buf_) {
strcpy(newbuf, buf_);
delete[] buf_;
}
buf_ = newbuf;
}
buf_[used_] = a;
used_++;
buf_[used_] = '\0';
}
const char* c_str() {return buf_;}
private:
char* buf_; // allocated buffer
size_t len_; // length of buffer
size_t used_; // # chars actually used
};
}
int main() {
Original::String s1;
std::string a("Hello World");
for(char c:a){
s1.Append(c);
}
Original::String s2(s1);
printf("%s\n", s2.c_str());
return 0;
}
- 手写vector
- memcpy,memmove,strcpy,snprinf,sprinf
- memcpy(不处理重叠策略)
void* memcpy(void* dest, const void* src, size_t n);
- C++多线程
- func1在线程1,func2在线程2,func2必须等待func1返回:
void func1() {
// do something
notify();
}
void func2() {
// do something;
}
int main() {
func1();
wait();
func2();
}
#include <iostream>
#include <mutex>
#include <condition_variable>
#include <thread>
std::mutex mtx;
std::condition_variable cv;
bool condition = false;
void func1() {
std::cout << "Func1" << std::endl;
{
std::lock_guard<std::mutex> lock(mtx);
condition = true;
}
cv.notify_one();
}
void func2() {
// do something
}
int main() {
std::thread t1(func1);
{
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] { return condition; } );
}
std::cout << "Func1 Finished" << std::endl;
func2();
return 0;
}
这里引申出一个条件变量使用范式:
std::unique_lock<std::mutex> lock(mtx); //构造时对mtx加锁
cv.wait(lock, []{ return condition; }); //condition不为true,则释放锁并等待挂起
{
std::lock_guard<std::mutex> lock(mtx); //尝试加锁
condition = true;
} //作用域结束,析构则释放锁
cv.notify_one(); //唤醒一个等待的线程
信号量版本:
#include <iostream>
#include <semaphore>
#include <thread>
std::binary_semophore sem{0};
void func1() {
std::cout << "do something" << std::endl;
std::this_thread::sleep_for(std::chrono::miliseconds(500)); //this_thread 用来与当前线程交互,可以yield、get_id
std::cout << "Release sem" << std::endl;
sem.release();
}
void func2() {
// do something
}
int main() {
std::thread t1(func1);
std::cout << "Main thread is waiting" << std::endl;
sem.acquire();
std::cout << "Now we move on" << std::endl;
func2();
return 0;
}
- C++特性
- noexcept(C++11)
以下情形鼓励使用noexcept:
- 移动构造函数(move constructor)
- 移动分配函数(move assignment)
让标准库容器(如 vector)敢于使用移动而非保守地使用拷贝,极大提升性能
- 析构函数(destructor)。这里提一句,在新版本的编译器中,析构函数是默认加上关键字noexcept的。
C++ 标准规定:如果异常传播到析构函数之外(即析构函数抛出异常并退出),程序会直接调用 std::terminate() 终止。在栈展开(stack unwinding)过程中,若多个异常同时存在(比如两个对象析构都抛异常),无法处理,只能终止。
CPU缓存一致性(单CPU:写回、写直达)、MESI协议(多CPU);
单CPU:内存屏障、C++内存序
无锁队列
重载赋值运算
#include <iostream>
#include <string>
class Person {
private:
std::string* name; // 使用指针管理名字(仅作演示)
public:
// 构造函数
Person(const std::string& n) {
name = new std::string(n);
std::cout << "Construct: " << *name << "\n";
}
// 析构函数
~Person() {
delete name;
std::cout << "Destruct\n";
}
// 拷贝构造函数(深拷贝)
Person(const Person& other) {
name = new std::string(*(other.name));
std::cout << "Copy construct: " << *name << "\n";
}
// ✅ 重载赋值运算符 =,返回类型引用,参数是const类型引用。基本按照4步走
Person& operator=(const Person& other) {
// 1. 检查自赋值
if (this == &other) {
return *this;
}
// 2. 释放当前资源
delete name;
// 3. 深拷贝
name = new std::string(*(other.name));
std::cout << "Assignment: " << *name << "\n";
// 4. 返回引用
return *this;
}
// 打印函数
void print() const {
std::cout << "Person{name=" << *name << "}\n";
}
};
// 移动赋值
ModernString& operator=(ModernString&& other) noexcept {
if (this == &other) return *this;
delete[] data; // 释放当前资源
data = other.data; // 接管资源(而不是新开资源然后strcpy)
other.data = nullptr; // 防止 double delete
return *this;
}
copy and swap:
class String {
char* str;
String& operator=(String s) {
s.swap(*this);
return *this;
}
void swap(String& s) noexcept {
std::swap(this->str, s.str);
}
}
class MyClass {
public:
// 使用explicit关键字防止隐式转换
explicit MyClass(int value) : _value(value) {}
private:
int _value;
};
int main() {
MyClass obj = 123; // 错误:不能隐式转换
MyClass obj2(123); // 正确:显式调用构造函数
}
explicit关键词如上
在编译时运算表达式可以改善执行时间,因为需要执行的代码少了,编译器可做额外的优化。 C++ 中的关键字 constexpr(constant expression 的简称)用于声明编译时常量对象和函数。
constexpr unsigned int factorial(unsigned int const n)
{
return n > 1 ? n * factorial(n-1) : 1;
}
手撕线程池
#include <condition_variable>
#include <mutex>
#include <thread>
#include <vector>
#include <queue>
#include <functional>
#include <iostream>
#include <future>
// 启动后,每个线程首先尝试加锁。拿到锁之后等待任务,取出任务并解锁,然后执行。无限循环。
// 析构时,加锁,stop改为true,然后cv通知所有线程,再等待所有线程join。
class ThreadPool {
public:
explicit ThreadPool(size_t nums): stop(false) {
for(size_t i = 0; i < nums; i++){
workers.emplace_back([this](){
while(1) {
std::unique_lock<std::mutex> ulock(this->mtx);
this->cv.wait(ulock, [this](){
return !taskQueue.empty() || stop;
});
if (stop && taskQueue.empty()) return;
auto task = std::move(taskQueue.front());
taskQueue.pop();
ulock.unlock();
task();
}
});
}
}
~ThreadPool() {
{
std::unique_lock<std::mutex> ulock(mtx);
stop = true;
}
cv.notify_all();
for(auto &w: workers)w.join();
}
// easy 版,不需要返回值
template<typename T, typename ...Args>
void enqueue(T&& f, Args&&... args) {
std::function<void()> task = std::bind(std::forward<T>(f), std::forward<Args>(args)...);
{
std::unique_lock<std::mutex> ulock(mtx);
taskQueue.emplace(std::move(task));
}
cv.notify_one();
}
// Normal版,需要返回值(可以改成自动推导)
template<typename R, typename F, typename... Args>
std::future<R> enqueue_with_result(F&& f, Args&&... args) {
auto task = std::make_shared<std::packaged_task<R()>>(
std::bind(std::forward<F>(f), std::forward<Args>(args)...));
auto future = task->get_future();
{
std::unique_lock<std::mutex> lock(mtx);
taskQueue.emplace([task]() { (*task)(); });
}
cv.notify_one();
return future;
}
private:
std::vector<std::thread> workers;
std::queue<std::function<void()>> taskQueue;
std::mutex mtx;
std::condition_variable cv;
bool stop;
};
void work(int i) {
std::cout << "Task: " << i << " is running\n";
std::this_thread::sleep_for(std::chrono::seconds(2));
std::cout << "Task: " << i << " is done\n";
}
int main() {
ThreadPool pool(4);
for (int i = 0; i < 5; i++) {
pool.enqueue(work, i);
}
std::future<int> future = pool.enqueue_with_result<int>([](int a, int b) {
std::this_thread::sleep_for(std::chrono::seconds(1));
return a + b;
}, 2, 3);
std::cout << "Result: " << future.get() << std::endl;
return 0;
}
操作系统
协程:与线程的区别在于,线程从头执行到尾;协程能够把自己挂起,让其他线程抢占CPU。由此可以看出,协程与异步密不可分
Python有yield关键词,asyncio定义的async函数就是一个协程。event_loop(aysnc.run)就是拿来跑协程的。
NUMA架构
零拷贝
数据库
网络
tbd