C++面试之线程池、智能指针、设计模式

一、线程池

1、线程池实现步骤

这里就讲讲正常的一个线程池的实现步骤。

1.1 定义任务类:首先需要定义一个任务类,用于封装需要在线程池中执行的任务。任务类至少应该包含一个执行任务的方法,可以是一个函数指针或者是一个函数对象。

cpp 复制代码
class Task {
public:
    virtual void execute() = 0;
};

1.2 定义线程池类:接下来定义线程池类,其中包含了线程池的管理逻辑,如线程的创建、销毁、任务的添加等。线程池类需要包含一个线程池容器,用于存放线程对象。

cpp 复制代码
#include <vector>
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>

class ThreadPool {
public:
    ThreadPool(size_t numThreads);
    ~ThreadPool();

    void addTask(Task* task);

private:
    std::vector<std::thread> workers;  // 线程池中的线程
    std::queue<Task*> tasks;            // 任务队列
    std::mutex queueMutex;              // 保护任务队列的互斥量
    std::condition_variable condition;  // 用于线程间通信的条件变量
    bool stop;                          // 标志线程池是否停止的标志位
};

1.3 实现线程池类的构造函数和析构函数:在构造函数中创建指定数量的线程,并启动这些线程;在析构函数中停止线程池中的所有线程。

cpp 复制代码
ThreadPool::ThreadPool(size_t numThreads) : stop(false) {
    for (size_t i = 0; i < numThreads; ++i) {
        workers.emplace_back([this] {
            while (true) {
                Task* task = nullptr;
                {
                    std::unique_lock<std::mutex> lock(queueMutex);
                    condition.wait(lock, [this] { return stop || !tasks.empty(); });
                    if (stop && tasks.empty()) return;
                    task = tasks.front();
                    tasks.pop();
                }
                task->execute();
                delete task;
            }
        });
    }
}

ThreadPool::~ThreadPool() {
    {
        std::unique_lock<std::mutex> lock(queueMutex);
        stop = true;
    }
    condition.notify_all();
    for (std::thread& worker : workers) {
        worker.join();
    }
}

1.4 实现添加任务的方法:在线程池类中添加一个方法用于向任务队列中添加任务。

cpp 复制代码
void ThreadPool::addTask(Task* task) {
    {
        std::unique_lock<std::mutex> lock(queueMutex);
        tasks.push(task);
    }
    // notify_one():唤醒等待队列中的第一个线程,不存在锁争用,可立即获得锁,其他线程继续等待;
    // notify_all():唤醒所有等待队列中阻塞的线程,存在锁争用,只有一个线程能够获得锁。其余锁会继续尝试获得锁(类似于轮询),而不会再次阻塞。
    condition.notify_one();
}

1.5 使用线程池:最后,在主程序中使用定义好的线程池类来执行任务。

cpp 复制代码
int main() {
     ThreadPool pool(4);  // 创建一个包含4个线程的线程池

     // 添加任务到线程池
     for (int i = 0; i < 8; ++i) {
         pool.addTask(new YourTask());  // YourTask 是需要执行的任务类
     }

     // ...

     return 0;
 }

2、存放线程执行任务的结构体或者类型是什么?(std::function)

在一个线程池中,通常需要一个结构体或者类型来表示线程执行的任务。这个结构体或者类型需要包含执行任务的信息,比如任务的具体内容、状态等。在C++中,可以使用函数指针、std::function 或者自定义的函数对象来表示任务。

举例:

cpp 复制代码
#include <functional>

// 使用 std::function 来表示任务
struct Task {
    std::function<void()> function;

    // 构造函数
    Task(const std::function<void()>& f) : function(f) {}

    // 执行任务的方法
    void execute() {
        if (function) {
            function();
        }
    }
};

3、线程 A 如何向线程 B 发起异步请求并获取到处理结果、接口是什么?

在 C++ 中,线程 A 可以向线程 B 发起异步请求并获取处理结果的一种常见方式是使用 std::future 和 std::promise。这种方法允许线程 A 发起异步任务,并在需要时等待线程 B 完成任务并获取结果。

使用 std::future 和 std::promise 实现线程 A 向线程 B 发起异步请求并获取处理结果的简单示例:

cpp 复制代码
#include <iostream>
#include <future>
#include <thread>

void asyncTask(std::promise<int>& promiseObj) {
    // 模拟一个耗时的异步任务
    std::this_thread::sleep_for(std::chrono::seconds(2));

    // 设置 promise 的值,表示任务完成
    promiseObj.set_value(42);
}

