1.1 课程介绍

为什么要用多线程

  • 任务分解
    耗时的操作,任务分解,实时响应
  • 数据分解
    充分利用多核CPU处理数据
  • 数据流分解
    读写分离,解耦合设计

第一个示例

  • 子线程与主线程线程id不同
  • 在子线程调用this_thread::sleep_for睡眠等待 ```cpp

include

include

// linux下 -lpthread

using namespace std;

void ThreadMain() { cout << “begin thread main” << endl; for (int i = 0; i < 10; i++) { cout << “ in thread “ << i << endl; this_thread::sleep_for(10ms); // 10s 有些版本不支持 // this_thread::sleep_for(chrono::seconds(1)); } }

int main(int argc, char* argv[]) { thread th(ThreadMain); cout << “子线程和主线程的线程id是不一样的: “ << endl; cout << “ main thread id: “ << this_thread::get_id() << endl; cout << “ sub thread id: “ << th.get_id() << endl << endl;

  1. cout << "begin wait sub thread " << endl;
  2. // 阻塞等待子线程退出
  3. cout << "每10ms执行一次:" << endl;
  4. th.join();
  5. cout << "end wait sub thread " << endl;
  6. return 0;

}

/ 子线程和主线程的线程id是不一样的: main thread id: 10476 sub thread id: 21996 begin wait sub thread 每10ms执行一次: begin thread main in thread 0 in thread 1 in thread 2 in thread 3 in thread 4 in thread 5 in thread 6 in thread 7 in thread 8 in thread 9 end wait sub thread /


- `#include <thread>` 包括标准库中对多线程支持的声明,管理线程的函数和类在`<thread>`中声明(保护共享数据的函数和类在其他头文件中声明)
- 因为每个线程都必须一个执行单元,新线程的执行从这里开始。对于应用程序来说,初始线程是`main()`,但是对于其他线程,可以在`std::thread`对象的构造函数中指定——本例中命名为th的`std::thread`对象拥有新函数`ThreadMain()`作为其执行函数。

