告别线程安全坑:C++ 多线程从入门到避坑全攻略

C++多线程避坑指南

目录

 

​编辑

引言

第一部分:C++多线程基础

1.1 为什么用多线程?

1.2 C++11前的多线程

1.3 C++线程库概览

第二部分:线程创建与管理

2.1 创建线程

2.2 传递参数

2.3 线程ID与硬件并发

第三部分:同步与互斥

3.1 std::mutex

3.2 RAII锁:std::lock_guard

3.3 std::unique_lock

3.4 死锁与避免

第四部分:条件变量

4.1 std::condition_variable

第五部分:原子操作

5.1 std::atomic

5.2 内存序

第六部分:异步与Future

6.1 std::async

6.2 std::promise

第七部分:C++20的jthread

第八部分:线程池

第九部分:调试与优化

9.1 调试工具

9.2 性能优化

第十部分:C++标准演进

常见错误与教训

结语


 

class 卑微码农:
    def __init__(self):
        self.技能 = ['能读懂十年前祖传代码', '擅长用Ctrl+C/V搭建世界', '信奉"能跑就别动"的玄学']
        self.发量 = 100  # 初始发量
        self.咖啡因耐受度 = '极限'
        
    def 修Bug(self, bug):
        try:
            # 试图用玄学解决问题
            if bug.严重程度 == '离谱':
                print("这一定是环境问题!")
            else:
                print("让我看看是谁又没写注释...哦,是我自己。")
        except Exception as e:
            # 如果try块都救不了,那就...
            print("重启一下试试?")
            self.发量 -= 1  # 每解决一个bug,头发-1
 
 
# 实例化一个我
我 = 卑微码农()

引言

多线程是现代C++的必备技能。现在CPU动不动就多核,不用多线程简直浪费性能。但多线程不好搞,同步、锁、条件变量,一个不小心就死锁或者数据乱了。我早年写服务器,没锁好共享资源,结果用户数据串了,修了一个通宵。这篇文章会帮你少走弯路,学好多线程,写出高效稳定的程序。

第一部分:C++多线程基础

1.1 为什么用多线程?

多线程让程序并发执行任务,比如一个线程处理用户输入,另一个线程跑计算,第三个线程写日志。多核CPU上,线程能并行,提升性能。常见场景:

  • 服务器:每个连接一个线程。
  • UI程序:主线程画界面,子线程加载数据。
  • 计算密集:并行矩阵运算。

但多线程有代价:同步复杂,调试难。我曾用单线程写游戏逻辑,后来改多线程,帧率翻倍,但debug时间也翻倍。

1.2 C++11前的多线程

C++11前,没标准线程库,靠平台API:

  • Windows:CreateThread。
  • POSIX:pthreads。

这些API麻烦,跨平台还得自己封装。C++11引入<thread>,统一了接口,幸福感暴增。

1.3 C++线程库概览

C++11起,标准库提供:

  • <thread>:线程创建、管理。
  • <mutex>:锁和同步。
  • <condition_variable>:条件等待。
  • <future>:异步任务。
  • <atomic>:原子操作。

这些工具让我从pthreads的繁琐中解放出来。

第二部分:线程创建与管理

2.1 创建线程

用std::thread创建,传函数或callable对象。

示例:简单线程。

#include <iostream>
#include <thread>
using namespace std;

void worker() {
    cout << "Worker thread running" << endl;
}

int main() {
    thread t(worker);
    t.join(); // 等待线程结束
    cout << "Main thread done" << endl;
    return 0;
}

输出:Worker thread running Main thread done

注意

  • join():主线程等子线程结束。
  • detach():子线程独立跑,结束后自动清理,但不好控制。
  • 不调用join/detach:抛std::terminate,程序崩。

我早年忘了join,程序莫名退出,后来加try-catch兜底。

2.2 传递参数

线程函数可带参数,注意生命周期。

示例

#include <iostream>
#include <thread>
#include <string>
using namespace std;

void worker(const string& msg) {
    cout << "Worker got: " << msg << endl;
}

int main() {
    string msg = "Hello from main";
    thread t(worker, msg); // 拷贝msg
    t.join();
    return 0;
}

输出:Worker got: Hello from main

若传引用,用std::ref:

void worker(string& msg) {
    msg += " modified";
}

int main() {
    string msg = "Hello";
    thread t(worker, ref(msg));
    t.join();
    cout << msg << endl; // Hello modified
    return 0;
}

2.3 线程ID与硬件并发

std::this_thread::get_id()获取线程ID,thread::hardware_concurrency()估算可用线程数。

示例

#include <iostream>
#include <thread>
using namespace std;

void worker() {
    cout << "Thread ID: " << this_thread::get_id() << endl;
}

int main() {
    cout << "Max threads: " << thread::hardware_concurrency() << endl;
    thread t1(worker);
    thread t2(worker);
    t1.join();
    t2.join();
    return 0;
}

