C++多线程编程入门实战

本文旨在帮助初学者快速理解和跑通C++多线程编程,涵盖从基础概念到实际应用的完整路径。

第一部分:核心概念与准备工作

多线程编程的重要性

在多核处理器成为主流的今天,多线程编程是提高程序性能的关键技术。它允许程序同时执行多个任务,充分利用硬件资源。

C++多线程开发环境配置

  • C++11及以上版本:确保你的编译器支持C++11或更高标准
  • 编译器选项 :使用 -std=c++11(或更高)和 -pthread 标志编译
  • 头文件#include <thread>, #include <mutex>, #include <atomic>

初学者建议

  1. 从单线程开始:先实现正确的单线程版本
  2. 理解数据竞争:多个线程同时访问共享数据的危险性
  3. 掌握同步机制:互斥锁、原子操作等
  4. 避免死锁:注意锁的获取和释放顺序

第二部分:基础代码示例

示例1:单线程 vs 多线程基础

a) 单线程版本
cpp 复制代码
#include <iostream>
#include <chrono>
#include <thread>

// 模拟耗时任务
void task(int id, int duration_ms) {
    std::this_thread::sleep_for(std::chrono::milliseconds(duration_ms));
    std::cout << "Task " << id << " completed on thread: " 
              << std::this_thread::get_id() << std::endl;
}

int main() {
    auto start = std::chrono::high_resolution_clock::now();
    
    // 顺序执行任务
    task(1, 100);
    task(2, 150);
    task(3, 200);
    
    auto end = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
    
    std::cout << "Single-threaded execution time: " << duration.count() << " ms" << std::endl;
    return 0;
}

编译命令:

bash 复制代码
g++ -std=c++11 -pthread single_thread.cpp -o single_thread
b) 多线程版本
cpp 复制代码
#include <iostream>
#include <chrono>
#include <thread>
#include <vector>

void task(int id, int duration_ms) {
    std::this_thread::sleep_for(std::chrono::milliseconds(duration_ms));
    std::cout << "Task " << id << " completed on thread: " 
              << std::this_thread::get_id() << std::endl;
}

int main() {
    auto start = std::chrono::high_resolution_clock::now();
    
    // 创建线程数组
    std::vector<std::thread> threads;
    
    // 启动多个线程
    threads.emplace_back(task, 1, 100);
    threads.emplace_back(task, 2, 150);
    threads.emplace_back(task, 3, 200);
    
    // 等待所有线程完成
    for (auto& t : threads) {
        t.join();
    }
    
    auto end = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
    
    std::cout << "Multi-threaded execution time: " << duration.count() << " ms" << std::endl;
    return 0;
}

编译命令:

bash 复制代码
g++ -std=c++11 -pthread multi_thread.cpp -o multi_thread

关键点说明:

  • std::thread 创建新线程
  • emplace_back 直接在线程向量中构造线程对象
  • join() 等待线程执行完成
  • 输出中线程ID不同,表明任务在不同线程执行

示例2:数据竞争与互斥锁解决方案

a) 存在数据竞争的代码
cpp 复制代码
#include <iostream>
#include <thread>
#include <vector>

int shared_counter = 0;

void increment_without_lock(int iterations) {
    for (int i = 0; i < iterations; ++i) {
        shared_counter++; // 非原子操作,存在数据竞争
    }
}

int main() {
    const int iterations = 100000;
    std::vector<std::thread> threads;
    
    // 创建多个线程同时增加计数器
    for (int i = 0; i < 5; ++i) {
        threads.emplace_back(increment_without_lock, iterations);
    }
    
    for (auto& t : threads) {
        t.join();
    }
    
    std::cout << "Expected: " << 5 * iterations << std::endl;
    std::cout << "Actual: " << shared_counter << std::endl;
    std::cout << "Data race occurred: " << (shared_counter != 5 * iterations ? "YES" : "NO") << std::endl;
    
    return 0;
}
b) 使用互斥锁的正确版本
cpp 复制代码
#include <iostream>
#include <thread>
#include <vector>
#include <mutex>

int shared_counter = 0;
std::mutex counter_mutex;

void increment_with_lock(int iterations) {
    for (int i = 0; i < iterations; ++i) {
        std::lock_guard<std::mutex> lock(counter_mutex);
        shared_counter++;
    }
}

