MST

星途 面试题库

面试题:C++类成员回调函数异步调用中的线程安全与资源管理

在使用异步调用C++类成员回调函数(如通过`std::thread`和`std::function`)的场景下,可能会遇到线程安全和资源管理的问题。请详细阐述在这种情况下可能出现的线程安全问题(例如竞态条件、死锁等)以及相应的解决方法。同时,说明如何正确管理异步调用过程中涉及的资源(如动态分配的内存),以避免内存泄漏。给出一个复杂一些的示例代码,展示如何在实际场景中处理这些问题。
30.8万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

线程安全问题及解决方法

  1. 竞态条件
    • 问题描述:多个线程同时访问和修改共享资源,导致最终结果取决于线程执行的顺序。例如,多个线程同时对一个计数器变量进行自增操作,可能会出现数据不一致的情况。
    • 解决方法
      • 互斥锁(std::mutex:使用std::mutex来保护共享资源。在访问共享资源前锁定互斥锁,访问完成后解锁。例如:
#include <iostream>
#include <mutex>
#include <thread>

std::mutex mtx;
int counter = 0;

void increment() {
    mtx.lock();
    counter++;
    mtx.unlock();
}
 - **读写锁(`std::shared_mutex`)**:当读操作远多于写操作时,可以使用读写锁。多个线程可以同时进行读操作,但写操作时需要独占锁。例如:
#include <iostream>
#include <shared_mutex>
#include <thread>

std::shared_mutex rw_mtx;
int data = 0;

void read() {
    rw_mtx.lock_shared();
    std::cout << "Read data: " << data << std::endl;
    rw_mtx.unlock_shared();
}

void write() {
    rw_mtx.lock();
    data++;
    rw_mtx.unlock();
}
  1. 死锁
    • 问题描述:两个或多个线程相互等待对方释放资源,导致所有线程都无法继续执行。例如,线程A持有锁1并等待锁2,而线程B持有锁2并等待锁1。
    • 解决方法
      • 避免嵌套锁:尽量避免在一个线程中获取多个锁,若必须获取多个锁,确保所有线程以相同的顺序获取锁。
      • 使用std::lockstd::lock函数可以一次性获取多个锁,并且不会产生死锁。例如:
#include <iostream>
#include <mutex>
#include <thread>

std::mutex mutex1;
std::mutex mutex2;

void threadFunction() {
    std::lock(mutex1, mutex2);
    std::lock_guard<std::mutex> lock1(mutex1, std::adopt_lock);
    std::lock_guard<std::mutex> lock2(mutex2, std::adopt_lock);
    // 执行需要两个锁保护的操作
}

资源管理避免内存泄漏

  1. 智能指针
    • 问题描述:在异步调用中,如果动态分配的内存没有正确释放,可能会导致内存泄漏。例如,在一个线程中分配了内存,但是线程异常终止或者没有正确释放内存。
    • 解决方法:使用智能指针(std::unique_ptrstd::shared_ptr)。std::unique_ptr拥有对象的唯一所有权,当std::unique_ptr离开作用域时,对象会自动销毁。std::shared_ptr允许多个指针共享对象的所有权,当最后一个指向对象的std::shared_ptr销毁时,对象会自动销毁。例如:
#include <iostream>
#include <memory>
#include <thread>

void processData(std::unique_ptr<int> data) {
    // 处理数据
    std::cout << "Processing data: " << *data << std::endl;
}

int main() {
    auto data = std::make_unique<int>(42);
    std::thread t(processData, std::move(data));
    t.join();
    return 0;
}

复杂示例代码

#include <iostream>
#include <mutex>
#include <thread>
#include <functional>
#include <memory>

class SharedResource {
public:
    SharedResource(int value) : data(value) {}
    void updateData(int newVal) {
        std::lock_guard<std::mutex> lock(mtx);
        data = newVal;
    }
    int getData() const {
        std::lock_guard<std::mutex> lock(mtx);
        return data;
    }
private:
    int data;
    mutable std::mutex mtx;
};

class CallbackHandler {
public:
    CallbackHandler() : resource(std::make_shared<SharedResource>(0)) {}

    void asyncCallback() {
        std::thread([this]() {
            for (int i = 0; i < 5; ++i) {
                resource->updateData(i);
                std::cout << "Thread " << std::this_thread::get_id() << " updated data to " << resource->getData() << std::endl;
            }
        }).detach();
    }

private:
    std::shared_ptr<SharedResource> resource;
};

int main() {
    CallbackHandler handler;
    handler.asyncCallback();

    // 主线程可以继续执行其他操作
    for (int i = 0; i < 3; ++i) {
        std::cout << "Main thread doing other work " << i << std::endl;
    }

    // 主线程退出前可以等待一段时间,确保异步线程完成操作
    std::this_thread::sleep_for(std::chrono::seconds(2));
    return 0;
}

在这个示例中:

  1. SharedResource类使用std::mutex来保护成员变量data,以避免竞态条件。
  2. CallbackHandler类使用std::shared_ptr来管理SharedResource对象,确保资源在不再需要时正确释放。
  3. asyncCallback方法使用std::thread启动一个异步线程,并通过std::function传递回调函数。在回调函数中,线程安全地更新和读取共享资源。