[原]C++新标准之std::thread

概览

从C++11开始提供了线程的支持,终于可以方便的编写跨平台的线程代码了。除了std::thread类,还提供了许多其它便利同步的机制,本篇总结是C++11学习笔记系列的首篇总结。

std::thread

std::thread定义在<thread>中,提供了方便的创建线程的功能。

类定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class thread
{
public:
thread() noexcept;
thread( thread&& other ) noexcept;
template< class Function, class... Args >
explicit thread( Function&& f, Args&&... args );
thread(const thread&) = delete;
~thread();
thread& operator=( thread&& other ) noexcept;
bool joinable() const noexcept;
std::thread::id get_id() const noexcept;
native_handle_type native_handle();
void join();
void detach();
void swap( thread& other ) noexcept;
static unsigned int hardware_concurrency() noexcept;
};

从定义中我们可以得知:

各个成员函数的简单介绍

例子

因为thread类比较简单,我们通过几个例子来学习。

  • 支持移动语义,但不支持拷贝语义

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #include <thread>
    void some_function() {}
    void some_other_function() {}
    int main()
    {
    std::thread t1(some_function); // 构造一个thread对象t1
    std::thread t2 = std::move(t1); // 把t1 move给另外一个thread对象t2,t1不再管理之前的线程了。
    // 这句不需要std::move(),从临时变量进行移动是自动和隐式的。调用的是operator=(std::thread&&)
    t1 = std::thread(some_other_function);
    std::thread t3;
    t3 = std::move(t2); // 把t2 move给t3
    // 把t3 move给t1,非法。因为`t1`已经有了一个相关的线程,会调用`std::terminate()`来终止程序。
    t1 = std::move(t3);
    }
  • 通过调用join()成员函数来等待线程结束

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #include <iostream>
    #include <thread>
    #include <chrono>

    void foo()
    {
    std::this_thread::sleep_for(std::chrono::seconds(1));
    }

    int main()
    {
    std::cout << "starting first helper...\n";
    std::thread helper1(foo);
    helper1.join();
    }
  • 传递参数给线程函数

    1
    2
    void f(int i, const std::string& s);
    std::thread t(f, 3, "hello");

注意:参数会以默认的方式被复制到内部存储空间,直到使用的时候才会转成对应的类型。

  • 下面的例子有问题吗?有什么问题?

    1
    2
    3
    4
    5
    6
    7
    8
    void f(int i, const std::string& s);
    void oops(int some_param)
    {
    char buffer[1024];
    sprintf(buffer, "%i", some_param);
    std::thread t(f, 3, buffer);
    t.detach();
    }

    局部变量buffer的指针会被传递给新线程,并在新线程里转换成string,如果oops()buffer被转换成string之前退出,那么会导致未定义的行为(因为buffer是局部变量,oops()结束后,buffer对应的内存不能被使用了)。解决之道是在构造std::thread的时候传递string变量。std::thread t(f, 3, std::string(buffer));

  • 可以使用std::ref()来显示表明要传递引用,就像std::bind()那样。

    1
    2
    3
    std::string data;
    void test(int a, std::string& data) { data = "modified"; }
    std::thread t(test, w, std::ref(data));
  • 使用类的成员函数作为线程参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    #include <thread>
    #include <string>
    class CRunner
    {
    public:
    void run0(){}
    void run1(int a) {}
    void run2(int a, int b) const {}
    int run3(int a, char b, const std::string& c) {return 0;}
    int run4(int& a, double b, float c, char d) { ++a; return 0; }
    static void run_static(int a) {}
    };

    int main()
    {
    CRunner runner;
    int a = 0;
    // 使用std::mem_fun,需要传指针
    std::thread t0(std::mem_fun(&CRunner::run0), &runner);
    // 使用std::mem_fun_ref,可以传引用或副本
    std::thread t1(std::mem_fun_ref(&CRunner::run1), std::ref(runner), 1);
    // std::thread t1(std::mem_fun_ref(&CRunner::run1), runner, 1);

    // 使用std::mem_fn,std::mem_fn支持多于一个参数的函数,std::mem_fun不支持。
    std::thread t2(std::mem_fn(&CRunner::run2), std::ref(runner), 1, 2);
    // 使用std::bind + std::mem_fn
    std::thread t3(std::bind(std::mem_fn(&CRunner::run3), &runner, 1, 2, "data"));
    // 使用std::mem_fn,注意std::ref的用法,如果不用std::ref行不行?
    std::thread t4(std::mem_fn(&CRunner::run4), &runner, std::ref(a), 2.2, 3.3f, 'd');
    // 使用类的静态函数
    std::thread t5(&CRunner::run_static, 1);

    t0.join();
    t1.join();
    t2.join();
    t3.join();
    t4.join();
    t5.join();
    }

注意

更多

虽然在之前的例子中的函数有返回值,但是我们却不能获得,想获得返回值我们需要使用std::future,关于std::future的总结,后续会慢慢补充,敬请期待。

参考资料

BianChengNan wechat
扫描左侧二维码关注公众号,扫描右侧二维码加我个人微信:)
  • 本文作者: BianChengNan
  • 本文链接: https://bianchengnan.github.io/articles/cpp11-thread/
  • 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议。转载请注明出处!
  • 作者寄语: 文章的结束只是思考的开始,您宝贵的意见和建议将是我继续前行的动力,点击右侧分享按钮即可携友同行!
0%