段总 發表於 2026-1-9 02:00:00

【C++】多线程

<h2 id="前言">前言</h2>
<p>实现多线程(win32 API、pthread、std::thread)、线程同步(互斥量、原子变量、读写锁、条件变量、线程局部存储)、如何调试。</p>
<h2 id="多线程">多线程</h2>
<p><strong>线程</strong>:是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。<br>
<strong>多线程</strong>:是多任务处理的一种特殊形式。</p>
<p>一般情况下,两种类型的多任务处理:<strong>基于进程和基于线程</strong>。</p>
<ul>
<li>基于进程的多任务处理是程序的并发执行。</li>
<li>基于线程的多任务处理是同一程序的片段的并发执行。
<ul>
<li><strong>并发</strong>:多个任务在时间片段内交替执行,表现出同时进行的效果。</li>
<li><strong>并行</strong>:多个任务在多个处理器或处理器核上同时执行。</li>
</ul>
</li>
</ul>
<p><strong>C++ 多线程编程</strong>涉及在一个程序中创建和管理多个并发执行的线程。</p>
<h2 id="实现多线程">实现多线程</h2>
<p>在C++ 11 新特性中std::thread对linux中的pthread和windows中的Win32 API进行封装,支持跨平台、移动语义等特点,本文主要使用std::thread,对pthread和Thread简单使用。</p>
<h3 id="使用windowsh实现">使用&lt;windows.h&gt;实现</h3>
<p>windows下的原生API进行创建线程。</p>
<ul>
<li>接口</li>
</ul>
<pre><code class="language-C++">//创建线程
HANDLE CreateThread(
    _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,// 安全属性
    _In_ SIZE_T dwStackSize,                           // 堆栈大小
    _In_ LPTHREAD_START_ROUTINE lpStartAddress,      // 线程函数地址
    _In_opt_ LPVOID lpParameter,                     // 线程参数
    _In_ DWORD dwCreationFlags,                        // 创建标志
    _Out_opt_ LPDWORD lpThreadId                     // 接收线程ID
);

//关闭句柄
CloseHandle(
    _In_ _Post_ptr_invalid_ HANDLE hObject
    );

//待线程结束
//等待事件、信号量等同步对象
DWORD WaitForSingleObject(
    _In_ HANDLE hHandle,      // 要等待的对象句柄
    _In_ DWORD dwMilliseconds   // 超时时间(毫秒)
);

// 使用标志变量让线程自然退出
// 使用事件对象通知线程退出
BOOL TerminateThread(
    _In_ HANDLE hThread,   // 要终止的线程句柄
    _In_ DWORD dwExitCode// 线程退出码
);

// 检查线程是否仍在运行
// 获取线程的执行结果
// 调试和错误处理
BOOL GetExitCodeThread(
    _In_ HANDLE hThread,         // 线程句柄
    _Out_ LPDWORD lpExitCode   // 接收退出码的指针
);

// 设置当前线程属性(优先级、亲和性等)
// 在线程函数中操作自身
HANDLE GetCurrentThread(VOID);// 无参数,返回当前线程伪句柄
</code></pre>
<ul>
<li>实现</li>
</ul>
<pre><code class="language-C++">#include &lt;windows.h&gt;
#include &lt;iostream&gt;
using namespace std;

DWORD WINAPI threadrun(LPVOID lpParamter)
{
    for (int i = 0; i &lt; 10; i++) {
      cout &lt;&lt; "Threadrun:" &lt;&lt; i &lt;&lt; endl;
      Sleep(50);
    }
    return 0;
}

int main()
{
    HANDLE hThread = CreateThread(NULL, 0, threadrun, NULL, 0, NULL);
    CloseHandle(hThread); //CloseHandle只是关闭了句柄,并不会终止线程。但是,如果主线程退出,进程会终止,所有线程都会结束。

    for (int i = 0; i &lt; 10; i++) {
      cout &lt;&lt; "Main:" &lt;&lt; i &lt;&lt; endl;
      Sleep(10);
    }

    //WaitForSingleObject(hThread, INFINITE); //等待线程完成 ,前提是hThread没有关闭
    return 0;
}
</code></pre>
<h3 id="使用pthread实现">使用pthread实现</h3>
<ul>
<li>接口</li>
</ul>
<pre><code class="language-C++">// 创建线程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine)(void *), void *arg);

// 线程退出
void pthread_exit(void *retval);

// 等待线程结束
int pthread_join(pthread_t thread, void **retval);

// 分离线程
int pthread_detach(pthread_t thread);


// 取消线程
int pthread_cancel(pthread_t thread);

// 获取当前线程ID
pthread_t pthread_self(void);

// 比较线程ID
int pthread_equal(pthread_t t1, pthread_t t2);

// 初始化线程属性
int pthread_attr_init(pthread_attr_t *attr);

// 销毁线程属性
int pthread_attr_destroy(pthread_attr_t *attr);

// 设置分离状态
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

// 获取分离状态
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);

// 设置堆栈大小
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);

// 设置调度策略
/*
* 参数:policy -
*   SCHED_FIFO    先进先出
*   SCHED_RR      轮转
*   SCHED_OTHER   其他(默认)
*/
int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);

</code></pre>
<ul>
<li>实现</li>
</ul>
<pre><code class="language-C++">#include &lt;pthread.h&gt;      // POSIX 线程库头文件
#include &lt;stdio.h&gt;      // 标准输入输出头文件
#include &lt;stdlib.h&gt;       // 标准库头文件(包含exit等函数)
#include &lt;unistd.h&gt;       // Unix标准库头文件,包含getpid(), sleep()等系统调用
#include &lt;iostream&gt;       // C++标准输入输出流
#include &lt;cstring&gt;
using namespace std;      // 使用std命名空间

