当前位置: 首页 > news >正文

代码随想录八股训练营第四十天| C++

目录

一、什么是菱形继承?

1.1.菱形继承的示例:

1.2.菱形继承的问题:

1.3.解决菱形继承问题:

二、C++中的多线程同步机制?

2.1.互斥锁(Mutex):

2.2.递归互斥锁(Recursive Mutex):

2.3.读写锁(Read-Write Lock):

2.4.条件变量(Condition Variable):

2.5.原子操作(Atomic Operations):

2.6.屏障(Barrier):

2.7.信号量(Semaphore):

2.8.纤程(Fiber):

三、如何在c++中创建和管理线程?

3.1.包含必要的头文件:

3.2.创建线程:

3.3.等待线程结束:

3.4.分离线程:

3.5.使用线程局部存储:

3.6.线程同步:

3.7.线程同步:

3.8.线程同步:

3.9.使用 C++20 协程:

总结


前言

在现代软件开发中,多线程编程和继承结构的合理设计是提高程序性能和代码复用性的关键。C++ 作为一种功能强大的编程语言,提供了丰富的特性来支持多线程编程和复杂的继承模式。本文档将详细介绍 C++ 中的菱形继承问题、多线程同步机制,以及如何在 C++ 中创建和管理线程。


一、什么是菱形继承?

在 C++ 中,菱形继承(Diamond Inheritance)是指一个类(称为派生类)继承两个或多个基类,而这些基类又有一个共同的基类。这种继承结构在类图上呈现出菱形,因此得名。菱形继承在 C++ 中特别常见,因为它允许多重继承,这是 C++ 与其他面向对象语言(如 Java 或 C#)的一个重要区别。

1.1.菱形继承的示例:

class Base {
public:void show() { cout << "Base show" << endl; }
};class Derived1 : public Base {
public:void show() { cout << "Derived1 show" << endl; }
};class Derived2 : public Base {
public:void show() { cout << "Derived2 show" << endl; }
};class Diamond : public Derived1, public Derived2 {
public:void show() { cout << "Diamond show" << endl; }
};

在这个例子中,Diamond 类继承自 Derived1Derived2,而这两个类又都继承自 Base 类。这就形成了一个菱形继承结构。

1.2.菱形继承的问题:

菱形继承的主要问题是多重继承可能导致的二义性和资源浪费。在上述例子中,Diamond 类会从 Derived1Derived2 继承两个 Base 类的实例,这可能导致以下问题:

  • 二义性:如果 Diamond 类需要访问 Base 类的成员,编译器可能会不确定应该使用哪个 Base 类的实例。

  • 资源浪费:每个 Derived1Derived2 实例都有自己的 Base 实例,这可能导致不必要的内存使用。

#include <iostream>
#include <vector>
#include <algorithm>int main() {std::vector<int> nums = {1, 2, 3, 4, 5};// 使用 lambda 表达式对向量进行排序std::sort(nums.begin(), nums.end(), [](int a, int b) {return a > b; // 降序排序});// 使用 lambda 表达式打印向量中的元素std::for_each(nums.begin(), nums.end(), [](int n) {std::cout << n << " ";});return 0;
}

1.3.解决菱形继承问题:

C++ 提供了几种方法来解决菱形继承的问题:

  • 虚继承(Virtual Inheritance):通过使用虚继承,可以确保 Base 类只有一个实例,即使它被多个基类继承。这可以通过在继承列表中使用 virtual 关键字来实现。使用虚继承后,Diamond 类将只有一个 Base 类的实例。

    class Base {
    public:void show() { cout << "Base show" << endl; }
    };class Derived1 : virtual public Base {
    // ...
    };class Derived2 : virtual public Base {
    // ...
    };class Diamond : public Derived1, public Derived2 {
    // ...
    };
  • 接口继承:在某些情况下,可以通过将共同的基类定义为接口(纯虚函数类)来避免菱形继承的问题。

  • 重新设计类结构:有时,重新设计类的结构可以避免菱形继承,例如通过使用组合而不是继承。