输出(因机器而异):Max threads: 8 Thread ID: 12345 Thread ID: 67890

我用hardware_concurrency()决定线程池大小。

第三部分:同步与互斥

多线程访问共享资源,容易数据竞争。得用锁。

3.1 std::mutex

mutex(互斥锁)确保一次只有一个线程访问资源。

示例:计数器保护。

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;

mutex mtx;
int counter = 0;

void increment(int n) {
    for(int i = 0; i < n; ++i) {
        mtx.lock();
        ++counter;
        mtx.unlock();
    }
}

int main() {
    thread t1(increment, 1000);
    thread t2(increment, 1000);
    t1.join();
    t2.join();
    cout << "Counter: " << counter << endl; // 2000
    return 0;
}

不用锁,counter可能少于2000,因为++不是原子操作。

3.2 RAII锁:std::lock_guard

手动lock/unlock易忘unlock,用lock_guard自动解锁。

示例

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;

mutex mtx;
int counter = 0;

void increment(int n) {
    for(int i = 0; i < n; ++i) {
        lock_guard<mutex> lock(mtx);
        ++counter;
    } // 自动解锁
}

int main() {
    thread t1(increment, 1000);
    thread t2(increment, 1000);
    t1.join();
    t2.join();
    cout << "Counter: " << counter << endl;
    return 0;
}

lock_guard让我少写一堆清理代码,异常安全。

3.3 std::unique_lock

比lock_guard灵活,支持延迟锁定、解锁。

示例

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;

mutex mtx;
int counter = 0;

void increment(int n) {
    unique_lock<mutex> lock(mtx, defer_lock);
    for(int i = 0; i < n; ++i) {
        lock.lock();
        ++counter;
        lock.unlock();
    }
}

int main() {
    thread t1(increment, 1000);
    thread t2(increment, 1000);
    t1.join();
    t2.join();
    cout << "Counter: " << counter << endl;
    return 0;
}

unique_lock适合复杂逻辑,我在服务器用它控制资源。

3.4 死锁与避免

死锁:线程A持锁1等锁2,线程B持锁2等锁1。

示例:死锁演示。

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;

mutex mtx1, mtx2;

void func1() {
    lock_guard<mutex> lock1(mtx1);
    this_thread::sleep_for(chrono::milliseconds(10));
    lock_guard<mutex> lock2(mtx2);
    cout << "Func1 done" << endl;
}

void func2() {
    lock_guard<mutex> lock2(mtx2);
    this_thread::sleep_for(chrono::milliseconds(10));
    lock_guard<mutex> lock1(mtx1);
    cout << "Func2 done" << endl;
}

int main() {
    thread t1(func1);
    thread t2(func2);
    t1.join();
    t2.join();
    return 0;
}

可能死锁。修复:统一加锁顺序。

用std::lock:

void func1() {
    lock(mtx1, mtx2);
    lock_guard<mutex> lock1(mtx1, adopt_lock);
    lock_guard<mutex> lock2(mtx2, adopt_lock);
    cout << "Func1 done" << endl;
}

我曾因死锁修了一夜,改用std::lock后稳定。

第四部分:条件变量

条件变量用于线程间同步,等待某个条件。

4.1 std::condition_variable

配合unique_lock,notify_one/wake_all唤醒。

示例:生产者-消费者。

#include <iostream>
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>
using namespace std;

mutex mtx;
condition_variable cv;
queue<int> q;
bool finished = false;

void producer(int n) {
    for(int i = 0; i < n; ++i) {
        {
            lock_guard<mutex> lock(mtx);
            q.push(i);
            cout << "Produced: " << i << endl;
        }
        cv.notify_one();
        this_thread::sleep_for(chrono::milliseconds(100));
    }
    {
        lock_guard<mutex> lock(mtx);
        finished = true;
    }
    cv.notify_all();
}

void consumer() {
    while(true) {
        unique_lock<mutex> lock(mtx);
        cv.wait(lock, [] { return !q.empty() || finished; });
        if(q.empty() && finished) break;
        if(!q.empty()) {
            cout << "Consumed: " << q.front() << endl;
            q.pop();
        }
        lock.unlock();
        this_thread::sleep_for(chrono::milliseconds(200));
    }
}

int main() {
    thread t1(producer, 5);
    thread t2(consumer);
    t1.join();
    t2.join();
    return 0;
}

输出(顺序不定):Produced: 0 Consumed: 0 Produced: 1 Consumed: 1 ...

条件变量让我在消息队列里高效同步。

第五部分:原子操作

std::atomic提供无锁操作,性能高。

5.1 std::atomic

支持int、bool等基本类型。

示例:无锁计数器。

#include <iostream>
#include <thread>
#include <atomic>
using namespace std;

atomic<int> counter(0);

