天天看点

C++11线程使用总结

C++11线程使用总结

std::thread 在 <thread> 头文件中声明,因此使用 std::thread 需包含 <thread> 头文件。

<thread> 头文件摘要

<thread> 头文件声明了 std::thread 线程类及 std::swap (交换两个线程对象)辅助函数。另外命名空间 std::this_thread 也声明在 <thread> 头文件中。下面是 C++11 标准所定义的 <thread> 头文件摘要:

参见 N3242=11-0012 草案第 30.3 节 Threads(p1133)。

<b>[cpp]</b> view plain copy

amespace std {

    #define __STDCPP_THREADS__ __cplusplus

    class thread;

    void swap(thread&amp; x, thread&amp; y);

    namespace this_thread {

        thread::id get_id();

        void yield();

        template &lt;class Clock, class Duration&gt;

        void sleep_until(const chrono::time_point&lt;Clock, Duration&gt;&amp; abs_time);

        template &lt;class Rep, class Period&gt;

        void sleep_for(const chrono::duration&lt;Rep, Period&gt;&amp; rel_time);

    }

}

&lt;thread&gt; 头文件主要声明了 std::thread 类,另外在 std::this_thread 命名空间中声明了get_id,yield,sleep_until 以及 sleep_for 等辅助函数,本章稍微会详细介绍 std::thread 类及相关函数。

std::thread 类摘要

std::thread 代表了一个线程对象,C++11 标准声明如下:

namespace std {

    class thread {

        public:

            // 类型声明:

            class id;

            typedef implementation-defined native_handle_type;

            // 构造函数、拷贝构造函数和析构函数声明:

            thread() noexcept;

            template &lt;class F, class ...Args&gt; explicit thread(F&amp;&amp; f, Args&amp;&amp;... args);

            ~thread();

            thread(const thread&amp;) = delete;

            thread(thread&amp;&amp;) noexcept;

            thread&amp; operator=(const thread&amp;) = delete;

            thread&amp; operator=(thread&amp;&amp;) noexcept;

            // 成员函数声明:

            void swap(thread&amp;) noexcept;

            bool joinable() const noexcept;

            void join();

            void detach();

            id get_id() const noexcept;

            native_handle_type native_handle();

            // 静态成员函数声明:

            static unsigned hardware_concurrency() noexcept;

    };

std::thread 中主要声明三类函数:(1). 构造函数、拷贝构造函数及析构函数;(2). 成员函数;(3). 静态成员函数。另外,std::thread::id 表示线程 ID,同时 C++11 声明如下:

    class thread::id {

            id() noexcept;

    bool operator==(thread::id x, thread::id y) noexcept;

    bool operator!=(thread::id x, thread::id y) noexcept;

    bool operator&lt;(thread::id x, thread::id y) noexcept;

    bool operator&lt;=(thread::id x, thread::id y) noexcept;

    bool operator&gt;(thread::id x, thread::id y) noexcept;

    bool operator&gt;=(thread::id x, thread::id y) noexcept;

    template&lt;class charT, class traits&gt;

    basic_ostream&lt;charT, traits&gt;&amp;

        operator&lt;&lt; (basic_ostream&lt;charT, traits&gt;&amp; out, thread::id id);

    // Hash 支持

    template &lt;class T&gt; struct hash;

    template &lt;&gt; struct hash&lt;thread::id&gt;;

std::thread 详解

std::thread 构造和赋值

std::thread 构造函数

默认构造函数 (1) thread() noexcept;

初始化构造函数 (2) template &lt;class Fn, class... Args&gt;

explicit thread(Fn&amp;&amp; fn, Args&amp;&amp;... args);

拷贝构造函数 [deleted] (3) thread(const thread&amp;) = delete;

Move 构造函数 (4) thread(thread&amp;&amp; x) noexcept;

默认构造函数(1),创建一个空的 std::thread 执行对象。

初始化构造函数(2),创建一个 std::thread 对象,该 std::thread 对象可被 joinable,新产生的线程会调用 fn 函数,该函数的参数由 args 给出。

拷贝构造函数(被禁用)(3),意味着 std::thread 对象不可拷贝构造。

Move 构造函数(4),move 构造函数(move 语义是 C++11 新出现的概念,详见附录),调用成功之后 x 不代表任何std::thread 执行对象。

线程状态:

在一个线程的生存期内,可以在多种状态之间转换,不同的操作系统可以实现不同的线程模型,定义许多不同的线程状态,每个状态还可以包含多个子状态,但大体来说,如下几种状态是通用的:

1)就绪:参与调度,等待被执行,一旦被调度选中,立即开始执行

2)运行:占用CPU,正在运行中