// 线程函数 - 子线程的入口点
// 参数:threadid - 传递给线程的参数(这里用作线程标识符)
// 返回值:void* - 线程退出时可以返回一个指针(这里返回NULL)
void *PrintThread(void *threadid)
{
    // 获取当前线程ID的方式:
    // 使用pthread_self()获取POSIX线程ID(pthread_t类型)
    pthread_t id = pthread_self();
    pid_t tid = getpid();// 这获取的是进程ID,不是线程ID

    cout &lt;&lt; "ChildThread:" &lt;&lt; " pid=" &lt;&lt; tid &lt;&lt; endl;// 这里打印的是进程ID
      cout &lt;&lt; "ChildSelf:" &lt;&lt; " id=" &lt;&lt; id &lt;&lt; endl;
    for(int i = 0; i &lt; 100; i++){
      cout &lt;&lt; i &lt;&lt; endl;
      sleep(1);            // 休眠1秒,模拟耗时操作
    }

    // 线程退出
    pthread_exit(NULL);      // 显式退出线程,参数NULL表示不返回任何值
    // 或者直接: return NULL;// 等效的退出方式
}


int main(int argc, char *argv[])
{
    // 获取当前进程ID(注意:主线程也在同一个进程中)
    pid_t tid = getpid();// 获取当前进程ID
    cout &lt;&lt; "main thread" &lt;&lt; " pid=" &lt;&lt; tid &lt;&lt; endl;// 打印主线程所在进程的ID
    pthread_t id = pthread_self();
    cout &lt;&lt; "main Self:" &lt;&lt; " id=" &lt;&lt; id &lt;&lt; endl;

    pthread_t thread;    // 线程句柄/标识符(用于引用创建的线程)
    pthread_attr_t attr; //线程属性对象
    int result;            // 存储函数返回码(return code)
    long param = 1;          // 线程参数,这里作为线程ID使用(值为1)

    result = pthread_attr_init(&amp;attr); //初始化属性
    if (result != 0) {
      cerr &lt;&lt; "Error: pthread_attr_init failed: " &lt;&lt; strerror(result) &lt;&lt; endl;
      return 0;
    }

    //设置分离状态 PTHREAD_CREATE_JOINABLE 或 PTHREAD_CREATE_DETACHED
    result = pthread_attr_setdetachstate(&amp;attr, PTHREAD_CREATE_JOINABLE);
    if (result == 0) {
      cout &lt;&lt; "设置线程为可连接状态(JOINABLE)" &lt;&lt; endl;
    }

    // 设置堆栈大小(256KB)
    size_t stacksize = 256 * 1024;// 256KB
    result = pthread_attr_setstacksize(&amp;attr, stacksize);
    if (result == 0) {
      size_t actual_stacksize;
      pthread_attr_getstacksize(&amp;attr, &amp;actual_stacksize);
      cout &lt;&lt; "设置堆栈大小: " &lt;&lt; actual_stacksize &lt;&lt; " bytes" &lt;&lt; endl;
    }

    // 设置调度策略普通应用用 SCHED_OTHER分时调度
    result = pthread_attr_setschedpolicy(&amp;attr, SCHED_OTHER);
    if (result == 0) {
      cout &lt;&lt; "设置调度策略: SCHED_OTHER" &lt;&lt; endl;
    }

    // 设置继承调度属性(使用显式设置而非继承)
    result = pthread_attr_setinheritsched(&amp;attr, PTHREAD_EXPLICIT_SCHED);
    if (result == 0) {
      cout &lt;&lt; "设置显式调度继承" &lt;&lt; endl;
    }

    //设置竞争范围(Linux只支持系统级)Linux只支持 PTHREAD_SCOPE_SYSTEM
    result = pthread_attr_setscope(&amp;attr, PTHREAD_SCOPE_SYSTEM);
    if (result == 0) {
      cout &lt;&lt; "设置竞争范围: PTHREAD_SCOPE_SYSTEM" &lt;&lt; endl;
    }

    // 创建新线程
    // &amp;thread: 用于存储新线程的标识符
    // NULL: 线程属性(使用默认属性)
    // PrintThread: 线程函数指针(新线程执行的函数)
    // (void *)param: 传递给线程函数的参数(将long转换为void*)
    result = pthread_create(&amp;thread, &amp;attr, PrintThread, (void *)param);

    // 检查线程创建是否成功
    if (result)// rc != 0 表示创建失败
    {
      // pthread_create返回错误码(非零)
      // 通常应该处理错误,这里直接返回
      return 0;
    }

    result = pthread_attr_destroy(&amp;attr);

    // 主线程继续执行自己的工作(与子线程并发执行)
    for(int i = 0; i &lt; 5; i++){
      cout &lt;&lt; "Main thread " &lt;&lt; i &lt;&lt; endl;// 主线程输出
      sleep(2);// 休眠2秒(子线程休眠1秒,所以子线程输出更频繁)
    }

    pthread_join(thread, NULL);

    return 0;
}
                                    
</code></pre>
<ul>
<li>out</li>
</ul>
<pre><code class="language-C++">main thread pid=6255
main Self: id=139668265477952
设置线程为可连接状态(JOINABLE)
设置堆栈大小: 262144 bytes
设置调度策略: SCHED_OTHER
设置显式调度继承
设置竞争范围: PTHREAD_SCOPE_SYSTEM
Main thread 0
ChildThread: pid=6255
ChildSelf: id=139668265473792

</code></pre>
<h4 id="调用普通函数">调用普通函数</h4>
<p>pthread_create接收的是函数指针,传入普通函数会报错参数类型错误,必须接收</p>
<blockquote>
<p>void * ( * )(void * )</p>
</blockquote>
<p>采用外面线程包装函数</p>
<blockquote>
<pre><code>error: invalid conversion from ‘void (*)()’ to ‘void* (*)(void*)’ [-fpermissive]
rc = pthread_create(&amp;thread, &amp;attr, &amp;PrintThread_1, NULL);

</code></pre>
</blockquote>
<pre><code class="language-C++">void PrintThread_1()
{
    for(int i = 0; i &lt; 100; i++){
      cout &lt;&lt; i &lt;&lt; endl;
      sleep(1);         
    }

        return;
}

void* thread_wrapper(void* arg) {
    (void)arg;// 忽略参数(防止编译器警告)
   
    // 调用真正的无参数函数
    PrintThread_1();
   
    return NULL;// 线程返回值
}