二、C++中的多线程同步机制?

在 C++ 中,多线程同步是确保多个线程在访问共享资源时能够正确、高效地协调工作的一种机制。C++11 标准引入了多线程支持,提供了一系列的同步工具,包括互斥锁(mutexes)、条件变量(condition variables)、原子操作(atomic operations)等。以下是一些常用的多线程同步机制:

2.1.互斥锁(Mutex):

  • 互斥锁用于保护临界区,确保同一时间只有一个线程可以访问共享资源。在 C++ 中,可以使用 std::mutex 来实现互斥锁。
#include <mutex>
#include <thread>std::mutex mtx;
int shared_data = 0;void increment() {mtx.lock();shared_data++;mtx.unlock();
}int main() {std::thread t1(increment);std::thread t2(increment);t1.join();t2.join();return 0;
}

2.2.递归互斥锁(Recursive Mutex)

  • 递归互斥锁允许同一个线程多次获得同一互斥锁。在 C++ 中,可以使用 std::recursive_mutex
class Singleton {
private:Singleton() {} // 构造函数Singleton(const Singleton&) = delete; // 禁止拷贝Singleton& operator=(const Singleton&) = delete; // 禁止赋值public:static Singleton& getInstance() {static Singleton instance; // 局部静态变量return instance;}
};

2.3.读写锁(Read-Write Lock):

  • 读写锁允许多个读线程同时访问共享资源,但写线程在访问时会独占资源。C++ 标准库中没有直接提供读写锁,但可以使用 std::shared_mutex(C++17 引入)。

2.4.条件变量(Condition Variable):

  • 条件变量用于线程间的同步,允许一个或多个线程在某个条件成立之前挂起等待。条件变量通常与互斥锁一起使用。
#include <mutex>
#include <condition_variable>
#include <thread>std::mutex mtx;
std::condition_variable cv;
bool ready = false;void print_id(int id) {std::unique_lock<std::mutex> lock(mtx);while (!ready) {cv.wait(lock);}// 临界区std::cout << "Thread " << id << '\n';
}void go() {std::unique_lock<std::mutex> lock(mtx);ready = true;cv.notify_all();
}int main() {std::thread threads[10];for (int i = 0; i < 10; ++i)threads[i] = std::thread(print_id, i);std::cout << "10 threads ready to race...\n";go();for (auto& th : threads) th.join();return 0;
}

2.5.原子操作(Atomic Operations):

  • 原子操作是不可分割的操作,用于在没有互斥锁的情况下同步线程。C++11 引入了 std::atomic 类模板,提供了一种机制来保证操作的原子性。
#include <atomic>
#include <thread>std::atomic<int> ai(0);void increment() {ai++;
}int main() {std::thread t1(increment);std::thread t2(increment);t1.join();t2.join();return 0;
}

2.6.屏障(Barrier):

  • 屏障是一种同步机制,它允许一组线程相互等待,直到所有线程都到达屏障点。

2.7.信号量(Semaphore):

  • 信号量是一种计数器,用于控制对共享资源的访问数量。在 C++ 标准库中没有直接提供信号量,但可以使用第三方库或者自定义实现。

2.8.纤程(Fiber):

  • 纤程是一种轻量级的线程,通常用于协程的实现。在 C++20 中,引入了协程的支持,可以通过纤程来实现非阻塞的同步。
  • 重新设计类结构:有时,重新设计类的结构可以避免菱形继承,例如通过使用组合而不是继承。


三、如何在c++中创建和管理线程?

在 C++ 中创建和管理线程涉及到几个关键步骤,包括创建线程、执行任务、同步线程以及管理线程生命周期。以下是详细的步骤和示例代码:

3.1.包含必要的头文件:

  • 互斥锁用于保护临界区,确保同一时间只有一个线程可以访问共享资源。在 C++ 中,可以使用 std::mutex 来实现互斥锁。
#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
#include <condition_variable>

