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

这篇具有很好参考价值的文章主要介绍了C++面试之线程池、智能指针、设计模式。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

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

class Task {
public:
    virtual void execute() = 0;
};

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

#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 实现线程池类的构造函数和析构函数:在构造函数中创建指定数量的线程,并启动这些线程;在析构函数中停止线程池中的所有线程。

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 实现添加任务的方法:在线程池类中添加一个方法用于向任务队列中添加任务。

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

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

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 或者自定义的函数对象来表示任务。
举例:

#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 发起异步请求并获取处理结果的简单示例:

#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)不适合在多线程环境下使用,因为没有进行线程安全的处理。

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 方法中加锁,避免了多个线程同时创建实例的问题。

#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)客户端通过调用工厂类的静态方法来创建产品对象,工厂类根据参数的不同来创建不同的产品对象。

// 产品基类
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)客户端通过调用具体工厂类的方法来创建产品对象,不同的工厂类创建不同的产品对象。

// 产品基类
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):

抽象工厂模式通过定义一组工厂接口来创建一组相关或依赖对象的产品族。
客户端通过选择具体的工厂来获取相应的产品族,不同的工厂可以创建不同的产品族。

// 抽象产品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(产品):表示被构建的复杂对象。
给个代码示例来加深理解:

#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(具体观察者):具体观察者是实现了观察者接口的具体对象,它注册到具体目标中,并在目标状态发生改变时接收到通知并进行更新操作。
再来个例子:文章来源地址https://www.toymoban.com/news/detail-795229.html

#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;
}

到了这里,关于C++面试之线程池、智能指针、设计模式的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C++设计模式学习之一(共计13种)

    1)简单工厂模式 2)builder-建造者模式 3)prototype-原型模式 4)proxy-代理模式 5)decorate-装饰器模式 6)adapter-适配器模式 7)bridge-桥接模式 8)facade-外观者模式 9)flyweight-享元模式 10)composite-组合模式 11)templateMethod-模板方法模式 12)Command-命令模式 13)reposiblitychain-责任链模

    2024年02月05日
    浏览(27)
  • 设计模式(单例模式,工厂模式),线程池

    目录 什么是设计模式? 单例模式 饿汉模式 懒汉模式 工厂模式 线程池 线程池种类 ThreadPoolExcutor的构造方法: 手动实现一个线程池  计算机行业程序员水平层次不齐,为了 让所有人都能够写出规范的代码, 于是就有了设计模式, 针对一些典型的场景,给出一些典型的解决方案 单例

    2024年02月11日
    浏览(39)
  • 面试:C++ 11 智能指针

    内存泄露在维基百科中的解释如下: 在计算机科学中,内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了

    2024年02月07日
    浏览(41)
  • 一文带你通俗理解23种软件设计模式(推荐收藏,适合小白学习,附带C++例程完整源码)

    作者:翟天保Steven 版权声明:著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处        设计模式是为了解决在软件开发过程中遇到的某些问题而形成的思想。同一场景有多种设计模式可以应用,不同的模式有各自的优缺点,开发者可以基于自身需求

    2024年02月09日
    浏览(53)
  • 面试—C++《智能指针》常考点

    目录 1.为什么需要智能指针 2. 内存泄漏 2.1 什么是内存泄漏,内存泄漏的危害 2.2 内存泄漏分类 2.3 如何检测内存泄漏 2.4如何避免内存泄漏 3.智能指针的使用及原理 3.3 std::auto_ptr 3.4 std::unique_ptr 3.5 std::shared_ptr  下面我们先分析一下下面这段程序有没有什么内存方面的问题?

    2023年04月11日
    浏览(31)
  • 设计模式之多线程分工模式--- 生产-消费者模式

    设计模式之避免共享的设计模式Immutability(不变性)模式 设计模式之并发特定场景下的设计模式 Two-phase Termination(两阶段终止)模式 设计模式之避免共享的设计模式Copy-on-Write模式 设计模式之避免共享的设计模式 Thread-Specific Storage 模式 设计模式之多线程版本的if------Guarde

    2024年01月16日
    浏览(35)
  • 面试设计模式-责任链模式

    在进行请假申请,财务报销申请,需要走部门领导审批,技术总监审批,大领导审批等判断环节。存在请求方和接收方耦合性太强,代码会比较臃肿,不利于扩展和维护。 针对上面,使用责任链模式,将请求方和接收方的业务进行解耦, 客户端发送一个请求,由一个抽象的

    2024年02月09日
    浏览(40)
  • Arch - 多线程设计架构模式

    多线程设计架构模式是一种通过合理地使用线程来提高系统性能和响应能力的设计模式。以下是一些常见的多线程设计架构模式: 线程池模式:通过预先创建一组线程,将任务提交到线程池中执行,避免了线程的频繁创建和销毁,提高了系统的性能和稳定性。 生产者-消费者

    2024年02月16日
    浏览(36)
  • <JavaEE> 经典设计模式之 -- 线程池

    目录 一、线程池的概念 二、Java 标准库中的线程池类 2.1 ThreadPoolExecutor 类 2.1.1 corePoolSize 和 maximumPoolSize 2.1.2 keepAliveTime 和 unit 2.1.3 workQueue 2.1.4 threadFactory 2.1.5 handler 2.1.6 创建一个参数自定义的线程池 2.2 Executors 类 2.3 实现自己的线程池 1)什么是线程池? 准备预期需要使

    2024年02月05日
    浏览(29)
  • 设计模式之多线程分工模式--- Thread-Per-Message模式

    设计模式之避免共享的设计模式Immutability(不变性)模式 设计模式之并发特定场景下的设计模式 Two-phase Termination(两阶段终止)模式 设计模式之避免共享的设计模式Copy-on-Write模式 设计模式之避免共享的设计模式 Thread-Specific Storage 模式 设计模式之多线程版本的if------Guarde

    2024年02月01日
    浏览(27)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包