MST

星途 面试题库

面试题:C++回调函数在多线程场景下的应用

在多线程环境中,假设主线程需要创建若干子线程,每个子线程执行完任务后通过回调函数通知主线程。请描述如何使用C++ 实现这一过程,包括线程创建、同步机制以及回调函数的设计和调用,同时分析可能存在的线程安全问题及解决方案。
18.1万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

线程创建

在C++ 中,可以使用<thread>库来创建线程。以下是一个简单的示例,展示如何创建一个线程:

#include <iostream>
#include <thread>

void task() {
    std::cout << "子线程正在执行任务" << std::endl;
}

int main() {
    std::thread t(task);
    t.join();
    std::cout << "主线程等待子线程完成" << std::endl;
    return 0;
}

同步机制

为了确保子线程执行完任务后主线程能够得到通知,可以使用条件变量(std::condition_variable)和互斥锁(std::mutex)来实现同步。

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <atomic>

std::mutex mtx;
std::condition_variable cv;
std::atomic<bool> taskCompleted(false);

void task() {
    std::cout << "子线程正在执行任务" << std::endl;
    // 模拟任务执行
    std::this_thread::sleep_for(std::chrono::seconds(2));
    {
        std::lock_guard<std::mutex> lock(mtx);
        taskCompleted = true;
    }
    cv.notify_one();
}

int main() {
    std::thread t(task);
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock, []{return taskCompleted;});
    std::cout << "主线程收到子线程完成的通知" << std::endl;
    t.join();
    return 0;
}

回调函数的设计和调用

可以将回调函数作为参数传递给子线程的任务函数。

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <atomic>

std::mutex mtx;
std::condition_variable cv;
std::atomic<bool> taskCompleted(false);

using Callback = void(*)();

void task(Callback callback) {
    std::cout << "子线程正在执行任务" << std::endl;
    // 模拟任务执行
    std::this_thread::sleep_for(std::chrono::seconds(2));
    {
        std::lock_guard<std::mutex> lock(mtx);
        taskCompleted = true;
    }
    cv.notify_one();
    if (callback) {
        callback();
    }
}

void callbackFunction() {
    std::cout << "回调函数被调用" << std::endl;
}

int main() {
    std::thread t(task, callbackFunction);
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock, []{return taskCompleted;});
    std::cout << "主线程收到子线程完成的通知" << std::endl;
    t.join();
    return 0;
}

线程安全问题及解决方案

  1. 竞态条件(Race Condition):当多个线程同时访问和修改共享资源时,可能会导致数据不一致。例如,在没有适当同步的情况下,多个线程同时修改taskCompleted变量。

    • 解决方案:使用互斥锁(std::mutex)来保护共享资源。在访问共享资源前加锁,访问完成后解锁。例如在修改taskCompleted变量前后使用std::lock_guard<std::mutex>来自动管理锁的生命周期。
  2. 死锁(Deadlock):当两个或多个线程相互等待对方释放锁时,会发生死锁。例如,线程A持有锁1并等待锁2,而线程B持有锁2并等待锁1。

    • 解决方案:避免嵌套锁,确保线程以相同的顺序获取锁。如果必须使用多个锁,使用std::lock函数一次性获取多个锁,该函数可以避免死锁。
  3. 条件变量的虚假唤醒(Spurious Wakeup):在某些情况下,条件变量可能在没有调用notify_onenotify_all时被唤醒。

    • 解决方案:在wait函数中使用谓词(predicate),如cv.wait(lock, []{return taskCompleted;});。这样即使发生虚假唤醒,也会检查谓词条件,只有满足条件时才继续执行。