3.2.创建线程:

  • 使用 std::thread 类来创建线程。你可以将函数或者 lambda 表达式传递给 std::thread 的构造函数。
void print_id(int id) {std::cout << "Thread " << id << std::endl;
}int main() {std::thread t1(print_id, 1);std::thread t2(print_id, 2);
}

3.3.等待线程结束:

  • 使用 join() 方法等待线程结束。这会阻塞主线程直到指定的线程完成其任务。
t1.join();
t2.join();

3.4.分离线程:

  • 使用 detach() 方法可以让线程在后台运行,主线程可以继续执行而不需要等待它结束。
t1.detach();
t2.detach();

3.5.使用线程局部存储:

  • 使用 thread_local 存储来定义线程特有的数据,每个线程都有自己的独立副本。
thread_local int thread_local_data = 0;void increment() {thread_local_data++;std::cout << "Thread local data: " << thread_local_data << std::endl;
}int main() {std::thread t1(increment);std::thread t2(increment);t1.join();t2.join();
}

3.6.线程同步:

  • 使用互斥锁(std::mutex)、条件变量(std::condition_variable)、原子操作(std::atomic)等同步机制来管理线程间的协作和资源共享。
std::mutex mtx;
std::condition_variable cv;
bool ready = false;void print_message() {std::unique_lock<std::mutex> lock(mtx);cv.wait(lock, []{ return ready; });std::cout << "Thread is ready to run" << std::endl;
}int main() {std::thread t1(print_message);{std::lock_guard<std::mutex> lock(mtx);ready = true;}cv.notify_one();t1.join();
}

3.7.线程同步:

  • 对于需要管理大量线程的场景,可以使用线程池来减少线程创建和销毁的开销。这里是一个简单的线程池示例:
#include <vector>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <iostream>class ThreadPool {
public:ThreadPool(size_t threads) : stop(false) {for(size_t i = 0; i < threads; ++i) {workers.emplace_back([this] {while(true) {std::function<void()> task;{std::unique_lock<std::mutex> lock(this->queue_mutex);this->condition.wait(lock, [this] { return this->stop || !this->tasks.empty(); });if(this->stop && this->tasks.empty())return;task = std::move(this->tasks.front());this->tasks.pop();}task();}});}}template<class F, class... Args>auto enqueue(F&& f, Args&&... args) -> std::future<typename std::result_of<F(Args...)>::type> {using return_type = typename std::result_of<F(Args...)>::type;auto task = std::make_shared<std::packaged_task<return_type()>>(std::bind(std::forward<F>(f), std::forward<Args>(args)...));std::future<return_type> res = task->get_future();{std::unique_lock<std::mutex> lock(queue_mutex);if(stop)throw std::runtime_error("enqueue on stopped ThreadPool");tasks.emplace([task](){ (*task)(); });}condition.notify_one();return res;}~ThreadPool() {{std::unique_lock<std::mutex> lock(queue_mutex);stop = true;}condition.notify_all();for(std::thread &worker: workers)worker.join();}private:std::vector<std::thread> workers;std::queue<std::function<void()>> tasks;std::mutex queue_mutex;std::condition_variable condition;bool stop;
};int main() {ThreadPool pool(4);auto result = pool.enqueue([](int answer) { return answer; }, 42);std::cout << "The answer is " << result.get() << std::endl;return 0;
}

3.8.线程同步:

  • 在创建线程时,可能会发生错误(例如,系统资源不足)。可以通过检查 std::thread 对象的状态来处理这些错误。
std::thread myThread(task);
if (!myThread.joinable()) {// 处理错误
}

3.9.使用 C++20 协程:

  • C++20 引入了协程,它提供了一种更轻量级的线程管理方式,允许在单个线程内以非阻塞的方式执行多个任务。
#include <coroutine>
#include <thread>
#include <iostream>generator<int> GetNumbers() {for (int i = 0; i < 5; ++i) {co_yield i;}
}task<void> RunGenerator() {for (auto n : GetNumbers()) {std::cout << n << std::endl;}
}int main() {std::jthread jthread(RunGenerator());jthread.join();return 0;
}

