MST
星途 面试题库

面试题:C++多线程环境下成员函数区分对象数据的挑战与解决方案

在多线程环境中,一个类的成员函数需要访问和修改对象的不同数据成员。请分析这个过程中可能遇到的并发访问问题,例如数据竞争等,并且详细说明如何通过锁机制、原子操作、线程局部存储等技术来确保成员函数正确区分和安全访问不同对象的数据,结合复杂的多线程场景代码示例进行阐述。
17.4万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

可能遇到的并发访问问题

  1. 数据竞争:当多个线程同时访问和修改同一个数据成员时,可能导致数据不一致。例如,线程A读取数据成员x,线程B同时修改x,线程A再使用x时,可能使用到已被修改的值,造成结果错误。
  2. 竞态条件:多个线程的执行顺序不确定,可能导致依赖于特定执行顺序的代码逻辑出现问题。比如初始化操作可能在使用之后才完成。

解决技术

  1. 锁机制

    • 互斥锁(Mutex):使用互斥锁来保护对数据成员的访问。在访问和修改数据成员前,线程获取互斥锁,访问完成后释放互斥锁。这样同一时间只有一个线程能访问和修改数据成员。
    • 读写锁(Read - Write Lock):适用于读多写少的场景。允许多个线程同时读数据成员,但写操作必须独占。读操作时获取读锁,写操作时获取写锁。
  2. 原子操作 对于一些简单的数据类型(如整数、指针等),可以使用原子操作。原子操作是不可分割的,不会被其他线程打断,从而避免数据竞争。例如,std::atomic<int>类型的变量,其自增、自减等操作都是原子的。

  3. 线程局部存储(TLS) 每个线程都有自己独立的存储空间,线程访问和修改的数据成员是自己独有的,不会与其他线程的数据产生竞争。常用于每个线程需要自己独立的状态变量等场景。

代码示例

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

class MyClass {
private:
    int data1;
    std::atomic<int> data2;
    std::mutex mtx;
public:
    MyClass() : data1(0), data2(0) {}

    void modifyData1(int value) {
        std::lock_guard<std::mutex> lock(mtx);
        data1 += value;
    }

    void modifyData2(int value) {
        data2 += value;
    }
};

void threadFunction(MyClass& obj) {
    for (int i = 0; i < 1000; ++i) {
        obj.modifyData1(i);
        obj.modifyData2(i);
    }
}

int main() {
    MyClass obj;
    std::vector<std::thread> threads;
    for (int i = 0; i < 10; ++i) {
        threads.emplace_back(threadFunction, std::ref(obj));
    }
    for (auto& th : threads) {
        th.join();
    }
    std::cout << "data1: " << obj.data1 << std::endl;
    std::cout << "data2: " << obj.data2 << std::endl;
    return 0;
}

在上述代码中,data1通过互斥锁mtx来保证安全访问。modifyData1函数在访问和修改data1前获取互斥锁,离开作用域时自动释放。data2使用std::atomic<int>类型,其修改操作是原子的,无需额外锁机制。在多线程环境下,不同线程对data1data2的并发访问和修改能保证数据一致性。