C++中的"线程"通常指单个执行流(如std::thread对象),而"多线程"指程序中同时存在多个这样的执行流,并涉及它们的创建、管理和同步。实现跨线程操作的核心在于安全地处理共享数据线程间通信

以下是实现跨线程操作的主要方法:

1. 🛡️ 使用互斥锁 (Mutex) 保护共享数据

互斥锁用于确保同一时间只有一个线程能访问共享资源,防止数据竞争。

  • std::mutex: 基本互斥锁,需手动管理锁的获取和释放。

    #include <iostream>#include <thread>#include <mutex>
    
    std::mutex mtx;// 全局互斥量int shared_data = 0;
    
    void increment() {
        mtx.lock();// 请求锁定互斥量
        ++shared_data;// 修改共享资源
        mtx.unlock();// 释放互斥量
    }
    
    

    更推荐使用RAII机制自动管理锁:

  • std::lock_guard: 在构造时加锁,析构时自动解锁,适用于简单作用域。

    void safe_increment() {
        std::lock_guard<std::mutex> lock(mtx);// 自动锁定和解锁
        ++shared_data;
    }// lock_guard析构,mtx自动解锁
    
    
  • std::unique_lock: 比lock_guard更灵活,支持延迟加锁、条件变量等。

    void safe_increment_flex() {
        std::unique_lock<std::mutex> ul(mtx);
        ++shared_data;
        ul.unlock();// 可手动提前解锁// ... 执行其他不操作共享数据的任务
    }
    
    

2. 🔄 使用条件变量 (Condition Variable) 进行线程间协调

条件变量允许一个或多个线程等待某个条件成立后再继续执行,常用于生产者-消费者模型

  • std::condition_variable: 线程等待特定条件被满足。

    #include <iostream>#include <thread>#include <mutex>#include <condition_variable>#include <queue>
    
    std::mutex mtx;
    std::condition_variable cv;
    std::queue<int> data_queue;
    bool ready = false;
    
    // 生产者线程void producer() {
        std::this_thread::sleep_for(std::chrono::seconds(1));
        {
            std::lock_guard<std::mutex> lk(mtx);
            data_queue.push(42);
            ready = true;
        }
        cv.notify_one();// 通知一个等待的消费者线程
    }
    
    // 消费者线程void consumer() {
        std::unique_lock<std::mutex> lk(mtx);
    // 等待条件满足:lambda函数返回false时,线程会阻塞并释放锁
        cv.wait(lk, []{ return ready; });
    // 条件满足后,重新获得锁,继续执行int data = data_queue.front();
        data_queue.pop();
        std::cout << "Consumed: " << data << std::endl;
    }
    
    int main() {
        std::thread prod(producer);
        std::thread cons(consumer);
        prod.join();
        cons.join();
        return 0;
    }
    
    

3. ⚡ 使用原子操作 (Atomic Operations)

原子操作确保对基本数据类型(如intbool)的读写操作是不可中断的,无需加锁,性能更高。

  • std::atomic: 模板类,提供原子操作。

    #include <atomic>#include <thread>std::atomic<int> atomic_counter(0);
    
    void safe_increment_atomic() {
        for (int i = 0; i < 1000; ++i) {
            ++atomic_counter;// 原子操作,线程安全
        }
    }
    
    int main() {
        std::thread t1(safe_increment_atomic);
        std::thread t2(safe_increment_atomic);
        t1.join();
        t2.join();
        std::cout << atomic_counter << std::endl;// 输出2000return 0;
    }
    
    

4. 🚀 使用异步操作 (Async Operations)

std::asyncstd::future/std::promise可以更方便地获取另一个线程的执行结果,实现线程间数据传递。

  • std::asyncstd::future: 异步执行任务并获取返回值。

    #include <iostream>#include <future>int heavy_calculation() {
    // 模拟耗时计算return 42;
    }
    
    int main() {
    // 异步启动任务,返回一个future对象
        std::future<int> fut = std::async(std::launch::async, heavy_calculation);
    // ... 主线程可以同时做其他工作int result = fut.get();// 获取异步任务的结果(如果未完成会等待)
        std::cout << "Result: " << result << std::endl;
        return 0;
    }
    
    
  • std::promisestd::future: 在线程间传递数据。

    #include <iostream>#include <thread>#include <future>void worker(std::promise<int> prom) {
    // 做一些工作...
        prom.set_value(123);// 设置值,通知future
    }
    
    int main() {
        std::promise<int> prom;
        std::future<int> fut = prom.get_future();
        std::thread t(worker, std::move(prom));
        int value = fut.get();// 等待并获取worker线程设置的值
        std::cout << "Received: " << value << std::endl;
        t.join();
        return 0;
    }
    
    

📊 方法对比与选择建议

方法 适用场景 优点 缺点
互斥锁 (Mutex) 保护复杂的共享数据结构或代码段 通用性强,可保护任意临界区 锁竞争可能导致性能开销和死锁风险
条件变量 (Condition Variable) 线程间等待特定条件发生(生产者-消费者) 高效协调线程执行顺序 需与互斥锁配合使用,逻辑稍复杂
原子操作 (Atomic) 简单的计数器、状态标志更新 高性能,无锁编程 仅适用于基本数据类型,逻辑复杂时易出错
异步操作 (Async/Future) 需要获取另一个线程的结果 简化线程间值传递和同步 不适合需要持续通信或复杂同步的场景

选择哪种方法取决于你的具体需求:

  • 需要对复杂数据结构或代码块进行排他性访问时,使用互斥锁(优先搭配lock_guardunique_lock)。
  • 需要让线程等待某个条件成立(如任务队列非空)时,使用条件变量
  • 只需安全地修改一个整数、布尔值等简单变量时,使用原子操作
  • 需要获取另一个线程的计算结果时,使用**std::asyncstd::future**。

💡 重要注意事项

  1. 避免死锁:确保锁的获取和释放顺序一致,或使用std::lock一次性锁定多个互斥量。
  2. 线程管理:使用join()等待线程完成,或使用detach()分离线程(需谨慎,分离后无法再控制线程)。
  3. 数据传递:向线程传递引用参数时,需使用std::ref(确保对象生命周期长于线程),或直接按值传递。

实现安全高效的跨线程操作,关键在于根据场景选择合适的同步机制,并谨慎管理共享数据的访问。

更多推荐