int result = pthread_create(&amp;thread, &amp;attr, thread_wrapper, NULL);
</code></pre>
<h4 id="调用静态函数">调用静态函数</h4>
<pre><code class="language-c++">static void* PrintThread_2(void* arg) {
    (void)arg;
   
    for(int i = 0; i &lt; 100; i++){
      cout &lt;&lt; i &lt;&lt; endl;
      sleep(1);         
    }
    return NULL;
}

int result = pthread_create(&amp;thread, &amp;attr, PrintThread_2, (void*)param);
</code></pre>
<h4 id="调用-类静态函数">调用 类静态函数</h4>
<pre><code class="language-C++">#include &lt;pthread.h&gt;
#include &lt;iostream&gt;
#include &lt;unistd.h&gt;
using namespace std;

class MyClass {
public:
    // 静态成员函数 - 可以作为线程函数
    static void* PrintThread_3(void* arg) {
      cout &lt;&lt; "Static member function called" &lt;&lt; endl;
      
         for(int i = 0; i &lt; 10; i++){
            cout &lt;&lt; "fun: " &lt;&lt; i &lt;&lt; endl;
            sleep(1);         
              }

      cout &lt;&lt; "Static member function finished" &lt;&lt; endl;
      return nullptr;
    }

};

int main() {
    pthread_t thread;
    int param = 42;
   
    // 直接调用类的静态成员函数
    //pthread_create(&amp;thread, NULL, &amp;MyClass::PrintThread_3,&amp;param);
    // 通过类名调用静态函数
    pthread_create(&amp;thread, NULL, MyClass::PrintThread_3, &amp;param);
    for(int i = 0; i &lt; 10; i++){
            cout &lt;&lt; "main:" &lt;&lt; i &lt;&lt; endl;
            sleep(1);         
              }
    pthread_join(thread, nullptr);
    cout &lt;&lt; "Main: Thread completed" &lt;&lt; endl;
    return 0;
}
</code></pre>
<h3 id="c11-stdthread-实现">C++11 std::thread 实现</h3>
<ul>
<li>std::thread主要接口</li>
</ul>
<pre><code class="language-C++">thread() noexcept;// 创建不表示线程的空线程对象

template&lt; class Function, class... Args &gt;
explicit thread( Function&amp;&amp; f, Args&amp;&amp;... args );// 创建新线程并执行函数

thread( const thread&amp; ) = delete;// 不可复制构造
thread( thread&amp;&amp; other ) noexcept;// 移动构造

join()                //阻塞当前线程,直到目标线程执行完毕

detach()        //分离线程,允许线程独立执行;分离后线程对象不再管理该线程

joinable()        //检查线程是否可合并
//返回 true 的情况:
//1.通过构造函数创建且未调用 join/detach
//2.已移动但未管理的线程对象

get_id()        //返回线程的唯一标识符,如果线程不可合并,返回默认构造的 id

hardware_concurrency()        //返回硬件支持的并发线程数,用于指导线程池大小设置

void swap(thread&amp; _Other) noexcept//交换两个 std::thread 对象的底层句柄
</code></pre>
<p>-实例</p>
<pre><code class="language-C++">#include &lt;stdio.h&gt;      // 标准输入输出头文件
#include &lt;stdlib.h&gt;       // 标准库头文件(包含exit等函数)
#include &lt;iostream&gt;       // C++标准输入输出流
#include &lt;cstring&gt;
#include &lt;thread&gt;
#include &lt;chrono&gt;

using namespace std;      // 使用std命名空间

// 线程函数 - 子线程的入口点
void PrintThread(int param)
{
    thread::id thread_id = this_thread::get_id();
    cout &lt;&lt; "ChildThread:" &lt;&lt; " id="&lt;&lt; thread_id&lt;&lt; endl;// 这里打印的是进程ID
    for (int i = 0; i &lt; 100; i++) {
      cout &lt;&lt; i &lt;&lt; endl;
      this_thread::sleep_for(chrono::seconds(1));            // 休眠1秒,模拟耗时操作
    }

}
//静态函数
static void staticPrintThread(int param){
        return PrintThread(param);
}


int main(int argc, char* argv[])
{
    thread::id main_id = this_thread::get_id();// 获取当前进程ID
    cout &lt;&lt; "main Self:" &lt;&lt; " id=" &lt;&lt; main_id &lt;&lt; endl;
        int temp = 50;
    // 创建新线程
    thread t(PrintThread,temp);
    //thread t(staticPrintThread,temp);
    //引用传递
    //thread t(PrintThread,ref(temp);

    // 主线程继续执行自己的工作(与子线程并发执行)
    for (int i = 0; i &lt; 5; i++) {
      cout &lt;&lt; "Main thread " &lt;&lt; i &lt;&lt; endl;// 主线程输出
      this_thread::sleep_for(chrono::seconds(2));// 休眠2秒(子线程休眠1秒,所以子线程输出更频繁)
    }

    if (t.joinable()) {
      t.join();
    }

    return 0;
}

</code></pre>
<h5 id="lambda表达式">Lambda表达式</h5>
<pre><code>int temp = 5;
thread t((int count){
for (int i = temp; i &lt; count; i++) {
      cout &lt;&lt; i &lt;&lt; endl;
      this_thread::sleep_for(chrono::seconds(1));            // 休眠1秒,模拟耗时操作
    }
},30);
</code></pre>
<h5 id="调用类普通方法类静态方法">调用类普通方法、类静态方法</h5>
<pre><code class="language-C++">#include &lt;iostream&gt;
#include &lt;thread&gt;
#include &lt;chrono&gt;
#include &lt;functional&gt;

using namespace std;
class ProgramA{
public:
      void PrintThread(){
                thread::id thread_id = this_thread::get_id();
                cout &lt;&lt;"thread_id: "&lt;&lt;thread_id&lt;&lt;endl;
                for(int i = 0;i&lt;50;i++){
                        cout&lt;&lt;"fun" &lt;&lt; i &lt;&lt;endl;
                        this_thread::sleep_for(chrono::seconds(1));
                }
                return;
      };