3)休眠:暂不参与调度,等待特定事件发生

4)中止:已经运行完毕,等待回收线程资源

线程环境:

线程存在于进程之中,进程内所有全局资源对于内部每个线程都是可见的。

进程内典型全局资源如下:

1)代码区:这意味着当前进程空间内所有的可见的函数代码,对于每个线程来说,也是可见的

2)静态存储区:全局变量,静态空间

3)动态存储区:堆空间

线程内典型的局部资源:

1)本地栈空间:存放本线程的函数调用栈,函数内部的局部变量等

2)部分寄存器变量:线程下一步要执行代码的指针偏移量

C++中的thread对象通常来说表达了执行的线程(thread of execution)。我在使用多线程的时候,发现很多情况下都是用join()函数,但是在使用detach的时候效果明显就是不一样了。

当thread::detach()函数被调用后,执行的线程从线程对象中被分离,该线程被从主线程分离出去放置到后台执行。已不再被一个线程对象所表达--这是两个独立的事情。C++线程对象可以被销毁,同时OS执行的线程可以继续。如果程序想要知道执行的线程何时结束,就需要一些其它的机制。join()函数在那个thread对象上不能再被调用,因为它已经不再和一个执行的线程相关联。没有thread对象指向该线程而失去了对它的控制,当对象析构时线程会继续在后台执行,但是当主程序退出时并不能保证线程能执行完。如果没有良好的控制机制或者这种后台线程比较重要,最好不用detach而应该使用join。

去销毁一个仍然可以“joinable”的C++线程对象会被认为是一种错误。为了销毁一个C++线程对象,约么join()函数需要被调用(并结束),要么detach()函数被调用。如果一个C++线程对象当销毁时仍然可以被join,异常会被抛出。

mutex:

mutex是用来保证线程同步的,防止不同的线程同时操作同一个共享数据。

示例代码:

int cnt= 20;

mutex m;

void t1()

{

    while (cnt &gt; 0)

    {

        m.lock();

        if (cnt &gt; 0)

        {

            --cnt;

            cout &lt;&lt; cnt &lt;&lt; endl;

        }

        m.unlock();

void t2()

int main()

    thread th1(t1);

    thread th2(t2);

    th1.join();

    th2.join();

    return 0;

运行结果,cnt是依次递减的,没有因为多线程而打乱次序:

lock_guard:

使用lock_guard则相对安全,它是基于作用域的,能够自解锁,当该对象创建时,它会像m.lock()一样获得互斥锁,当生命周期结束时,它会自动析构(unlock),不会因为某个线程异常退出而影响其他线程。

int cnt = 20;

        lock_guard&lt;mutex&gt; lockGuard(m);

get_id:

获取线程 ID,返回一个类型为 std::thread::id 的对象

示例如下:

#include &lt;iostream&gt;

#include &lt;thread&gt;

#include &lt;chrono&gt;

void foo()

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

    std::thread t1(foo);

    std::thread::id t1_id = t1.get_id();

    std::thread t2(foo);

    std::thread::id t2_id = t2.get_id();

    std::cout &lt;&lt; "t1‘s id: " &lt;&lt; t1_id &lt;&lt; ‘\n‘;

    std::cout &lt;&lt; "t2‘s id: " &lt;&lt; t2_id &lt;&lt; ‘\n‘;

    t1.join();

    t2.join();

sleep_until: 

线程休眠至某个指定的时刻(time point),该线程才被重新唤醒。

template&lt; class Clock, class Duration &gt;

void sleep_until( const std::chrono::time_point&lt;Clock,Duration&gt;&amp; sleep_time );

sleep_for: 

线程休眠某个指定的时间片(time span),该线程才被重新唤醒,不过由于线程调度等原因,实际休眠时间可能比sleep_duration 所表示的时间片更长。

template&lt; class Rep, class Period &gt;

void sleep_for( const std::chrono::duration&lt;Rep,Period&gt;&amp; sleep_duration );

    std::cout &lt;&lt; "waiter" &lt;&lt; std::endl;

    std::chrono::milliseconds dura( 1000 );

    std::this_thread::sleep_for( dura );

    std::cout &lt;&lt; "Waited 1000 ms\n";

继续阅读