引言

> C++ 多线程一直是很多学习者绕不开、却又最容易“学懵”的一部分内容。
一方面,它涉及操作系统、并发模型、内存可见性等底层概念;
另一方面,标准库中又引入了 thread、mutex、condition_variable、atomic、future、promise 等一整套并发工具,使得初学者很容易陷入“API 会用,但原理不清楚”的状态。
在学习和实践 C++ 多线程的过程中,我发现碎片化的知识点如果缺乏系统梳理,很难真正理解线程之间的协作方式。因此,我将自己在学习和编程过程中整理的内容系统地记录下来,覆盖了 C++ 多线程编程中最基础、也是最核心的部分。
本系列文章不追求晦涩的理论推导,而是从实际代码出发,结合典型使用场景,希望这些内容,能为正在学习 C++ 多线程的读者提供一条清晰、可实践、可回顾的入门与夯实路线。

一、线程库的基本使用

进程是运行中的程序

线程是进程中的进程

线程的多少取决于CPU的核数   几核几线程

std::thread thread1(printHelloWorld,"Hello Thread");    //创建线程
thread1.join();     //会堵塞,主程序等待线程执行完毕 
thread1.detach();     //主程序结束,子线程在后台持续运行
bool isJion = thread1.joinable();   //判断能否调用join和detach的函数

二、线程函数中的数据未定义错误

#include<iostream>
#include<thread>

void foo(int & x)
{
    x = x + 1;
}

int main()
{
    int a = 1;
    std::thread t(foo,std::ref(a));    //std::ref 传递引用类型    传递的临时变量是给引用的时候要注意类型要统一
    t.join();
    std::cout<<a<<std::endl;
    return 0;
}

传递临时变量的问题

#include<iostream>
#include<thread>

std::thread t;
void foo(int  *x)
{
    std::cout<< *x << std::endl;
}

int main()
{
    int* ptr = new int(1);
    std::thread t(foo,ptr);    //std::ref 传递引用类型    传递的临时变量是给引用的时候要注意类型要统一
    delete ptr;

    t.join();

    return 0;
}
 

     传递指针或引用指向已释放的内存的问题

#include<iostream>
#include<thread>
#include<memory>
class A
{
public:
    void foo()
    {
        std::cout<<"Hello"<<std::endl;
    }
};

int main()
{
    std::shared_ptr<A> a =std::make_shared<A>();

    std::thread t(&A::foo,a);

    t.join();
}

使用智能指针解决问题!

#include<iostream>
#include<thread>
#include<memory>
class A
{
private:
    friend void thread_foo();
    void foo()
    {
        std::cout<<"Hello"<<std::endl;
    }
};

void thread_foo()
{
    std::shared_ptr<A> a =std::make_shared<A>();

    std::thread t(&A::foo,a);
    t.join();
}


int main()
{
    thread_foo();
}

入口函数为类的私有成员函数的问题:用友元

三、互斥量解决多线程数据共享问题

数据共享问题:在多个线程中共享数据时,需要注意线程安全问题。如果多个线程同时访问同一个变量,并且其中至少有一个对该变量进行了写操作,那么就会出现数据竞争问题。数据竞争可能会导致程序崩溃、产生未定义的结果,或者得到错误的结果

解决:加锁(mutex)

#include<iostream>
#include<thread>
#include<mutex>

int a = 0;
std::mutex mtx;
void func()
{
    for (int i = 0 ; i<10000 ; i++)
    {
        mtx.lock();   //获取所有权
        a = a + 1 ;
        mtx.unlock();   //释放所有权
    }
}

int main()
{
    std::thread t1(func);
    std::thread t2(func);

    t1.join();
    t2.join();

    std::cout<<a<<std::endl;
    return 0;
}

线程安全:如果多线程程序每一次的运行结果和单线程运行的结果始终是一样的,那么你的线程就是安全的

四、互斥量死锁

#include<iostream>
#include<thread>
#include<mutex>

std::mutex m1,m2;
void func_1()
{
    for(int i = 0 ; i < 50 ; i++)
    {
    m1.lock();
    m2.lock();
    m1.unlock();
    m2.unlock();
    }

}

void func_2()
{
    for(int i = 0 ; i < 50 ; i++)
    {
    m2.lock();
    m1.lock();
    m1.unlock();
    m2.unlock();
    }

}

int main()
{
    std::thread t1(func_1);
    std::thread t2(func_2);

    t1.join();
    t2.join();

    std::cout<<"over"<<std::endl;
    return 0;
}

代码会产生死锁,如果让两个函数都m1开始获取,就不会死锁

五、lock_guard与unique_lock

1.lock_guard:

当函数构造时被调用,该互斥量会被自动锁定

当析构函数时被调用,该互斥量会被自动解锁

#include<iostream>
#include<thread>
#include<mutex>

int share_data = 0;

std::mutex mtx;

void func()
{
    for (int i = 0 ; i<10000 ; i++)
    {
        std::lock_guard<std::mutex> lg(mtx);
        share_data++;
    }
}

