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

目录

一、什么是菱形继承?

1.1.菱形继承的示例:

1.2.菱形继承的问题:

1.3.解决菱形继承问题:

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

2.1.互斥锁(Mutex):

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

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

[2.4.条件变量(Condition Variable):](#2.4.条件变量(Condition Variable):)

[2.5.原子操作(Atomic Operations):](#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 协程:](#3.9.使用 C++20 协程:)

总结


前言

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


**一、**什么是菱形继承?

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

1.1.菱形继承的示例:

cpp 复制代码
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 实例,这可能导致不必要的内存使用。

cpp 复制代码
#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 类的实例。

    cpp 复制代码
    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 来实现互斥锁。
cpp 复制代码
#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
cpp 复制代码
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):

  • 条件变量用于线程间的同步,允许一个或多个线程在某个条件成立之前挂起等待。条件变量通常与互斥锁一起使用。
cpp 复制代码
#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 类模板,提供了一种机制来保证操作的原子性。
cpp 复制代码
#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 来实现互斥锁。
cpp 复制代码
#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
#include <condition_variable>

3.2.创建线程:

  • 使用 std::thread 类来创建线程。你可以将函数或者 lambda 表达式传递给 std::thread 的构造函数。
cpp 复制代码
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() 方法等待线程结束。这会阻塞主线程直到指定的线程完成其任务。
cpp 复制代码
t1.join();
t2.join();

3.4.分离线程:

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

3.5.使用线程局部存储:

  • 使用 thread_local 存储来定义线程特有的数据,每个线程都有自己的独立副本。
cpp 复制代码
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)等同步机制来管理线程间的协作和资源共享。
cpp 复制代码
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.线程同步:

  • 对于需要管理大量线程的场景,可以使用线程池来减少线程创建和销毁的开销。这里是一个简单的线程池示例:
cpp 复制代码
#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 对象的状态来处理这些错误。
cpp 复制代码
std::thread myThread(task);
if (!myThread.joinable()) {
    // 处理错误
}

3.9.使用 C++20 协程:

  • C++20 引入了协程,它提供了一种更轻量级的线程管理方式,允许在单个线程内以非阻塞的方式执行多个任务。
cpp 复制代码
#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++ 语言的不断发展,新的功能和改进也在不断地被引入,以支持更先进的并发编程模式。

相关推荐
我是谁??7 分钟前
C/C++使用AddressSanitizer检测内存错误
c语言·c++
发霉的闲鱼41 分钟前
MFC 重写了listControl类(类名为A),并把双击事件的处理函数定义在A中,主窗口如何接收表格是否被双击
c++·mfc
小c君tt44 分钟前
MFC中Excel的导入以及使用步骤
c++·excel·mfc
xiaoxiao涛1 小时前
协程6 --- HOOK
c++·协程
羊小猪~~3 小时前
数据结构C语言描述2(图文结合)--有头单链表,无头单链表(两种方法),链表反转、有序链表构建、排序等操作,考研可看
c语言·数据结构·c++·考研·算法·链表·visual studio
脉牛杂德4 小时前
多项式加法——C语言
数据结构·c++·算法
legend_jz4 小时前
STL--哈希
c++·算法·哈希算法
CSUC4 小时前
【C++】父类参数有默认值时子类构造函数列表中可以省略该参数
c++
Vanranrr4 小时前
C++ QT
java·c++·qt
鸿儒5174 小时前
C++ lambda 匿名函数
开发语言·c++