int main() {
    // 创建一个 promise 对象和一个 future 对象
    std::promise<int> promiseObj;
    std::future<int> futureObj = promiseObj.get_future();

    // 在另一个线程中执行异步任务
    // std::ref 它的作用是将一个对象转换成一个引用包装器(reference wrapper),以便在函数模板中使用
    std::thread worker(asyncTask, std::ref(promiseObj));
    worker.detach();  // 让 worker 线程在后台运行

    // 在主线程中等待异步任务的结果
    std::cout << "Waiting for result..." << std::endl;
    int result = futureObj.get();  // 阻塞等待任务完成并获取结果
    std::cout << "Result: " << result << std::endl;

    return 0;
}

示例中,线程 A(主线程)创建了一个 std::promise 对象 promiseObj 和一个与之关联的 std::future 对象 futureObj。然后,线程 A 启动了一个新的线程(线程 B),并将 promiseObj 作为参数传递给异步任务函数 asyncTask。异步任务函数中通过 promiseObj.set_value() 设置了异步任务的结果。

在主线程中,通过 futureObj.get() 方法阻塞等待异步任务的完成,并获取到任务的结果。这样,线程 A 就能够向线程 B 发起异步请求并获取处理结果了。

二:介绍一下智能指针及特性
2.1 std::unique_ptr

1)std::unique_ptr 用于管理独占所有权的对象,即同一时间只能有一个 std::unique_ptr 指向一个对象。

2)当 std::unique_ptr 被销毁时,它所指向的对象也会被销毁,这样可以确保资源的正确释放

3)std::unique_ptr 不支持拷贝和赋值操作,但可以通过 std::move 来转移所有权。

4)适合用于管理局部对象或者作为容器元素的指针。

2.2 std::shared_ptr

1)std::shared_ptr 用于管理共享所有权的对象,即多个 std::shared_ptr 可以指向同一个对象

2)内部通过引用计数来管理资源的生命周期,当最后一个指向对象的 std::shared_ptr 被销毁时,对象会被释放。

3)支持拷贝和赋值操作,内部使用引用计数来追踪对象的引用情况。

4)适合用于多个对象共享同一资源的情况,比如多个对象共享同一个动态分配的对象。

2.3 std::weak_ptr

1)std::weak_ptr 是 std::shared_ptr 的一种辅助工具,用于解决 std::shared_ptr 的循环引用问题。

2)std::weak_ptr 本身不增加引用计数,它只是观察 std::shared_ptr 的引用计数,并提供了一种机制来检测对象是否已经被释放。

3)可以通过 std::weak_ptr 的 lock 方法获取一个指向对象的 std::shared_ptr,如果对象已经被释放,则返回一个空的 std::shared_ptr。

4)适合用于解决 std::shared_ptr 循环引用导致的内存泄漏问题。

2.4 std::auto_ptr(c++11之前)

1)std::auto_ptr 用于管理动态分配的对象,在 C++11 中已被废弃,不推荐使用。

2)std::auto_ptr 具有独占所有权,不支持拷贝构造和拷贝赋值操作,但支持移动语义。

3)在 C++11 中被 std::unique_ptr 替代,因为 std::unique_ptr 具有更好的语义和性能。

三、了解哪些设计模式(单例、工厂、建造者)
3.1 单例模式

主要用于确保一个类只有一个实例,并提供一个全局访问点来访问该实例。

1、饿汉式单例模式(线程不安全):

1)在类的静态成员变量中直接创建实例,并在类的静态方法中返回该实例。

2)这种方式在程序启动时就会创建单例对象,无论是否需要使用,可能会导致资源浪费。

3)不适合在多线程环境下使用,因为没有进行线程安全的处理。

cpp 复制代码
class Singleton {
public:
    static Singleton& getInstance() {
        static Singleton instance;
        return instance;
    }
    // 防止拷贝构造和赋值操作
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
private:
    Singleton() {}  // 私有化构造函数,禁止外部创建实例
};

2、懒汉式单例模式(线程安全):

1)使用加锁的方式保证在多线程环境下也能正常工作,但会影响性能。

2)在 getInstance 方法中加锁,避免了多个线程同时创建实例的问题。

cpp 复制代码
#include <mutex>
class Singleton {
public:
    static Singleton& getInstance() {
        std::lock_guard<std::mutex> lock(mutex);
        static Singleton instance;
        return instance;
    }
    // 防止拷贝构造和赋值操作
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
private:
    Singleton() {}  // 私有化构造函数,禁止外部创建实例
    static std::mutex mutex;
};
std::mutex Singleton::mutex;

3.2 工厂模式

主要用于封装对象的创建过程。它通过定义一个工厂类来负责创建产品对象,从而将客户端代码与具体产品的实现进行解耦。

1、简单工厂模式(Simple Factory Pattern):

1)简单工厂模式通过一个工厂类来创建产品对象,客户端只需要与工厂类交互,而不需要直接与具体产品类交互。