int main()
{
    std::thread t1(func);
    std::thread t2(func);

    t1.join();
    t2.join();

    std::cout<<share_data<<std::endl;
    return 0;
}

2.unique_lock

lock_guard能做的它都能做,并且还能做到延迟加锁、条件变量、超时等

#include<iostream>
#include<thread>
#include<mutex>

int share_data = 0;

std::timed_mutex mtx;

void func()
{
    for (int i = 0 ; i<2 ; i++)
    {
        std::unique_lock<std::timed_mutex> lg(mtx,std::defer_lock);
        if(lg.try_lock_for(std::chrono::seconds(2)))     //等待两秒
        {
            std::this_thread::sleep_for(std::chrono::seconds(1));  //抢到了睡眠1秒
            share_data++;
        }

    }
}

int main()
{
    std::thread t1(func);
    std::thread t2(func);

    t1.join();
    t2.join();

    std::cout<<share_data<<std::endl;
    return 0;
}




六、call_once与其使用场景(单例模式场景)

单例模式场景:单例设计模式是一种常见的设计模式,用于确保某个类只能创建一个实例。由于单例实例是全局唯一的,因此在多线程环境中使用单例模式时,需要考虑例程安全的问题。

(只能有一个实例,不能随便new,通过统一接口拿到这个实例)

#include<iostream>
#include<thread>
#include<mutex>
#include<string>

class Log
{
public:
    Log() {};
    Log (const Log& log) =delete;   //防止通过拷贝制造第二个实例
    Log& operator = (const Log& log) = delete;

    static Log& GetInstance()
    {
        static Log* log = nullptr;

        if(!log) log =new Log;    //懒汉模式场景,用到的时候才创建

        return *log;
    }

    void PrintLog(std::string msg)
    {
        std::cout<<__TIME__<<' '<<msg<<std::endl;
    }
};

int main()
{
    Log::GetInstance().PrintLog( "error");
}

call_once只能在线程函数中使用,在main函数中调用call_once 会报错

#include<iostream>
#include<thread>
#include<mutex>
#include<string>

static Log* log = nullptr;
static std::once_flag once;

class Log
{
public:
    Log() {};
    Log (const Log& log) =delete;
    Log& operator = (const Log& log) = delete;

    static Log& GetInstance()
    {

        std::call_once(once,init);

        return *log;
    }

    static void init()
    {
        if(!log) log =new Log;
    }

    void PrintLog(std::string msg)
    {
        std::cout<<__TIME__<<' '<<msg<<std::endl;
    }
};

void print_error()
{
    Log::GetInstance().PrintLog("error");
}

int main()
{
    std::thread t1(print_error);
    std::thread t2(print_error);
    t1.join();
    t2.join();
}

七、condition_variable与其使用场景(生产者消费者模型)

#include<iostream>
#include<thread>
#include<mutex>
#include<string>
#include<condition_variable>
#include<queue>


std::queue<int> g_queue;
std::condition_variable g_cv;
std::mutex mtx;

void Producer()
{
    for (int i = 0 ; i < 10 ; i++){
        {
            std::unique_lock<std::mutex> lock(mtx);
            g_queue.push(i);
            g_cv.notify_one();       //通知消费者有任务
            std::cout<<"Producer : " <<i<<std::endl;
        }
        std::this_thread::sleep_for(std::chrono::microseconds(1000));
    }
}


void Consumer()
{
    while(1)
    {
        std::unique_lock<std::mutex> lock(mtx);
        g_cv.wait(lock,[]() { return !g_queue.empty();          //lambda
            });
        int value = g_queue.front();
        g_queue.pop();
        std::cout<<"Consumer : " <<value<<std::endl;
    }
}


int main()
{
    std::thread t1(Producer);
    std::thread t2(Consumer);
    t1.join();
    t2.join();
    return 0;
}

八、C++跨平台线程池

#include<iostream>
#include<thread>
#include<mutex>
#include<string>
#include<condition_variable>
#include<queue>
#include<vector>
#include<functional>

class ThreadPool
{
private:
    std::vector<std::thread> threads;
    std::queue<std::function<void()>> tasks;

    std::mutex mtx;
    std::condition_variable condition;

    bool stop;

public:
    ThreadPool(int numThreads):stop(false)
    {
        for (int i = 0 ; i < numThreads; i++)
        {
            threads.emplace_back([this]
                {
                            while(1)
                            {
                                std::unique_lock<std::mutex> lock(mtx);
                                condition.wait(lock,[this]{
                                               return !tasks.empty()  || stop;
                                               });
                                if(stop && tasks.empty())
                                {
                                    return;
                                }

                                std::function<void()>  task(std::move(tasks.front()));
                                tasks.pop();
                                lock.unlock();
                                task();
                            }
                        });

            }
        }

        ~ThreadPool()
        {
        {
            std::unique_lock<std::mutex> lock(mtx);
            stop = true;
        }
        condition.notify_all();
        for(auto& t : threads)
        {
            t.join();
        }
        }