void increment_with_try_lock(int iterations) {
    for (int i = 0; i < iterations; ++i) {
        std::unique_lock<std::mutex> lock(counter_mutex, std::try_to_lock);
        if (lock.owns_lock()) {
            shared_counter++;
        } else {
            // 处理获取锁失败的情况
            std::this_thread::yield();
        }
    }
}

int main() {
    const int iterations = 100000;
    std::vector<std::thread> threads;
    
    // 测试 lock_guard
    shared_counter = 0;
    for (int i = 0; i < 5; ++i) {
        threads.emplace_back(increment_with_lock, iterations);
    }
    
    for (auto& t : threads) {
        t.join();
    }
    
    std::cout << "Using lock_guard - Expected: " << 5 * iterations << std::endl;
    std::cout << "Using lock_guard - Actual: " << shared_counter << std::endl;
    
    // 清空线程向量
    threads.clear();
    
    // 测试 unique_lock with try_lock
    shared_counter = 0;
    for (int i = 0; i < 5; ++i) {
        threads.emplace_back(increment_with_try_lock, iterations);
    }
    
    for (auto& t : threads) {
        t.join();
    }
    
    std::cout << "Using unique_lock(try_lock) - Expected: " << 5 * iterations << std::endl;
    std::cout << "Using unique_lock(try_lock) - Actual: " << shared_counter << std::endl;
    
    return 0;
}

关键点说明:

  • std::mutex 提供基本的互斥功能
  • std::lock_guard RAII风格,自动管理锁的生命周期
  • std::unique_lock 更灵活,支持延迟锁定、尝试锁定等

示例3:原子操作 - 无锁解决方案

cpp 复制代码
#include <iostream>
#include <thread>
#include <vector>
#include <atomic>

std::atomic<int> atomic_counter(0);

void increment_atomic(int iterations) {
    for (int i = 0; i < iterations; ++i) {
        atomic_counter++; // 原子操作,无数据竞争
    }
}

int main() {
    const int iterations = 100000;
    std::vector<std::thread> threads;
    
    for (int i = 0; i < 5; ++i) {
        threads.emplace_back(increment_atomic, iterations);
    }
    
    for (auto& t : threads) {
        t.join();
    }
    
    std::cout << "Atomic counter - Expected: " << 5 * iterations << std::endl;
    std::cout << "Atomic counter - Actual: " << atomic_counter << std::endl;
    
    return 0;
}

示例4:条件变量实现生产者-消费者模式

cpp 复制代码
#include <iostream>
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <chrono>

std::queue<int> data_queue;
std::mutex queue_mutex;
std::condition_variable queue_condvar;
const int MAX_ITEMS = 10;

void producer(int id) {
    for (int i = 0; i < 5; ++i) {
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
        
        std::unique_lock<std::mutex> lock(queue_mutex);
        
        // 等待队列有空间
        queue_condvar.wait(lock, []{ return data_queue.size() < MAX_ITEMS; });
        
        int data = id * 100 + i;
        data_queue.push(data);
        std::cout << "Producer " << id << " produced: " << data << std::endl;
        
        lock.unlock();
        queue_condvar.notify_all(); // 通知消费者
    }
}

void consumer(int id) {
    for (int i = 0; i < 3; ++i) {
        std::unique_lock<std::mutex> lock(queue_mutex);
        
        // 等待队列有数据
        queue_condvar.wait(lock, []{ return !data_queue.empty(); });
        
        int data = data_queue.front();
        data_queue.pop();
        std::cout << "Consumer " << id << " consumed: " << data << std::endl;
        
        lock.unlock();
        queue_condvar.notify_all(); // 通知生产者
    }
}

int main() {
    std::vector<std::thread> producers;
    std::vector<std::thread> consumers;
    
    // 创建2个生产者和3个消费者
    for (int i = 0; i < 2; ++i) {
        producers.emplace_back(producer, i + 1);
    }
    
    for (int i = 0; i < 3; ++i) {
        consumers.emplace_back(consumer, i + 1);
    }
    
    for (auto& t : producers) {
        t.join();
    }
    
    for (auto& t : consumers) {
        t.join();
    }
    
    return 0;
}

第三部分:进阶主题与最佳实践

1. 线程池基础实现

cpp 复制代码
#include <iostream>
#include <thread>
#include <vector>
#include <queue>
#include <functional>
#include <mutex>
#include <condition_variable>
#include <future>