2)客户端通过调用工厂类的静态方法来创建产品对象,工厂类根据参数的不同来创建不同的产品对象。

cpp 复制代码
// 产品基类
class Product {
public:
    virtual void operation() = 0;
    virtual ~Product() {}
};
// 具体产品类
class ConcreteProduct : public Product {
public:
    void operation() override {
        // 具体产品的操作
    }
};
// 简单工厂类
class SimpleFactory {
public:
    static Product* createProduct() {
        return new ConcreteProduct();
    }
};

2、工厂方法模式(Factory Method Pattern):

1)工厂方法模式通过定义一个创建产品的接口,每个具体产品都有对应的工厂类负责创建。

2)客户端通过调用具体工厂类的方法来创建产品对象,不同的工厂类创建不同的产品对象。

cpp 复制代码
// 产品基类
class Product {
public:
    virtual void operation() = 0;
    virtual ~Product() {}
};
// 具体产品类
class ConcreteProduct : public Product {
public:
    void operation() override {
        // 具体产品的操作
    }
};
// 工厂接口
class Factory {
public:
    virtual Product* createProduct() = 0;
    virtual ~Factory() {}
};
// 具体工厂类
class ConcreteFactory : public Factory {
public:
    Product* createProduct() override {
        return new ConcreteProduct();
    }
};

3、抽象工厂模式(Abstract Factory Pattern):

抽象工厂模式通过定义一组工厂接口来创建一组相关或依赖对象的产品族。

客户端通过选择具体的工厂来获取相应的产品族,不同的工厂可以创建不同的产品族。

cpp 复制代码
// 抽象产品A
class AbstractProductA {
public:
    virtual void operationA() = 0;
    virtual ~AbstractProductA() {}
};

// 具体产品A1
class ConcreteProductA1 : public AbstractProductA {
public:
    void operationA() override {
        // 具体产品A1的操作
    }
};

// 抽象产品B
class AbstractProductB {
public:
    virtual void operationB() = 0;
    virtual ~AbstractProductB() {}
};

// 具体产品B1
class ConcreteProductB1 : public AbstractProductB {
public:
    void operationB() override {
        // 具体产品B1的操作
    }
};

// 抽象工厂
class AbstractFactory {
public:
    virtual AbstractProductA* createProductA() = 0;
    virtual AbstractProductB* createProductB() = 0;
    virtual ~AbstractFactory() {}
};

// 具体工厂1
class ConcreteFactory1 : public AbstractFactory {
public:
    AbstractProductA* createProductA() override {
        return new ConcreteProductA1();
    }

    AbstractProductB* createProductB() override {
        return new ConcreteProductB1();
    }
};

4、建造者模式

建造者模式是一种创建型设计模式,它的主要目的是将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建不同的表示。

建造者模式通常涉及以下几个角色:

1)Director(指挥者):负责使用建造者对象构建产品对象的算法。

2)Builder(建造者):定义创建产品各个部件的接口,以及构建产品的方法。

3)ConcreteBuilder(具体建造者):实现 Builder 接口,负责具体的产品构建工作。

4)Product(产品):表示被构建的复杂对象。

给个代码示例来加深理解:

cpp 复制代码
#include <iostream>
#include <string>

// 产品(Pizza)
class Pizza {
public:
    void setDough(const std::string& dough) {
        dough_ = dough;
    }

    void setSauce(const std::string& sauce) {
        sauce_ = sauce;
    }

    void setTopping(const std::string& topping) {
        topping_ = topping;
    }

    void showPizza() {
        std::cout << "Pizza with " << dough_ << " dough, " << sauce_ << " sauce and " << topping_ << " topping." << std::endl;
    }

private:
    std::string dough_;   // 面团
    std::string sauce_;   // 酱料
    std::string topping_; // 配料
};

// 建造者(Builder)接口
class PizzaBuilder {
public:
    virtual void buildDough() = 0;     // 建造面团
    virtual void buildSauce() = 0;     // 建造酱料
    virtual void buildTopping() = 0;   // 建造配料
    virtual Pizza* getPizza() = 0;     // 获取Pizza对象
    virtual ~PizzaBuilder() {}
};

// 具体建造者(ConcreteBuilder)
class HawaiianPizzaBuilder : public PizzaBuilder {
public:
    void buildDough() override {
        pizza_->setDough("cross");      // 设置交叉面团
    }

    void buildSauce() override {
        pizza_->setSauce("mild");       // 设置温和酱料
    }

    void buildTopping() override {
        pizza_->setTopping("ham+pineapple"); // 设置火腿+菠萝配料
    }

    Pizza* getPizza() override {
        return pizza_;  // 返回构建好的Pizza对象
    }