- 与直接写入标准输出或是从`main()`调用`ThreadMain()`不同,该程序启动了一个全新的线程来实现,将线程数量一分为二--初始线程始于`main()`,而新线程始于`ThreadMain()`。
- 新的线程启动之后,初始线程继续执行。如果它不等待新线程结束,就运行到main()函数结束——有可能发生在新线程运行之前。这就是为什么调用`join()`的原因,这会让创建线程等待`std::thread`对象创建的线程。
<a name="PUXKq"></a>
# 1.2 thread 对象生命周期和线程等待和分离
```cpp
#include <iostream>
#include <thread>

using namespace std;

bool is_exit = false;

void ThreadMain()
{
    cout << "begin thread main" << endl;

    for (int i = 0; i < 10; i++) {
        cout << "  in thread " << i << endl;
        if (is_exit) {
            break;
        }
        this_thread::sleep_for(1000ms);
    }
    cout << "end thread main" << endl;
}

int main()
{
    // 情景一
    {
        // thread th(ThreadMain);  // 出错,thread对象被销毁可子线程还在执行
    }

    // 情景二
    {
        // thread th(ThreadMain);
        // th.detach();  // 子线程与主线程分离,守护线程
        // 坑, 主线程退出后, 子线程不一定会退出.
    }

    // 情景三
    {
        // thread th(ThreadMain);
        // th.join();  // 主线程堵塞,等待子线程退出0
    }

    // 情景四
    {
        // 改进,添加全局变量 is_exit
        thread th(ThreadMain);

        this_thread::sleep_for(800ms); // 模拟业务逻辑

        is_exit = true; // 通知子线程退出
        th.join();
        cout << "子线程已经退出..." << endl;
    }
    cout << "main quit..." << endl;
    return 0;
}
  • 情景一,thread对象被销毁时,子线程依旧在执行

image.png

  • 情景二,子线程与主线程分离,守护线程。
    上面的红框:主线程退出了,可子线程开始执行了也就是并没退出
    下面的红框:看不出来子线程有没有执行结束

image.png

  • 情景三,主线程会堵塞,一直等待子线程退出

image.png

  • 情景四,通过设置全局变量通知子线程退出
    子线程循环里的sleep的时间为100ms,主线程中sleep的时间为800ms。运行后,子线程的for循环i=8的时候主线程就结束了,然后子线程也结束了。

image.png

1.3 C++11线程创建的多种方式

1.3.1 全局函数作为线程入口

如何传递参数

  • C++11 内部如何实现参数传递
  • C++11 thread源码分析 ```cpp template , thread>, int> = 0> explicit thread(_Fn&& _Fx, _Args&&… _Ax) {
      using _Tuple                 = tuple<decay_t<_Fn>, decay_t<_Args>...>;
      auto _Decay_copied           = _STD make_unique<_Tuple>(_STD forward<_Fn>(_Fx), _STD forward<_Args>(_Ax)...);
      constexpr auto _Invoker_proc = _Get_invoke<_Tuple>(make_index_sequence<1 + sizeof...(_Args)>{});
    

pragma warning(push)

pragma warning(disable : 5039) // pointer or reference to potentially throwing function passed to

                            // extern C function under -EHc. Undefined behavior may occur
                            // if this function throws an exception. (/Wall)
    _Thr._Hnd =
        reinterpret_cast<void*>(_CSTD _beginthreadex(nullptr, 0, _Invoker_proc, _Decay_copied.get(), 0, &_Thr._Id));

pragma warning(pop)

    if (_Thr._Hnd) { // ownership transferred to the thread
        (void) _Decay_copied.release();
    } else { // failed to start thread
        _Thr._Id = 0;
        _Throw_Cpp_error(_RESOURCE_UNAVAILABLE_TRY_AGAIN);
    }
}
基于模板函数,可以让任意函数都可以传递,不会限制类型。所有参数都拷贝到列表中,然后扔到线程句柄中。
```cpp
#include <thread>
#include <iostream>
#include <string>

using namespace std;

void ThreadMain(int p1, float p2, string str)
{
    this_thread::sleep_for(100ms);
    cout << "ThreadMain " << p1 << " " << p2 << " " << str << endl;
}

int main()
{
    thread th;
    {
        float f1 = 12.1f;
        // 所有的参数做赋值
        th = thread(ThreadMain, 101, f1, "test string para");
    }
    th.join();
    return 0;
}
// 输出: ThreadMain 101 12.1 test string para

如果将类传递给线程:

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

using namespace std;

class Para {
public:
    Para()
    {
        cout << "default Para" << endl;
    }

    Para(const Para& p)
    {
        cout << "Copy Para" << endl;
    }

    ~Para()
    {
        cout << "Drop Para" << endl;
    }
    string name;
};

void ThreadMain(Para para)
{
    this_thread::sleep_for(100ms);
    cout << "ThreadMain " << para.name << endl;
}

int main()
{
    thread th;
    {
        Para p;
        p.name = "test para class";

        // 所有的参数做赋值
        th = thread(ThreadMain, p);
    }
    th.join();
    return 0;
}

运行可见,类的析构函数被调用三次:
image.png

  • main函数调用一次
  • thread函数拷贝时调用一次(通过拷贝构造函数查看)
  • thread里的回调函数又调用了一次

使用拷贝构造函数时,可直观看到默认构造函数调用一次,拷贝构造函数调用两次,析构函数调用三次:
image.png

参数传递的一些坑

传递空间已经销毁 多线程共享访问一块空间 传递的指针变量的生命周期小于线程

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

using namespace std;

class Para {
public:
    Para()
    {
        cout << "Create Para" << endl;
    }

    Para(const Para& p)
    {
        cout << "Copy Para" << endl;
    }

    ~Para()
    {
        cout << "Drop Para" << endl;
    }
    string name;
};

void ThreadMainRef(Para& para)
{
    this_thread::sleep_for(10ms);
    cout << "ThreadMain para->name: " << para.name << endl;
}

void ThreadMainPtr(Para* para)
{
    this_thread::sleep_for(10ms);
    cout << "ThreadMain para->name: " << para->name << endl;
}

int main()
{
    // 指针
    // 情景一: 使用 join
    {
        Para p;
        p.name = "test para class";
        this_thread::sleep_for(5ms);
        thread th(ThreadMainPtr, &p);
        th.join();
    }

    // 情景二: 使用 detach
    {
        //        Para p;
        //        p.name = "test para class";
        //        thread th(ThreadMainPtr, &p);
        //        th.detach();
    }

    // 引用
    // 情景三
    {
        //        Para p;
        //        p.name = "test para class";
        //        thread th(ThreadMainRef, ref(p));
        //        th.join();
    }
    return 0;
}
  • 情景一:

image.png

  • 情景二 线程访问的p空间会提前释放

image.png

  • 情景三 引用 注意传递给线程的参数加上 ref(),告诉线程这个是引用

image.png

1.3.2 成员函数作为线程入口

接口调用和参数传递

#include <iostream>
#include <thread>

using namespace std;

class MyThread {
public:
    // 入口线程函数
    void Main()
    {
        cout << "MyThread Main " << name << ": " << age << endl;
    }

    string name = "";
    int age = 100;
};
int main()
{
    MyThread myTh;
    myTh.name = "Test name 001";
    myTh.age = 20;
    // 参数一: 成员函数的地址; 参数二: 类对象的地址
    thread th(&MyThread::Main, &myTh);
    th.join();
}

// 输出:MyThread Main Test name 001: 20

线程基类封装

#include <iostream>
#include <thread>

using namespace std;

class XThread {
public:
    virtual void Start()
    {
        is_exit_ = false;
        th_ = std::thread(&XThread::Main, this);
    }

    virtual void Stop()
    {
        is_exit_ = true;
        Wait();
    }

    virtual void Wait()
    {
        if (th_.joinable()) {
            th_.join();
        }
    }

    bool is_exit()
    {
        return is_exit_;
    }

private:
    virtual void Main() = 0;

    bool is_exit_; // 退出条件, start时设为true

    std::thread th_;
};

class TestXThread : public XThread {
public:
    void Main() override
    {
        cout << "MyThread Main begin" << endl;
        while (!is_exit()) {
            this_thread::sleep_for(100ms);
            cout << "." << flush;
        }
        cout << endl
             << "MyThread Main end" << endl;
    }
    string name;
};

int main()
{
    TestXThread testTh;
    testTh.name = "Test name 001";
    testTh.Start();
    this_thread::sleep_for(3000ms);
    testTh.Stop();
}

1.4 lambad临时函数作为线程入口函数

lambda函数,其基本格式如下 [捕捉列表] ( 参数 ) mutable -> 返回值类型 { 函数体 }

lambda线程示例

thread t( [](){
    cout << "hello world from lambda thread.";    
} )

普通全局函数的lambda表达式

#include <iostream>
#include <thread>

using namespace std;

int main()
{
    thread th(
        [](int i) {
            cout << "test lmbda " << i << endl;
        },
        123);
    th.join();
}

类成员的lambda表达式

#include <iostream>
#include <thread>

using namespace std;

class TestLambda {
public:
    void Start()
    {
        thread th([this /* = */]() { // 注意 this传导
            cout << "name = " << name << endl;
        });
        th.join();
    }

    string name = "test lambad";
};

int main()
{
    TestLambda test;
    test.Start();
}