        template<class F,class... Args>
        void enqueue( F && f ,Args&&...args)
        {
            std::function<void()>task = std::bind(std::forward<F>(f),std::forward<Args>(args)...);
            {
            std::unique_lock<std::mutex> lock(mtx);
            tasks.emplace(std::move(task));
            }
            condition.notify_one();
        }


};

int main()
{
    ThreadPool pool(4);
    for (int i = 0 ; i < 10 ; i ++)
    {
        pool.enqueue( [i] {
                     std::cout<<"task : " <<i<< " is running" <<std::endl;
                     std::this_thread::sleep_for(std::chrono::seconds(1));
                     std::cout<<"task : " <<i<<"  is done " <<std::endl;
                     });
    }

}

1.右值引用

int x = 0;   //x是左值   左值为可以取地址的值
int y = x;   //x还能再用
int z = 10;  //10是右值 (临时的)

int&& r = 10   //√
int a = 5 ;
int&& r2 = a   //×

2.bind   把函数和参数绑定

3.forward 完美转发

4.lambda

5.move 移动语义

6.std::this_thread::sleep_for(std::chrono::seconds(1));

7.auto 自动类型推导

8.可变列表

九、异步并发——async future packaged_task   promise

async是C++11 引入的一个函数模板,用于异步执行一个函数,并返回一个std::future对象,表示异步操作的结果。使用std::async可以方便地进行异步编程,避免了手动创建线程和管理线程的麻烦

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

int func()
{
    int i = 0;
    for (i = 0 ; i< 1000; i++)
    {
        i++;
    }
    return i;
}

int main()
{
    std::future<int> future_result = std::async(std::launch::async,func);
    cout<<func() <<endl;

    cout<< future_result.get()<<endl;

    return 0 ;
}

//该代码的核心作用是:异步调用函数 func,并将其返回值存储在 std::future<int> 对象中。
//提高程序效率:将耗时操作放入异步线程执行,从而避免阻塞主线程。提供机制来获取异步任务的结果。

packaged_task的意义在于你可以手动的去启动线程, 而不是像async直接启动线程

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

int func()
{
    int i = 0;
    for (i = 0 ; i< 1000; i++)
    {
        i++;
    }
    return i;
}

int main()
{
    std::packaged_task<int()> task(func);

    auto future_result = task.get_future();

    std::thread t1(std::move(task));

    cout<<func() <<endl;
    t1.join();
    cout<< future_result.get()<<endl;

    return 0 ;
}

promise是一个类模板,用于在一个线程中产生一个值,并在另一个线程中获取这个值。promise通常与future和async一起使用,用于实现异步编程

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

int func(std::promise<int> &f)
{
    f.set_value(1000);
}

int main()
{
   std::promise<int> f;
   auto future_result = f.get_future();

   std::thread t1(func,std::ref(f));

   t1.join();

    cout<<future_result.get()<<endl;
    return 0 ;
}

十、原子操作 atomic

平常加锁再解锁的时间大约在57000左右

#include <iostream>
#include <thread>
#include <mutex>

int shared_data = 0;

std::mutex mtx;
void func()
{
    for (int i = 0 ; i < 1000000 ; ++i)
    {
        mtx.lock();
        shared_data++;
        mtx.unlock();
    }
}

int main()
{
    auto last = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
    std::thread t1(func);
    std::thread t2(func);
    t1.join();
    t2.join();
    std::cout<<"shared data = " <<shared_data << std::endl;
    auto cur = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
    std::cout <<cur - last<<std::endl;
    return 0 ;
}

shared data = 2000000
57838
#include <iostream>
#include <thread>
#include <mutex>
#include<atomic>


std::atomic<int> shared_data = 0;

void func()
{
    for (int i = 0 ; i < 1000000 ; ++i)
    {
        shared_data++;
    }
}

int main()
{
    auto last = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
    std::thread t1(func);
    std::thread t2(func);
    t1.join();
    t2.join();
    std::cout<<"shared data = " <<shared_data << std::endl;
    auto cur = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
    std::cout <<cur - last<<std::endl;  
    return 0 ;
}

shared data = 2000000
39893
时间缩短了近一半, 原子操作相对于加锁解锁效率是高很多的

    结尾语

    > C++ 多线程并不是“学会几个 API 就结束”的内容,它更像是一种编程思维方式的转变:
    从顺序执行,转向对并发、同步与共享资源的整体把控。
    本文所整理的内容,覆盖了 C++ 多线程编程中最基础、也是最常用的知识点,目的是帮助学习者建立完整的并发认知框架,而不是停留在“照着示例敲代码”的阶段。
    需要强调的是,多线程编程没有银弹,正确性永远优先于性能。在实际项目中,应根据具体场景谨慎选择并发模型,避免过度设计,也要警惕数据竞争和隐蔽的同步问题。
    如果本文的内容能帮助你在学习或实践中少走一些弯路,那这次整理就已经达到了它的意义。
    也欢迎读者在评论区交流自己的理解和经验,共同完善对 C++ 多线程编程的认识。
    Logo

    有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

    更多推荐