代码随想录八股训练营第四十天| 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
类继承自 Derived1
和 Derived2
,而这两个类又都继承自 Base
类。这就形成了一个菱形继承结构。
1.2.菱形继承的问题:
菱形继承的主要问题是多重继承可能导致的二义性和资源浪费。在上述例子中,Diamond
类会从 Derived1
和 Derived2
继承两个 Base
类的实例,这可能导致以下问题:
-
二义性:如果
Diamond
类需要访问Base
类的成员,编译器可能会不确定应该使用哪个Base
类的实例。 -
资源浪费:每个
Derived1
和Derived2
实例都有自己的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;
}
总结
-
菱形继承:在 C++ 中,菱形继承是一个常见的多重继承问题,它可能导致二义性和资源浪费。通过使用虚继承(virtual inheritance),可以确保基类只有一个共享实例,从而解决这个问题。
-
多线程同步机制:C++ 提供了多种同步机制,包括互斥锁(mutexes)、条件变量(condition variables)、原子操作(atomic operations)等,以确保在多线程环境中对共享资源的安全访问。正确使用这些同步工具对于避免数据竞争和死锁至关重要。
-
创建和管理线程:在 C++ 中,可以通过
std::thread
创建线程,并通过join()
或detach()
管理线程的生命周期。线程同步、线程局部存储和线程池等技术有助于高效地管理线程资源,提高程序的性能和响应能力。
通过深入理解这些概念和机制,开发者可以设计出更高效、更稳定且易于维护的多线程应用程序。随着 C++ 语言的不断发展,新的功能和改进也在不断地被引入,以支持更先进的并发编程模式。
相关文章:

代码随想录八股训练营第四十天| C++
目录 一、什么是菱形继承? 1.1.菱形继承的示例: 1.2.菱形继承的问题: 1.3.解决菱形继承问题: 二、C中的多线程同步机制? 2.1.互斥锁(Mutex): 2.2.递归互斥锁(Recursive Mutex)…...

【C++】10道经典面试题带你玩转二叉树
🦄个人主页:修修修也 🎏所属专栏:C ⚙️操作环境:Leetcode/牛客网 目录 一.根据二叉树创建字符串 二.二叉树的层序遍历 三.二叉树的层序遍历 II 四.二叉树的最近公共祖先 五.二叉搜索树与双向链表 六.从前序与中序遍历序列构造二叉树 七.从中序与后序遍历…...

【裸机装机系列】13.kali(ubuntu)-优化-自定义grub启动界面个性化背景
推荐阅读: 1.kali(ubuntu)-为什么弃用ubuntu,而选择基于debian的kali操作系统 当裸机安装了linux之后,开机的时候总会让人误会是黑客,还是优化一下开机界面吧,毕竟是日常开发使用。 注:修改有grub启动项有…...

数组高阶应用(C++版)
在C中,普通的数组(C-style array)、std::initializer_list 、 std::array和std::vector 是四种不同的容器类型,它们各自有不同的特性和用途。下面是对它们进行详细比较和解释。 1. 普通数组(C-style Array)…...