      static void staticPrintThread(){
                thread::id thread_id = this_thread::get_id();
                cout &lt;&lt;"thread_id: "&lt;&lt;thread_id&lt;&lt;endl;
                for(int i = 0;i&lt;50;i++){
                        cout&lt;&lt;"static" &lt;&lt; i &lt;&lt;endl;
                        this_thread::sleep_for(chrono::seconds(1));
                }
                return ;
      }


};


int main(){
      thread t1(ProgramA::staticPrintThread);

      ProgramA PA;

      thread t2(&amp;ProgramA::PrintThread,&amp;PA);

      thread t3(bind(&amp;ProgramA::PrintThread,&amp;PA));

      thread t4(&amp;ProgramA::PrintThread,&amp;PA);
      t1.join();
      t2.join();
      t3.join();
      t4.join();

}

</code></pre>
<h4 id="promisefutureasync">promise、future、async</h4>
<p><strong>问题:</strong>如果有一个使用场景计算A需要5min,计算B需要4分钟,执行C动作需要完成A和B。不使用多线程时需要9min</p>
<p>才能执行C,使用多线程时只需要5min就可以完成前面步骤。</p>
<p><img src="https://img2024.cnblogs.com/blog/2659401/202601/2659401-20260110013513652-1929552682.png" alt="62fb9dc7321d61f3e9a03610d59ea168" loading="lazy"></p>
<p>但是如何保证AB完成后才让C执行。</p>
<ul>
<li>
<p>在执行C之前让A、B join();</p>
</li>
<li>
<p>使用promise、future、async</p>
</li>
</ul>
<p>promise-future 对是通过共享状态,来帮助线程间传递值或异常的一种沟通通道。是实现线程同步的一种方式。</p>
<p><code>std::promise</code>:<strong>数据提供者</strong>;用于存储一个值或异常,之后可以通过与之关联的<code>std::future</code>来获取这个值或异常。</p>
<p><code>std::future</code>: <strong>数据接收者</strong>;提供一个异步操作结果的访问。它可以等待(阻塞)直到<code>std::promise</code>设置好值,然后获取该值。</p>
<p><code>std::async</code> :用于异步执行任务,并返回一个 <code>std::future</code> 对象来获取结果</p>
<hr>
<ul>
<li>promise、future示例</li>
</ul>
<blockquote>
<p><strong>promise</strong></p>
<p>get_future(): 返回一个与该promise关联的future对象。每个 promise 只能调用一次 <code>get_future()</code>,多次调用会抛出 <code>std::future_error</code> 异常。<br>
set_value(value): 设置异步操作的结果值。如果多次调用会抛出 <code>std::future_error</code> 异常。<br>
set_exception(exception_ptr): 设置异步操作的异常。<br>
set_value_at_thread_exit(value): 设置异步操作的结果值,但该值会在当前线程退出时才变得可用。<br>
set_exception_at_thread_exit(exception_ptr): 设置异步操作的异常,但该异常会在当前线程退出时才变得可用。</p>
</blockquote>
<blockquote>
<p><strong>future</strong></p>
<p>get(): 阻塞当前线程,直到异步操作完成并返回结果。<code>get()</code> 只能调用一次,第二次调用会抛出 <code>std::future_error</code> 异常。<br>
wait(): 阻塞当前线程,直到异步操作完成,但不获取结果。<br>
wait_for(duration): 阻塞当前线程,直到异步操作完成或指定的时间已过。<br>
wait_until(time_point): 阻塞当前线程,直到异步操作完成或到达指定的时间点。</p>
</blockquote>
<hr>
<p>设置返回值</p>
<pre><code class="language-C++">1 #include &lt;iostream&gt;
2 #include &lt;thread&gt;
3 #include &lt;chrono&gt;
4 #include &lt;functional&gt;
5 #include &lt;future&gt;
6
7 using namespace std;
8 void computeA(promise&lt;int&gt; &amp;&amp;prom){
9         this_thread::sleep_for(chrono::seconds(5));//5s
10         cout&lt;&lt; "A执行完成!5s" &lt;&lt;endl;
11         prom.set_value(1);//设置结果值
12 }
13
14
15 void computeB(promise&lt;float&gt; &amp;&amp;prom){
16         this_thread::sleep_for(chrono::seconds(4));//4s
17         cout&lt;&lt; "B执行完成!4s" &lt;&lt;endl;
18         prom.set_value(1);//设置结果值
19 }
20
21
22 void computeC(future&lt;int&gt; &amp;&amp;futi,future&lt;float&gt; &amp;&amp;futf){
23         cout&lt;&lt; "C开始!" &lt;&lt;endl;
24         futi.get();
25         futf.get();
26         cout&lt;&lt; "C接受A B结果后执行!" &lt;&lt;endl;
27 }
28
29 int main(){
30
31         promise&lt;int&gt; prom_i;
32         future&lt;int&gt; resultFutI = prom_i.get_future();
33
34         promise&lt;float&gt; prom_f;
35         future&lt;float&gt; resultFutF= prom_f.get_future();
36
37
38         thread threadA = thread(computeA , move(prom_i));
39
40         thread threadB = thread(computeB , move(prom_f));
41
42         thread threadC = thread(computeC , move(resultFutI) , move(resultFutF));
43
44
45         threadA.join();
46
47         threadB.join();
48
49         threadC.join();
50 }
</code></pre>
<hr>
<p>设置异常</p>
<pre><code class="language-C++">1 #include &lt;iostream&gt;
2 #include &lt;thread&gt;
3 #include &lt;chrono&gt;
4 #include &lt;functional&gt;
5 #include &lt;future&gt;
6 using namespace std;
7 void funAThrowException(std::promise&lt;int&gt;&amp;&amp; prom) {
8   try {
9         throw std::runtime_error("An error occurred");
10   } catch (...) {
11         prom.set_exception(std::current_exception());
12   }
13 }
14
15 void funBReceiveException(std::future&lt;int&gt;&amp;&amp; fut) {
16   try {
17         int value = fut.get();
18   } catch (const std::exception&amp; e) {
19         std::cout &lt;&lt; "Caught exception: " &lt;&lt; e.what() &lt;&lt; std::endl;
20   }
21 }
22
23
24 int main(){
25         cout&lt;&lt;"main fun! "&lt;&lt;endl;
26         promise&lt;int&gt; prom;
27         future&lt;int&gt; fut = prom.get_future();
28         thread threadA = thread(funAThrowException,move(prom));
29         thread threadB = thread(funBReceiveException,move(fut));
30         threadA.join();
31         threadB.join();
32         return 0;
33
34 }