void increment(int n) {
    for(int i = 0; i < n; ++i) {
        counter++;
    }
}

int main() {
    thread t1(increment, 1000);
    thread t2(increment, 1000);
    t1.join();
    t2.join();
    cout << "Counter: " << counter << endl; // 2000
    return 0;
}

原子操作比锁快,我在高并发计数用atomic。

5.2 内存序

atomic默认sequential consistency,可用memory_order_relaxed等优化。

示例

atomic<int> x(0), y(0);

void thread1() {
    x.store(1, memory_order_relaxed);
}

void thread2() {
    y.store(1, memory_order_relaxed);
    if(x.load(memory_order_relaxed) == 1) {
        cout << "x is 1" << endl;
    }
}

内存序复杂,我一般用默认,性能敏感时研究relaxed。

第六部分:异步与Future

6.1 std::async

异步运行任务,返回std::future。

示例

#include <iostream>
#include <future>
using namespace std;

int compute(int x) {
    this_thread::sleep_for(chrono::seconds(1));
    return x * x;
}

int main() {
    auto fut = async(launch::async, compute, 5);
    cout << "Computing..." << endl;
    cout << "Result: " << fut.get() << endl;
    return 0;
}

输出:Computing... Result: 25

async简化异步任务,我用它跑后台计算。

6.2 std::promise

传递值给future。

示例

#include <iostream>
#include <future>
using namespace std;

void worker(promise<int>& p) {
    p.set_value(42);
}

int main() {
    promise<int> p;
    future<int> f = p.get_future();
    thread t(worker, ref(p));
    cout << "Result: " << f.get() << endl;
    t.join();
    return 0;
}

输出:Result: 42

promise适合线程间通信。

第七部分:C++20的jthread

C++20引入std::jthread,自动join。

示例

#include <iostream>
#include <thread>
using namespace std;

void worker() {
    cout << "jthread running" << endl;
}

int main() {
    jthread t(worker); // 自动join
    return 0;
}

输出:jthread running

jthread省心,我新项目都用它。

第八部分:线程池

手动管理线程麻烦,线程池复用线程。

示例:简单线程池。

#include <iostream>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <vector>
using namespace std;

class ThreadPool {
    vector<thread> workers;
    queue<function<void()>> tasks;
    mutex mtx;
    condition_variable cv;
    bool stop = false;

public:
    ThreadPool(size_t n) {
        for(size_t i = 0; i < n; ++i) {
            workers.emplace_back([this] {
                while(true) {
                    function<void()> task;
                    {
                        unique_lock<mutex> lock(mtx);
                        cv.wait(lock, [this] { return stop || !tasks.empty(); });
                        if(stop && tasks.empty()) return;
                        task = move(tasks.front());
                        tasks.pop();
                    }
                    task();
                }
            });
        }
    }

    void enqueue(function<void()> task) {
        {
            lock_guard<mutex> lock(mtx);
            tasks.push(move(task));
        }
        cv.notify_one();
    }

    ~ThreadPool() {
        {
            lock_guard<mutex> lock(mtx);
            stop = true;
        }
        cv.notify_all();
        for(auto& w : workers) w.join();
    }
};

int main() {
    ThreadPool pool(4);
    for(int i = 0; i < 8; ++i) {
        pool.enqueue([i] {
            cout << "Task " << i << " on thread " << this_thread::get_id() << endl;
        });
    }
    this_thread::sleep_for(chrono::seconds(1));
    return 0;
}

输出(顺序不定):Task 0 on thread ... Task 1 on thread ...

线程池是我服务器项目的标配。

第九部分:调试与优化

9.1 调试工具

  • GDB:break在锁或条件变量。
  • TSan:ThreadSanitizer查数据竞争,编译加-fsanitize=thread。
  • Valgrind:查内存问题。

我用TSan抓到过隐藏的数据竞争。

9.2 性能优化

  • 减少锁粒度:只锁关键代码。
  • 无锁编程:用atomic。
  • 线程亲和性:绑定线程到核(非标准,如pthread_setaffinity_np)。
  • 任务划分:平衡线程负载。

我优化过实时视频处理,atomic减小锁开销,帧率提升20%。

第十部分:C++标准演进

  • C++11:thread、mutex、atomic、future。
  • C++17:parallel STL算法。
  • C++20:jthread、semaphore、latch、barrier。

C++20的jthread让我少写一堆清理代码。

常见错误与教训

  • 忘join:程序terminate。
  • 数据竞争:没锁共享变量。
  • 死锁:锁顺序不一致。
  • 过度同步:锁太多,性能差。
  • 线程爆炸:创建过多线程。

教训:多用RAII锁,测试并发场景。

结语

C++多线程编程是高性能程序的利器,但坑多。我从手动pthreads到C++20 jthread,靠多练和debug积累经验。建议新手从thread练起,老手关注同步和优化。欢迎评论你的坑!

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值