总结

  1. 菱形继承:在 C++ 中,菱形继承是一个常见的多重继承问题,它可能导致二义性和资源浪费。通过使用虚继承(virtual inheritance),可以确保基类只有一个共享实例,从而解决这个问题。

  2. 多线程同步机制:C++ 提供了多种同步机制,包括互斥锁(mutexes)、条件变量(condition variables)、原子操作(atomic operations)等,以确保在多线程环境中对共享资源的安全访问。正确使用这些同步工具对于避免数据竞争和死锁至关重要。

  3. 创建和管理线程:在 C++ 中,可以通过 std::thread 创建线程,并通过 join()detach() 管理线程的生命周期。线程同步、线程局部存储和线程池等技术有助于高效地管理线程资源,提高程序的性能和响应能力。

通过深入理解这些概念和机制,开发者可以设计出更高效、更稳定且易于维护的多线程应用程序。随着 C++ 语言的不断发展,新的功能和改进也在不断地被引入,以支持更先进的并发编程模式。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Leetcode Hot 100刷题记录 -Day14(矩阵置0)
  • Nacos未授权访问
  • 大工程师插件下载 官方地址
  • 【数据结构】十大经典排序算法总结与分析
  • Vue3.0组合式API:computed计算属性、watch监听器、watchEffect高级监听器
  • C++ 常用设计模式
  • 有源滤波器UAF42
  • Golang协程泄漏定位和排查
  • 项目小总结
  • 在CentOS上搭建NFS服务器
  • rtmp推流
  • yolov8多任务模型-目标检测+车道线检测+可行驶区域检测-yolo多检测头代码+教程
  • 硬件工程师笔试面试——变压器
  • OpenAl o1论文:Let’s Verify Step by Step 快速解读
  • 模版进阶(template)
  • [NodeJS] 关于Buffer
  • es6(二):字符串的扩展
  • ES6语法详解(一)
  • in typeof instanceof ===这些运算符有什么作用
  • JavaScript/HTML5图表开发工具JavaScript Charts v3.19.6发布【附下载】
  • Java多态
  • js对象的深浅拷贝
  • JS函数式编程 数组部分风格 ES6版
  • Linux下的乱码问题
  • Netty 框架总结「ChannelHandler 及 EventLoop」
  • pdf文件如何在线转换为jpg图片
  • python3 使用 asyncio 代替线程
  • python学习笔记-类对象的信息
  • vue2.0项目引入element-ui
  • 表单中readonly的input等标签,禁止光标进入(focus)的几种方式
  • 服务器从安装到部署全过程(二)
  • 基于组件的设计工作流与界面抽象
  • 前端自动化解决方案
  • 区块链共识机制优缺点对比都是什么
  • 优秀架构师必须掌握的架构思维
  • 自制字幕遮挡器
  • 400多位云计算专家和开发者,加入了同一个组织 ...
  • ​Benvista PhotoZoom Pro 9.0.4新功能介绍
  • $.ajax()参数及用法
  • (51单片机)第五章-A/D和D/A工作原理-A/D
  • (C#)Windows Shell 外壳编程系列9 - QueryInfo 扩展提示
  • (Git) gitignore基础使用
  • (附源码)ssm教材管理系统 毕业设计 011229
  • (十一)c52学习之旅-动态数码管
  • (四)opengl函数加载和错误处理
  • (转)GCC在C语言中内嵌汇编 asm __volatile__
  • (转)重识new
  • *上位机的定义
  • .net core 客户端缓存、服务器端响应缓存、服务器内存缓存
  • .NET delegate 委托 、 Event 事件,接口回调
  • .net mvc部分视图
  • .net 调用海康SDK以及常见的坑解释
  • .net 开发怎么实现前后端分离_前后端分离:分离式开发和一体式发布
  • @EventListener注解使用说明
  • @NestedConfigurationProperty 注解用法