</code></pre>
<hr>
<h5 id="shared_future">shared_future</h5>
<ul>
<li>若需要使用一个线程的结果,让多个线程获取呢?</li>
</ul>
<p>可以使用shared_future</p>
<blockquote>
<p><strong>可多次调用 <code>get()</code></strong>:与 <code>std::future</code> 不同,<code>shared_future</code> 的 <code>get()</code> 可多次调用</p>
<p><strong>线程安全</strong>:多个线程可同时调用 <code>get()</code>,但返回引用类型时要小心数据竞争</p>
<p><strong>异常传播</strong>:异常会被存储,每次 <code>get()</code> 都会重新抛出</p>
<p><strong>生命周期</strong>:共享状态由所有副本共同管理,最后一个副本销毁时释放资源</p>
<p><strong>复制廉价</strong>:复制操作只增加引用计数,适合传递到多个线程</p>
<p><strong>值语义优先</strong>:尽量返回值类型而非引用类型,避免悬挂引用</p>
<p><strong>检查有效性</strong>:使用前检查 <code>valid()</code>,避免操作空的 <code>shared_future</code></p>
<p><strong>内存模型</strong>:<code>get()</code> 提供 <code>memory_order_acquire</code> 语义,确保结果可见性</p>
</blockquote>
<pre><code class="language-C++">1 #include &lt;iostream&gt;
2 #include &lt;thread&gt;
3 #include &lt;chrono&gt;
4 #include &lt;functional&gt;
5 #include &lt;future&gt;
6
7 using namespace std;
8 void computeA(promise&lt;int&gt; &amp;&amp;prom){
9         cout&lt;&lt; "进入A!" &lt;&lt;endl;
10         this_thread::sleep_for(chrono::seconds(5));//5s
11         cout&lt;&lt; "A完成!5s" &lt;&lt;endl;
12         prom.set_value(1);//设置结果值
13 }
14
15
16 void computeB(shared_future&lt;int&gt; shared_fut){
17         cout&lt;&lt; "进入B!"&lt;&lt; endl;
18         shared_fut.get();       //等待A完成
19         this_thread::sleep_for(chrono::seconds(4));//4s
20         cout&lt;&lt; "B完成!4s" &lt;&lt;endl;
21
22 }
23
24
25 void computeC(shared_future&lt;int&gt; shared_fut){
26         cout&lt;&lt; "进入C!" &lt;&lt;endl;
27         shared_fut.get();
28         cout&lt;&lt; "C完成!" &lt;&lt;endl;
29 }
30
31 int main(){
32
33         promise&lt;int&gt; prom_i;
34         future&lt;int&gt; resultFutI = prom_i.get_future();
35         shared_future&lt;int&gt; shared_fut = resultFutI.share();//两步获取shared_future
36                 // 直接从 promise 获取 shared_future
37         //share_future&lt;int&gt; share_fut = prom_i.get_future().share();
38
39         thread threadA = thread(computeA , move(prom_i));
40
41         thread threadB = thread(computeB , shared_fut);
42
43         thread threadC = thread(computeC , shared_fut);
44
45
46         threadA.join();
47
48         threadB.join();
49
50         threadC.join();
51 }

</code></pre>
<hr>
<ul>
<li>aysnc</li>
</ul>
<pre><code class="language-C++">1 #include &lt;iostream&gt;
2 #include &lt;thread&gt;
3 #include &lt;chrono&gt;
4 #include &lt;functional&gt;
5 #include &lt;future&gt;
6
7 using namespace std;
8 intcomputeA(){
9         cout&lt;&lt; "进入A!" &lt;&lt;endl;
10         this_thread::sleep_for(chrono::seconds(5));//5s
11         cout&lt;&lt; "A完成!5s" &lt;&lt;endl;
12         return 100;
13 }
14
15
16 void computeB(shared_future&lt;int&gt; shared_fut){
17         cout&lt;&lt; "进入B!"&lt;&lt; endl;
18         int result = shared_fut.get();//等待A完成
19         this_thread::sleep_for(chrono::seconds(4));//4s
20         cout&lt;&lt; "B完成!4s result = "&lt;&lt; result &lt;&lt;endl;
21
22 }
23
24 int main(){
25         cout&lt;&lt;"main fun! "&lt;&lt;endl;
26         future&lt;int&gt; fut = async(launch::async,computeA);
27         shared_future&lt;int&gt; shared_fut = fut.share();
28         thread threadA = thread(computeB,shared_fut);
29         for(int i = 0;i&lt;50;i++){
30               cout&lt;&lt;"main:i = "&lt;&lt; i &lt;&lt;endl;
31         }
32         threadA.join();
33         return 0;
34
35 }

</code></pre>
<h2 id="线程同步">线程同步</h2>
<p>线程同步是多线程编程中协调线程执行顺序的机制,通过控制多个线程对共享资源的访问顺序,防止数据竞争与不可预知的数据损坏。其核心在于保证同一时刻仅有一个线程操作关键数据段。</p>
<p>为什么要线程同步</p>
<p>解决竞争条件和数据不一致性。线程同步的本质就是保证数据操作原子性。</p>
<p>线程同步的方法:</p>
<p>互斥锁、读写锁、条件变量、原子变量、线程局部存储。</p>
<h4 id="互斥锁-mutex和原子变量-atomic">互斥锁 mutex和原子变量 atomic</h4>
<p>mutex:提供基本的锁定和解锁功能;</p>
<p>recursive_mutex:递归互斥锁,允许同一个线程多次锁定同一个互斥锁,避免自死锁。</p>
<p>timed_mutex:带超时功能的互斥锁,可以尝试锁定一段时间,避免永久阻塞。</p>
<p>recursive_timed_mutex:结合递归和超时功能的互斥锁.</p>
<ul>
<li>
<p>锁管理器 (RAII机制)</p>
<ul>
<li>lock_guard:轻量级,自动释放,构造时加锁</li>
<li>unique_lock:支持延时锁定,手动锁定/解锁,所有权转移
<ul>
<li>延迟锁定:<code> std::unique_lock&lt;std::mutex&gt; lock(mtx, std::defer_lock);</code></li>
</ul>
</li>
<li>scoped_lock - 多锁管理 C++ 17</li>
</ul>
<pre><code class="language-C++">        std::mutex mtx1, mtx2, mtx3;
    // 同时锁定多个互斥锁,避免死锁
    std::scoped_lock lock(mtx1, mtx2, mtx3);