class ThreadPool {
public:
    ThreadPool(size_t num_threads) : stop(false) {
        for (size_t i = 0; i < num_threads; ++i) {
            workers.emplace_back([this] {
                for (;;) {
                    std::function<void()> task;
                    {
                        std::unique_lock<std::mutex> lock(this->queue_mutex);
                        this->condition.wait(lock, [this] {
                            return this->stop || !this->tasks.empty();
                        });
                        if (this->stop && this->tasks.empty())
                            return;
                        task = std::move(this->tasks.front());
                        this->tasks.pop();
                    }
                    task();
                }
            });
        }
    }
    
    template<class F, class... Args>
    auto enqueue(F&& f, Args&&... args) 
        -> std::future<typename std::result_of<F(Args...)>::type> {
        using return_type = typename std::result_of<F(Args...)>::type;
        
        auto task = std::make_shared<std::packaged_task<return_type()>>(
            std::bind(std::forward<F>(f), std::forward<Args>(args)...)
        );
        
        std::future<return_type> res = task->get_future();
        {
            std::unique_lock<std::mutex> lock(queue_mutex);
            if(stop)
                throw std::runtime_error("enqueue on stopped ThreadPool");
            tasks.emplace([task](){ (*task)(); });
        }
        condition.notify_one();
        return res;
    }
    
    ~ThreadPool() {
        {
            std::unique_lock<std::mutex> lock(queue_mutex);
            stop = true;
        }
        condition.notify_all();
        for (std::thread &worker : workers)
            worker.join();
    }

private:
    std::vector<std::thread> workers;
    std::queue<std::function<void()>> tasks;
    std::mutex queue_mutex;
    std::condition_variable condition;
    bool stop;
};

// 使用示例
int main() {
    ThreadPool pool(4);
    std::vector<std::future<int>> results;
    
    for (int i = 0; i < 8; ++i) {
        results.emplace_back(
            pool.enqueue([i] {
                std::this_thread::sleep_for(std::chrono::seconds(1));
                std::cout << "Task " << i << " executed by thread " 
                         << std::this_thread::get_id() << std::endl;
                return i * i;
            })
        );
    }
    
    for (auto&& result : results)
        std::cout << "Result: " << result.get() << std::endl;
    
    return 0;
}

2. 最佳实践总结

  1. 优先使用RAIIstd::lock_guard, std::unique_lock
  2. 避免死锁 :按固定顺序获取锁,或使用std::lock同时锁定多个互斥量
  3. 合理使用原子操作:对于简单的计数器,原子操作比互斥锁更高效
  4. 使用条件变量进行线程间通信:避免忙等待
  5. 考虑使用线程池:避免频繁创建销毁线程的开销
  6. 异常安全:确保锁在异常情况下也能正确释放

3. 常见陷阱

  • 数据竞争:未正确同步共享数据的访问
  • 死锁:循环等待资源
  • 活锁:线程不断改变状态但无法进展
  • 优先级反转:低优先级线程持有高优先级线程需要的资源

结语

多线程编程是C++开发中的重要技能,虽然有一定难度,但通过循序渐进的学习和实践,完全可以掌握。建议从简单的示例开始,逐步深入理解同步机制,最终能够设计出高效、安全的并发程序。

相关推荐
闻缺陷则喜何志丹4 小时前
【中位数贪心】P6696 [BalticOI 2020] 图 (Day2)|普及+
c++·算法·贪心·洛谷·中位数贪心
青草地溪水旁5 小时前
设计模式(C++)详解——备忘录模式(2)
c++·设计模式·备忘录模式
小张成长计划..5 小时前
STL简介
c++
CHANG_THE_WORLD5 小时前
函数简单传入参数的汇编分析
汇编·c++·算法
HalvmånEver6 小时前
初学者入门 C++ map 容器:从基础用法到实战案例
开发语言·c++·学习·map
saber_andlibert7 小时前
【C++】——new和delete与malloc和free的区别
c语言·c++
维度攻城狮7 小时前
C++中的多线程编程及线程同步
开发语言·c++·性能优化·多线程·线程同步
拾光Ծ7 小时前
【C++哲学】面向对象的三大特性之 多态
开发语言·c++·面试
小欣加油7 小时前
leetcode 494 目标和
c++·算法·leetcode·职场和发展·深度优先