Spring(四)多线程+异步任务执行服务+常见的Enable注解+SpringUnit测试
Spring多线程 Spring通过任务执行器(TaskExecutor)来实现多线程和并发编程ThreadPoolTaskExecutor实现一个基于线程池的TaskExecutor配置类中EnableAsync开启对异步任务的支持使用Async声明该任务为异步 ①、配置类 Configuration ComponentScan(&quo…...

解析与实现二叉树
在数据结构与算法的学习中,二叉树无疑是一个重要且实用的数据结构。它不仅在理论上具有深刻的研究价值,更在实际应用中广泛存在,如搜索引擎的索引结构、文件系统的目录树、数据库的索引、游戏开发中的场景管理等等。本文将深入探讨二叉树的基…...

Java面向对象——内部类(成员内部类、静态内部类、局部内部类、匿名内部类,完整详解附有代码+案例)
文章目录 内部类17.1概述17.2成员内部类17.2.1 获取成员内部类对象17.2.2 成员内部类内存图 17.3静态内部类17.4局部内部类17.5匿名内部类17.5.1概述 内部类 17.1概述 写在一个类里面的类叫内部类,即 在一个类的里面再定义一个类。 如,A类的里面的定义B类&#x…...

操作系统笔记三
进程 把一个静态程序通过OS在内存中让cpu执行起来的动态执行过程叫进程 写代码都是用户态,而进程在执行过程中需要完成特定的功能,这些功能呢只有操作系统能提供,比如说读写文件,读写文件的过程是与硬盘打交道,这个过程…...

uniapp快速入门教程,内容来源于官方文档,仅仅记录快速入门需要了解到的知识点
uniapp快速入门教程,内容来源于官方文档,仅仅记录快速入门需要了解到的知识点 目录 介绍uniapp 介绍uniapp x 介绍功能框架图创建项目&发布组件/标签的变化js的变化css的变化工程结构和页面管理 pages.jsonmanifest.json 应用配置组件easycom组件规…...

基于微信小程序的商品展示+ssm(lw+演示+源码+运行)
商品展示系统 摘 要 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,微信小程序被用户普遍使用,为方…...

【Linux】常用指令(下)(内含more、less、 head、tail、date、find、grep、zip、tar以及学习笔记)
文章目录 前言1. more指令2. less指令(重要)3. head指令4. tail指令5. 管道(做到学会使用即可)6. date指令6.1 时间戳 7. cal指令8. find指令9. grep指令10. zip/unzip指令11. tar指令 前言 Linux下的常用指令终于要在本文落下帷…...

DesignMode__unity__抽象工厂模式在unity中的应用、用单例模式进行资源加载
目录 抽象工厂模式 思维导图 接口(抽象类) 工厂接口 抽象产品类 抽象武器接口 抽象人物接口 具体工厂和具体产品 具体工厂 (1)产品接口,生成具体人物 (2)武器接口,生成具体…...

Leetcode3289. 数字小镇中的捣蛋鬼
Every day a Leetcode 题目来源:3289. 数字小镇中的捣蛋鬼 解法1:哈希 代码: /** lc appleetcode.cn id3289 langcpp** [3289] 数字小镇中的捣蛋鬼*/// lc codestart class Solution { public:vector<int> getSneakyNumbers(vector…...

13_Python的高阶函数
高阶函数 高阶函数是Python编程中一个非常强大和有用的特性,它们允许程序员编写更简洁、更抽象的代码。 Python中的高阶函数是那些至少满足以下一个条件的函数: 接受一个或多个函数作为输入(也就是说,它的参数之一是函数&#…...

清空当前机器所有Docker容器和镜像
sudo docker stop $(sudo docker ps -aq) sudo docker rm $(sudo docker ps -aq) sudo docker rmi $(sudo docker images -q)删除当前机器上的所有Docker镜像是一个高风险操作,因为它会删除所有镜像,包括那些可能正在被容器使用的镜像。在执行此操作之前…...

FreeRTOS学习——Systick中断、SVC中断、PendSV中断
FreeRTOS学习——接口宏portmacro.h,仅用于记录自己阅读与学习源码 FreeRTOS Kernel V10.5.1 port :GCC/ARM_CM7 文章目录 Systick源码触发方式 SVC源码触发方式 PendSV源码触发方式 相关汇编指令 Systick 源码 在Systick中断xPortSysTickHandler中&am…...

汇量科技大数据面试题及参考答案
如何在 SQL 中处理三个字段完全一样的去重?在 Scala 中又该如何实现? 在 SQL 中,可以使用多种方法来处理三个字段完全一样的去重。一种常见的方法是使用 DISTINCT 关键字结合多个字段来实现。例如,假设有表 table_name,包含字段 field1、field2 和 field3,可以使用以下 S…...

移情别恋c++ ദ്ദി˶ー̀֊ー́ ) ——14.AVL树
1.AVL 树 1.1AVL 树的概念 二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查 找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii 和E.M.Landis在1962…...

Python 的数据类型与操作
一、常用内置类型(Built - in Types) Python 拥有多种内置数据类型,这些类型满足了各种编程需求,从简单的数据存储到复杂的数据结构表示。 1. 数值类型(Numeric Types) 整数(int)&a…...

Python燃烧废气排放推断算法模型
🎯要点 宏观能耗场景模型参数化输入数据,分析可视化输出结果,使用场景时间序列数据模型及定量和定性指标使用线图和箱线图、饼图、散点图、堆积条形图、桑基图等可视化模型输出结果根据气体排放过程得出其时间序列关系,使用推断模…...

Qt中多语言的操作(以QtCreator为例)
1、首先,我们在代码中与文本相关的且需要支持多语言的地方,用tr来包含多语言key(多语言key是我们自己定义的),如下 //举例 QPushButton* btnnew QPushButton(this); btn->move(20,20); btn->resize(100,50); //…...

计算机毕业设计 社区医疗服务系统的设计与实现 Java实战项目 附源码+文档+视频讲解
博主介绍:✌从事软件开发10年之余,专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ 🍅文末获取源码联系🍅 👇🏻 精…...

html+css学习
html 元素 html元素是HTML的根元素,一个文档只能有一个,其他所有元素都是其后代元素 html有一个属性为lang,其作用是: 帮助语言合成工具确定要使用的发音帮助翻译工具确定要使用的翻译规则 当属性lang“en”则表示告诉其浏览器…...

2.gitlab ce 细粒度的权限控制
需求: 在提交merge reqeust时,必须指定审核人,并且要选审核人清单里的 有个code owners应该可以做到(gitlab ce应该也可以用) 下面是参考的文档 细粒度的代码权限怎么做?极狐GitLab 代码所有者来帮忙 -…...

G - Merchant Takahashi / F - Useless for LIS
G - Merchant Takahashi 首先考虑暴力 DP。 设最后一步走到编号 ii 的城镇的方案的最大收益为 fifi,则每次集市相当于是 fTi←fj−C∣Ti−j∣Pi(1≤j≤n)。 这样每次可以通过枚举 j 来转移,这样总时间复杂度是 O(nm) 的&…...

自然语言处理实例
引子:基于聊天机器人项目的自然语言处理(NLP)学习路线 自然语言处理(Natural Language Processing,简称 NLP)是人工智能的重要分支,旨在帮助计算机理解、生成和处理人类语言。NLP 技术广泛应用于搜索引擎、机器翻译、语音识别、文本摘要、情感分析、对话系统等领域。为…...

『功能项目』主角属性值显示【75】
本章项目成果展示 我们打开上一篇74穿戴装备的项目, 本章要做的事情是制作主角属性界面,实现在面板上显示主角的攻击力等数值 制作一个简易的主角界面(创建Image与Text显示即可) 创建一个空物体 重命名为PlayerInfo 在其子级下创…...

单片机嵌入式编程中常用技术点
Open CV,QT,Linux,多线程,网络编程,文件编程在单片机嵌入式编程中,这些技术在单片机嵌入式编程中的作用: 一、OpenCV 在单片机嵌入式编程中,虽然单片机的计算能力相对有限…...

【毕业论文+源码】基于ASP+NET的人事管理系统
引言 人事管理系统是针对企业内部人事管理设计,分角色实现对公司部门及各部门员工的增、删、改、查以及对员工考勤的管理。 编写目的: 在系统需求分析的基础上,对需求分析中产生的功能模块进行过程描述,设计功能模块的内部细节&…...

计算机毕业设计 校园志愿者管理系统的设计与实现 Java实战项目 附源码+文档+视频讲解
博主介绍:✌从事软件开发10年之余,专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ 🍅文末获取源码联系🍅 👇🏻 精…...