</code></pre>
</li>
</ul>
<hr>
<p>atomic内存序</p>
<p>内存序(Memory Order)是因为编译器和 CPU 为了性能,会进行<strong>指令重排</strong>(Instruction Reordering)。</p>
<p>memory_order_relaxed</p>
<blockquote>
<p><strong><code>relaxed</code></strong>读/写无同步,仅保证操作原子化(常用于计数器)。</p>
<p><strong><code>consume</code></strong>读比 acquire 更轻,只同步有数据依赖的变量(不推荐初学者使用)。</p>
<p><strong><code>acquire</code></strong>读配合 release,防止读操作被重排到后面。</p>
<p><strong><code>release</code></strong>写配合 acquire,防止写操作被重排到前面。</p>
<p><strong><code>acq_rel</code></strong>读-改-写同时具有 acquire 和 release 的特性。</p>
<p><strong><code>seq_cst</code></strong>读/写最严格,所有线程看到完全一致的顺序。默认。</p>
</blockquote>
<p>实例:</p>
<pre><code class="language-C++">1 #include &lt;iostream&gt;
2 #include &lt;thread&gt;
3 #include &lt;chrono&gt;
4 #include &lt;functional&gt;
5 #include &lt;future&gt;
6 #include &lt;mutex&gt;
7 #include &lt;atomic&gt;
8
9 using namespace std;
10 //atomic&lt;int&gt; shared_data(0); atomic适用于基本类型
11 int shared_data = 0;
12 mutex g_mutex;
13
14 //测试不准确
15 //不加锁          0ms结果错误
16 //atomic          3ms
17 //mutex 加锁      12ms
18 //lock_guard      13ms
19 //unique_lock   15ms
20
21 void addValue() {
22         for(int i = 0;i&lt;100000;++i){
23 //            mutex加锁
24 //            g_mutex.lock();
25 //            ++shared_data;
26 //            g_mutex.unlock();
27
28               unique_lock&lt;mutex&gt; lock(g_mutex,defer_lock);
29               lock.lock();
30               ++shared_data;
31               lock.unlock();
32
33         }
34 }
35
36
37 int main(){
38         auto startTime = chrono::high_resolution_clock::now();
39         cout&lt;&lt;"main fun! "&lt;&lt;endl;      
40         thread threadA = thread(addValue);
41         thread threadB = thread(addValue);
42         threadA.join();
43         threadB.join();
44         auto stopTime = chrono::high_resolution_clock::now();
45         auto duration = chrono::duration_cast&lt;chrono::milliseconds&gt;(stopTime-startTime).count();
46         cout&lt;&lt;"sharedData: "&lt;&lt;shared_data&lt;&lt;" 时间:"&lt;&lt;duration&lt;&lt; "ms"&lt;&lt;endl;
47         return 0;
48
49 }
</code></pre>
<h4 id="读写锁">读写锁</h4>
<p>允许多个读线程同时访问共享资源,但只允许一个写线程独占访问</p>
<ul>
<li><strong>读锁(共享锁)</strong>:多个线程可以同时持有读锁</li>
<li><strong>写锁(独占锁)</strong>:同一时间只能有一个线程持有写锁,且持有写锁时不能有读锁</li>
</ul>
<pre><code class="language-C++">// 读写锁的状态转换
// 无锁状态 -&gt; 可以加读锁或写锁
// 有读锁时 -&gt; 可以再加读锁,不能加写锁
// 有写锁时 -&gt; 不能加读锁,也不能加写锁
// 写者优先(Writer-preference) 或防止写饥饿(Write starvation prevention) 的策略。
</code></pre>
<pre><code class="language-C++">1 #include &lt;iostream&gt;
2 #include &lt;thread&gt;
3 #include &lt;shared_mutex&gt;
4 #include &lt;vector&gt;
5 #include &lt;chrono&gt;
6
7 class ThreadCounter {
8 private:
9   mutable std::shared_mutex mutex_;
10   int value_ = 0;
11
12 public:
13   // 读取操作:使用共享锁
14   int read(int i) const {
15         std::cout&lt;&lt;"读线程调用!"&lt;&lt;std::endl;
16         std::shared_lock&lt;std::shared_mutex&gt; lock(mutex_);// 共享锁
17         std::cout &lt;&lt; " (线程ID: " &lt;&lt; std::this_thread::get_id() &lt;&lt; ") 读操作 顺序号:"&lt;&lt; i &lt;&lt; std::end    l;
18         std::this_thread::sleep_for(std::chrono::milliseconds(1000));
19         return value_;
20   }
21
22   // 写入操作:使用独占锁
23   void write(int i) {
24         std::cout&lt;&lt; "写线程调用!"&lt;&lt;std::endl;
25         std::unique_lock&lt;std::shared_mutex&gt; lock(mutex_);// 独占锁
26         std::cout &lt;&lt; " (线程ID: " &lt;&lt; std::this_thread::get_id() &lt;&lt; ") 写操作 顺序号:" &lt;&lt; i &lt;&lt; std::en    dl;
27         std::this_thread::sleep_for(std::chrono::milliseconds(5000));
28         ++value_;
29   }
30
31   // 写入操作:重置值
32   void reset() {
33         std::unique_lock&lt;std::shared_mutex&gt; lock(mutex_);// 独占锁
34         std::cout &lt;&lt; " (线程ID: " &lt;&lt; std::this_thread::get_id() &lt;&lt; ") 重置操作" &lt;&lt; std::endl;
35         std::this_thread::sleep_for(std::chrono::milliseconds(10));
36         value_ = 0;
37   }
38 };
39
40 int main() {
41   std::cout &lt;&lt; "=== 基本读写锁示例 ===" &lt;&lt; std::endl;
42
43   ThreadCounter counter;
44   std::vector&lt;std::thread&gt; threads;
45
46   // 启动多个读线程
47   for (int i = 0; i &lt; 5; ++i) {
48         threads.emplace_back([&amp;counter, i]() {
49             for (int j = 0; j &lt; 3; ++j) {
50               counter.read(i);
51             }
52         });
53   }
54
55   // 启动写线程
56   threads.emplace_back([&amp;counter]() {
57         for (int i = 0; i &lt; 2; ++i) {
58             counter.write(i);
59         }
60   });
61
62   for (auto&amp; t : threads) {
63         t.join();
64   }
65   return 0;
66 }