    HawaiianPizzaBuilder() {
        pizza_ = new Pizza(); // 在构造函数中创建Pizza对象
    }

    ~HawaiianPizzaBuilder() {
        delete pizza_;  // 析构函数中释放内存
    }

private:
    Pizza* pizza_;  // Pizza对象指针
};

// 指挥者(Director)
class Waiter {
public:
    void setPizzaBuilder(PizzaBuilder* builder) {
        pizzaBuilder_ = builder;    // 设置建造者
    }

    Pizza* getPizza() {
        return pizzaBuilder_->getPizza();   // 获取建造好的Pizza对象
    }

    void constructPizza() {
        pizzaBuilder_->buildDough();       // 建造面团
        pizzaBuilder_->buildSauce();       // 建造酱料
        pizzaBuilder_->buildTopping();     // 建造配料
    }

private:
    PizzaBuilder* pizzaBuilder_;    // 建造者对象指针
};

int main() {
    Waiter waiter;  // 创建指挥者对象
    HawaiianPizzaBuilder hawaiianPizzaBuilder; // 创建具体建造者对象

    waiter.setPizzaBuilder(&hawaiianPizzaBuilder); // 设置具体建造者对象
    waiter.constructPizza();    // 指挥者构建Pizza对象

    Pizza* pizza = waiter.getPizza(); // 获取建造好的Pizza对象
    pizza->showPizza(); // 展示Pizza对象信息

    delete pizza;   // 释放Pizza对象内存

    return 0;
}

5、观察者模式

观察者模式是一种行为设计模式,用于定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都会得到通知并自动更新。

观察者模式通常包含以下几个角色:

1)Subject(目标):目标是被观察的对象,它包含了一组观察者对象,并提供了添加、删除和通知观察者的方法。

2)Observer(观察者):观察者是依赖于目标的对象,当目标状态发生改变时,观察者会得到通知并进行相应的更新操作。

3)ConcreteSubject(具体目标):具体目标是实现了目标接口的具体对象,它维护了一组观察者对象,并在状态发生改变时通知观察者。

4)ConcreteObserver(具体观察者):具体观察者是实现了观察者接口的具体对象,它注册到具体目标中,并在目标状态发生改变时接收到通知并进行更新操作。

再来个例子:

cpp 复制代码
#include <iostream>
#include <vector>

// 观察者接口
class Observer {
public:
    virtual void update() = 0;
    virtual ~Observer() {}
};

// 目标接口
class Subject {
public:
    virtual void attach(Observer* observer) = 0;
    virtual void detach(Observer* observer) = 0;
    virtual void notify() = 0;
    virtual ~Subject() {}
};

// 具体观察者
class ConcreteObserver : public Observer {
public:
    void update() override {
        std::cout << "ConcreteObserver: Received update from subject." << std::endl;
    }
};

// 具体目标
class ConcreteSubject : public Subject {
public:
    void attach(Observer* observer) override {
        observers_.push_back(observer);
    }

    void detach(Observer* observer) override {
        // 在实际应用中可能需要实现查找并删除的逻辑
        observers_.erase(std::remove(observers_.begin(), observers_.end(), observer), observers_.end());
    }

    void notify() override {
        for (auto observer : observers_) {
            observer->update();
        }
    }

    void setState(int state) {
        state_ = state;
        notify();
    }

private:
    std::vector<Observer*> observers_;
    int state_;
};

int main() {
    ConcreteSubject subject;
    ConcreteObserver observer1, observer2;

    // 将观察者注册到目标中
    subject.attach(&observer1);
    subject.attach(&observer2);

    // 改变目标的状态,并通知观察者
    subject.setState(1);

    // 将观察者从目标中移除
    subject.detach(&observer2);

    // 再次改变目标的状态,并通知观察者
    subject.setState(2);

    return 0;
}
相关推荐
奋斗的小花生1 小时前
c++ 多态性
开发语言·c++
闲晨1 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
UestcXiye2 小时前
《TCP/IP网络编程》学习笔记 | Chapter 3:地址族与数据序列
c++·计算机网络·ip·tcp
霁月风4 小时前
设计模式——适配器模式
c++·适配器模式
jrrz08284 小时前
LeetCode 热题100(七)【链表】(1)
数据结构·c++·算法·leetcode·链表
咖啡里的茶i4 小时前
Vehicle友元Date多态Sedan和Truck
c++
WaaTong4 小时前
《重学Java设计模式》之 单例模式
java·单例模式·设计模式
海绵波波1074 小时前
Webserver(4.9)本地套接字的通信
c++
@小博的博客4 小时前
C++初阶学习第十弹——深入讲解vector的迭代器失效
数据结构·c++·学习
爱吃喵的鲤鱼5 小时前
linux进程的状态之环境变量
linux·运维·服务器·开发语言·c++