Python

  1. 异步编程

事件循环(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())
  1. 垃圾回收机制

最基础的:引用计数,当引用计数为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))

  1. 装饰器

装饰器提供了一种动态修改或者说加强函数的能力,通过使用装饰器,你可以在原函数的基础上增加一些逻辑。

那装饰器主要可以用在哪些场景呢

日志记录:在函数执行前后自动添加日志记录,方便调试和监控程序行为。

性能测试:装饰器可以用来测量函数的执行时间,帮助识别性能瓶颈。

def simple_decorator(func): # 传入一个函数作为参数
    def wrapper():
        print("即将执行函数...")
        func()
        print("函数执行完毕。")
    return wrapper
  1. 单例

类装饰器:存储一个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()
  1. 不可变对象,可变对象:int、string、tuple不可变,list、dict可变

C++

  1. class与struct区别:使用 class 时,类中的成员默认都是 private 属性的;而使用 struct 时,结构体中的成员默认都是 public 属性的。
    1. 同理,class继承默认是 private 继承,而 struct 继承默认是 public 继承。
    2. 另外,class成员可以用模板,struct不行
  2. 内存管理
    1. 栈与堆:栈每个线程独立,没有碎片,指针向下移动,速度快;堆由程序员通过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的整数倍开始存储.)

7unionsize需要满足以下两个条件,第一: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
  1. 智能指针

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 来表示其中一个方向的引用,就可以打破循环引用,使节点能够正常释放

  1. 虚函数

  2. 类型相关

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;
};
  1. 手写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;
}
  1. 手写vector
  2. memcpy,memmove,strcpy,snprinf,sprinf
    1. memcpy(不处理重叠策略)
void* memcpy(void* dest, const void* src, size_t n);
  1. C++多线程
    1. 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;
}
  1. C++特性
    1. noexcept(C++11)

以下情形鼓励使用noexcept:

  • 移动构造函数(move constructor)
  • 移动分配函数(move assignment)

让标准库容器(如 vector)敢于使用移动而非保守地使用拷贝,极大提升性能

  • 析构函数(destructor)。这里提一句,在新版本的编译器中,析构函数是默认加上关键字noexcept的。

C++ 标准规定:如果异常传播到析构函数之外(即析构函数抛出异常并退出),程序会直接调用 std::terminate() 终止。在栈展开(stack unwinding)过程中,若多个异常同时存在(比如两个对象析构都抛异常),无法处理,只能终止。

  1. CPU缓存一致性(单CPU:写回、写直达)、MESI协议(多CPU);

  2. 单CPU:内存屏障、C++内存序

  3. 无锁队列

  4. 重载赋值运算

#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