MST

星途 面试题库

面试题:C++ 引用与指针在多线程高并发场景下的精准抉择

在一个多线程高并发的 C++ 程序中,存在共享资源的读写操作。当对这些共享资源进行访问和操作时,需要考虑线程安全问题。请深入分析引用和指针在这种场景下的特性,说明在不同的并发控制策略(如互斥锁、读写锁等)下,如何精准选择引用或指针来优化性能、提高程序的稳定性,并给出具体的代码示例及详细解释。
34.5万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

引用和指针在多线程场景下的特性

  1. 引用
    • 特性:引用一旦初始化,就不能再指向其他对象,它是对象的别名。这使得在多线程环境中,如果引用的对象本身是线程安全的,那么使用引用访问该对象相对简单直接,因为不用担心引用被意外修改指向其他对象。
    • 缺点:不能为 nullptr,如果共享资源可能存在空的情况,引用处理起来不如指针灵活。
  2. 指针
    • 特性:指针可以指向不同的对象,也可以为 nullptr。这在共享资源可能为空或者需要动态重新分配资源的场景下很有用。但是,指针的灵活性也带来了风险,在多线程环境中,指针可能被意外修改指向错误的对象,导致数据访问错误。

在不同并发控制策略下的选择

  1. 互斥锁
    • 使用引用:如果共享资源在程序运行期间不会变为 nullptr,并且不需要重新分配,使用引用可以简化代码,提高可读性。因为互斥锁保证同一时间只有一个线程能访问共享资源,使用引用不会出现引用指向混乱的问题。
    • 使用指针:当共享资源可能为空或者需要动态重新分配时,指针更合适。但是在使用指针时,需要特别注意在获取互斥锁后再操作指针,防止指针在其他线程中被意外修改。
  2. 读写锁
    • 使用引用:对于读多写少的场景,如果共享资源不会变为 nullptr,引用很适合读操作。因为读操作可以多个线程同时进行,引用的稳定性可以保证数据一致性。在写操作时,获取写锁后,也可以使用引用安全地修改共享资源。
    • 使用指针:当共享资源的生命周期需要动态管理,或者在写操作时可能需要重新分配内存(导致指针变化),指针更合适。同样,在读写操作时都要正确获取读写锁,防止数据竞争。

代码示例

  1. 使用互斥锁和引用
#include <iostream>
#include <mutex>
#include <thread>

class SharedResource {
public:
    int data;
    SharedResource(int value) : data(value) {}
};

std::mutex resourceMutex;

void modifyResource(SharedResource& res, int newVal) {
    std::lock_guard<std::mutex> lock(resourceMutex);
    res.data = newVal;
}

void readResource(const SharedResource& res) {
    std::lock_guard<std::mutex> lock(resourceMutex);
    std::cout << "Read data: " << res.data << std::endl;
}

int main() {
    SharedResource resource(10);
    std::thread writer1(modifyResource, std::ref(resource), 20);
    std::thread reader1(readResource, std::ref(resource));

    writer1.join();
    reader1.join();

    return 0;
}

解释:这里 SharedResource 类有一个 int 类型的成员变量 datamodifyResource 函数通过引用修改共享资源,readResource 函数通过引用读取共享资源。在这两个函数中,都使用 std::lock_guard 来获取互斥锁,保证线程安全。

  1. 使用读写锁和指针
#include <iostream>
#include <shared_mutex>
#include <thread>
#include <memory>

class SharedResource {
public:
    int data;
    SharedResource(int value) : data(value) {}
};

std::shared_mutex resourceMutex;
std::unique_ptr<SharedResource> resourcePtr;

void writeResource(int newVal) {
    std::unique_lock<std::shared_mutex> lock(resourceMutex);
    if (!resourcePtr) {
        resourcePtr = std::make_unique<SharedResource>(newVal);
    } else {
        resourcePtr->data = newVal;
    }
}

void readResource() {
    std::shared_lock<std::shared_mutex> lock(resourceMutex);
    if (resourcePtr) {
        std::cout << "Read data: " << resourcePtr->data << std::endl;
    } else {
        std::cout << "Resource is nullptr" << std::endl;
    }
}

int main() {
    std::thread writer1(writeResource, 30);
    std::thread reader1(readResource);

    writer1.join();
    reader1.join();

    return 0;
}

解释:这里使用 std::shared_mutex 作为读写锁。writeResource 函数在获取独占锁(std::unique_lock)后,可以动态分配或修改 SharedResource 对象。readResource 函数获取共享锁(std::shared_lock)后读取共享资源。通过指针 resourcePtr 可以处理共享资源可能为空的情况,在多线程读写操作中保证数据的一致性和安全性。