</code></pre>
<h4 id="条件变量-condition_variable">条件变量 condition_variable</h4>
<p>条件变量实现多个线程间的同步操作,当条件不满足时,相关线程被一直阻塞,直到某种条件出现,这些线程才会被唤醒</p>
<blockquote>
<p>典型流程</p>
<p>mutex条件变量运行状态切换时的同步</p>
<p>condition_variable 等待/唤醒</p>
<p>共享数据</p>
<p>条件</p>
</blockquote>
<pre><code class="language-C++">1 #include &lt;iostream&gt;
2 #include &lt;queue&gt;
3 #include &lt;thread&gt;
4 #include &lt;mutex&gt;
5 #include &lt;condition_variable&gt;
6 #include &lt;vector&gt;
7
8 class ProducerConsumer {
9 private:
10   std::queue&lt;int&gt; queue;          // 共享资源:缓冲区队列
11   std::mutex mtx;               // 互斥锁,保护队列
12   std::condition_variable cv_prod; // 条件变量:控制生产者(当队列满时等待)
13   std::condition_variable cv_cons; // 条件变量:控制消费者(当队列空时等待)
14   size_t capacity;                // 缓冲区最大容量
15
16 public:
17   explicit ProducerConsumer(size_t capacity) : capacity(capacity) {}
18
19   // 生产者调用的入队函数
20   void prod(int value) {
21         // 获取锁:保护共享资源 queue
22         std::unique_lock&lt;std::mutex&gt; lock(mtx);
23
24         // 等待判断:如果队列满了,生产者阻塞并释放锁,直到消费者消费后唤醒
25         // 使用 lambda 表达式防止虚假唤醒
26         //wait()的谓词返回true时继续等待,返回false时才退出等待
27         cv_prod.wait(lock, () { return queue.size() &lt; capacity; });
28
29         // 执行生产
30         queue.push(value);
31         std::cout &lt;&lt; "Produced: " &lt;&lt; value &lt;&lt; " | Queue size: " &lt;&lt; queue.size() &lt;&lt; std::endl;
32
33         // 唤醒:告诉正在等待的消费者,现在有货了
34         cv_cons.notify_one();
35
36         // 作用域结束,lock 自动析构并释放锁
37   }
38
39   // 消费者调用的出队函数
40   int cons() {
41         std::unique_lock&lt;std::mutex&gt; lock(mtx);
42
43         // 等待判断:如果队列空了,消费者阻塞并释放锁,直到生产者生产后唤醒
44         cv_cons.wait(lock, () { return !queue.empty(); });
45
46         // 执行消费
47         int value = queue.front();
48         queue.pop();
49         std::cout &lt;&lt; "Consumed: " &lt;&lt; value &lt;&lt; " | Queue size: " &lt;&lt; queue.size() &lt;&lt; std::endl;
50
51         // 通知:告诉正在等待的生产者,现在有空位了
52         cv_prod.notify_one();
53
54         return value;
55   }
56 };
57
58 // --- 测试代码 ---
59 void producer_task(ProducerConsumer&amp; q, int id) {
60   for (int i = 0; i &lt; 5; ++i) {
61         q.prod(id * 100 + i); // 生产数据
62         std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟生产耗时
63   }
64 }
65
66 void consumer_task(ProducerConsumer&amp; q) {
67   for (int i = 0; i &lt; 10; ++i) {
68         q.cons(); // 消费数据
69         std::this_thread::sleep_for(std::chrono::milliseconds(150)); // 模拟消费耗时
70   }
71 }
72
73 int main() {
74   ProducerConsumer q(5); // 缓冲区容量为 3
75
76   // 开启 2 个生产者线程和 1 个消费者线程
77   std::thread p1(producer_task, std::ref(q), 1);
78   std::thread p2(producer_task, std::ref(q), 2);
79   std::thread c1(consumer_task, std::ref(q));
80
81   p1.join();
82   p2.join();
83   c1.join();
84
85   return 0;
86 }

</code></pre>
<h5 id="wait-notify之间做了什么">wait-notify之间做了什么</h5>
<p><strong>释放锁并进入等待(原子性阶段)</strong></p>
<p>当你调用 <code>cv.wait(lock)</code> 时,底层会立即执行以下操作:</p>
<ul>
<li><strong>释放锁</strong>:自动释放当前线程持有的 <code>std::unique_lock&lt;std::mutex&gt;</code>。</li>
<li><strong>加入队列</strong>:将当前线程放入该条件变量的等待队列中。</li>
<li><strong>进入休眠</strong>:挂起当前线程,不再消耗 CPU 资源。</li>
</ul>
<blockquote>
<p><strong>核心细节</strong>:释放锁和进入等待这两个动作是<strong>原子性</strong>的。这意味着不会出现“刚释放锁,还没进入等待队列,通知就来了”的情况(即错失信号)。</p>
</blockquote>
<p><strong>被唤醒并尝试重新获取锁</strong></p>
<p>当另一个线程调用 <code>cv.notify_one()</code> 或 <code>cv.notify_all()</code> 时:</p>
<ul>
<li><strong>唤醒</strong>:操作系统将线程从等待队列中移出,状态变为“就绪”。</li>
<li><strong>重新抢锁</strong>:线程在 <code>wait</code> 内部<strong>尝试重新获取(acquire)</strong>之前释放的那个 <code>mutex</code>。</li>
<li><strong>阻塞等待锁</strong>:如果锁此时被其他线程持有(比如通知者还没释放锁),被唤醒的线程会停在 <code>wait</code> 内部,直到它抢到了锁。</li>
</ul>
<p><strong>返回阶段</strong></p>
<p>只有当<strong>成功重新持有锁</strong>后,<code>cv.wait(lock)</code> 才会结束阻塞并返回。此时,你的线程恢复了对共享资源的独占访问权限。</p>
<h4 id="信号丢失虚假唤醒">信号丢失&amp;虚假唤醒</h4>
<p>信号丢失:A发送信号唤醒B,A已经发送信号,但是B还没进入等待,就会倒是B收不到A的信号,这个信号就丢失了。</p>
<p>虚假唤醒:感官上是程序中没有调用notify,唤醒某些处于阻塞的线程。</p>
<ul>
<li>如何解决</li>
</ul>
<p>在调用wait前检查条件,生产者只有在队列满的情况下阻塞;消费者在队列空的情况下阻塞;</p>
<p>使用if检查条件可以避免信号丢失。使用while检查变量可以解决信号丢失和虚假唤醒。</p>
<hr>
<p>为什么 <code>if</code> 可以防止信号丢失?</p>
<p><strong>信号丢失(Lost Wake-up)</strong> 发生在:生产者发出了“队列已满”的信号,但消费者此时并没有在等待,或者生产者在消费者还没来得及进入 <code>wait</code> 状态时就发送notify。</p>
<ul>
<li><strong>检查条件的必要性:</strong> 在调用 <code>wait()</code> <strong>之前</strong> 检查条件(无论是 <code>if</code> 还是 <code>while</code>),本质上是为了确认当前是否真的需要阻塞。</li>
<li><strong>逻辑:</strong> 消费者进入临界区后,先看一眼队列。如果队列不为空,它直接拿走数据,根本不调用 <code>wait()</code>。这样即使生产者之前发过信号,消费者也已经处理了数据,不会因为错过信号而死锁。</li>
</ul>
<hr>
<p>为什么 <code>while</code> 是金标准?</p>
<p>使用 <code>while</code> 循环检查条件被称为 <strong>"Mesa-style monitoring"</strong>。它的逻辑是:<strong>被唤醒后,必须再次检查条件。</strong></p>
<p>使用while检查状态等效于 cv.wait(unique_lock(mutex),pred)</p>
<pre><code class="language-c++">// 伪代码:cv.wait(lock, pred) 的等效实现
while (!pred()) {
    wait(lock);
}
</code></pre>
<hr>
<h4 id="线程局部存储">线程局部存储</h4>
<p>thread_local 每一个线程都是独立的副本变量,线程销毁时临时变量销毁。</p>
<pre><code class="language-C++">truct ThreadContext {
    int thread_id;
    std::string name;
    std::vector&lt;int&gt; local_data;
   
    ThreadContext() : thread_id(0) {
      std::cout &lt;&lt; "构造线程局部结构体" &lt;&lt; std::endl;
    }
   
    ~ThreadContext() {
      std::cout &lt;&lt; "析构线程局部结构体,线程ID: " &lt;&lt; thread_id &lt;&lt; std::endl;
    }
};

// C++11 thread_local
thread_local ThreadContext ctx;
</code></pre>
<h3 id="如何调试">如何调试</h3>
<p>gdb命令</p>
<pre><code class="language-bash">## 编译生成 加-g
g++ -g test.cpp test -pthread

## 帮助
help /h
## 启动调试
gdb test

## 查看代码
list

## 运行
run /r运行到第一个断点
start运行到第一行执行程序
## 打断点
break / b 行号/函数名

## 查看所有断点
info b
info breakpoints

## 执行
next / n 下一步 不进函数 逐过程
step / s 下一步 进函数   逐语句
continute /c 跳转下一个断点
finish 结束当前函数
info 查看函数局部变量的值
## 退出
quit /q

## 输出
print / p 变量
p m_vector
p m_map
p *(m_vector._M_impl._start_)@m_vector.size()
display 追踪具体变量值
undisplay 取消追踪
watch 设置观察点 变量修改时打印显示

# x 查看内存
## 查看所有进程
info thread

## 跳转进程
thread i

## 打印调用独占
bt
## 打印所有线程的调用堆栈
thread apply all bt

## 生成日志文件,开启日志模式
set logging on # 日志功能开启

## 观察点 watchpoint
watch

set scheduler-locking on#锁定调度。设置后,当你 next 时,只有当前线程运行,其他线程暂停。防止你在调试 A 线程时,B 线程也在跑,导致输出混乱。
</code></pre>
<pre><code class="language-bash"># 查找线程id
ps -ef | grep hello
gdb hello -p pid
</code></pre>
<ul>
<li>set scheduler-locking on/step/off</li>
</ul>
<table>
<thead>
<tr>
<th style="text-align: left">模式</th>
<th style="text-align: left">命令</th>
<th style="text-align: left">行为</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left"><strong>off</strong> (默认)</td>
<td style="text-align: left"><code>set scheduler-locking off</code></td>
<td style="text-align: left">所有线程自由运行,GDB可能在任何线程停止时切换</td>
</tr>
<tr>
<td style="text-align: left"><strong>step</strong></td>
<td style="text-align: left"><code>set scheduler-locking step</code></td>
<td style="text-align: left">单步执行时锁定当前线程,其他情况不锁定</td>
</tr>
<tr>
<td style="text-align: left"><strong>on</strong></td>
<td style="text-align: left"><code>set scheduler-locking on</code></td>
<td style="text-align: left">只运行当前线程,其他线程被冻结</td>
</tr>
</tbody>
</table><br><br>
来源:https://www.cnblogs.com/hjk-airl/p/19459097
頁: [1]
查看完整版本: 【C++】多线程