C++11 並發指南------std::thread 詳解


參考: https://github.com/forhappy/Cplusplus-Concurrency-In-Practice/blob/master/zh/chapter3-Thread/Introduction-to-Thread.md#stdthread-%E8%AF%A6%E8%A7%A3

 

本節將詳細介紹 std::thread 的用法。

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)。

namespace std {
    #define __STDCPP_THREADS__ __cplusplus
    class thread;
    void swap(thread& x, thread& y);
    namespace this_thread {
        thread::id get_id();
        void yield();

        template <class Clock, class Duration>
        void sleep_until(const chrono::time_point<Clock, Duration>& abs_time);

        template <class Rep, class Period>
        void sleep_for(const chrono::duration<Rep, Period>& rel_time);
    }        
}

<thread> 頭文件主要聲明了 std::thread 類,另外在 std::this_thread 命名空間中聲明了get_idyieldsleep_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 <class F, class ...Args> explicit thread(F&& f, Args&&... args);
            ~thread();
            thread(const thread&) = delete;
            thread(thread&&) noexcept;
            thread& operator=(const thread&) = delete;
            thread& operator=(thread&&) noexcept;

            // 成員函數聲明:
            void swap(thread&) 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 聲明如下:

namespace std {
    class thread::id {
        public:
            id() noexcept;
    };

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

    template<class charT, class traits>
    basic_ostream<charT, traits>&
        operator<< (basic_ostream<charT, traits>& out, thread::id id);


    // Hash 支持
    template <class T> struct hash;
    template <> struct hash<thread::id>;
}

std::thread 詳解

std::thread 構造和賦值

std::thread 構造函數

默認構造函數 (1) thread() noexcept;
初始化構造函數 (2) template <class Fn, class... Args>
explicit thread(Fn&& fn, Args&&... args);
拷貝構造函數 [deleted] (3) thread(const thread&) = delete;
Move 構造函數 (4) thread(thread&& x) noexcept;
  1. 默認構造函數(1),創建一個空的 std::thread 執行對象。
  2. 初始化構造函數(2),創建一個 std::thread 對象,該 std::thread 對象可被 joinable,新產生的線程會調用 fn 函數,該函數的參數由 args 給出。
  3. 拷貝構造函數(被禁用)(3),意味着 std::thread 對象不可拷貝構造。
  4. Move 構造函數(4),move 構造函數(move 語義是 C++11 新出現的概念,詳見附錄),調用成功之后 x 不代表任何std::thread 執行對象。

注意:可被 joinable 的 std::thread 對象必須在他們銷毀之前被主線程 join 或者將其設置為 detached.

std::thread 各種構造函數例子如下(參考):

#include <iostream>
#include <utility>
#include <thread>
#include <chrono>
#include <functional>
#include <atomic>

void f1(int n)
{
    for (int i = 0; i < 5; ++i) {
        std::cout << "Thread " << n << " executing\n";
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
}

void f2(int& n)
{
    for (int i = 0; i < 5; ++i) {
        std::cout << "Thread 2 executing\n";
        ++n;
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
}

int main()
{
    int n = 0;
    std::thread t1; // t1 is not a thread
    std::thread t2(f1, n + 1); // pass by value
    std::thread t3(f2, std::ref(n)); // pass by reference
    std::thread t4(std::move(t3)); // t4 is now running f2(). t3 is no longer a thread
    t2.join();
    t4.join();
    std::cout << "Final value of n is " << n << '\n';
}

std::thread 賦值操作

Move 賦值操作 (1) thread& operator=(thread&& rhs) noexcept;
拷貝賦值操作 [deleted] (2) thread& operator=(const thread&) = delete;
  1. Move 賦值操作(1),如果當前對象不可 joinable,需要傳遞一個右值引用(rhs)給 move 賦值操作;如果當前對象可被joinable,則會調用 terminate() 報錯。
  2. 拷貝賦值操作(2),被禁用,因此 std::thread 對象不可拷貝賦值。

請看下面的例子:

#include <stdio.h>
#include <stdlib.h>

#include <chrono>    // std::chrono::seconds
#include <iostream>  // std::cout
#include <thread>    // std::thread, std::this_thread::sleep_for

void thread_task(int n) {
    std::this_thread::sleep_for(std::chrono::seconds(n));
    std::cout << "hello thread "
        << std::this_thread::get_id()
        << " paused " << n << " seconds" << std::endl;
}

int main(int argc, const char *argv[])
{
    std::thread threads[5];
    std::cout << "Spawning 5 threads...\n";
    for (int i = 0; i < 5; i++) {
        threads[i] = std::thread(thread_task, i + 1);
    }
    std::cout << "Done spawning threads! Now wait for them to join\n";
    for (auto& t: threads) {
        t.join();
    }
    std::cout << "All threads joined.\n";

    return EXIT_SUCCESS;
}

其他成員函數

本小節例子來自 http://en.cppreference.com

  • get_id: 獲取線程 ID,返回一個類型為 std::thread::id 的對象。請看下面例子:

    #include <iostream>
    #include <thread>
    #include <chrono>
    
    void foo()
    {
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
    
    int main()
    {
        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 << "t1's id: " << t1_id << '\n';
        std::cout << "t2's id: " << t2_id << '\n';
    
        t1.join();
        t2.join();
    }
    
  • joinable: 檢查線程是否可被 join。檢查當前的線程對象是否表示了一個活動的執行線程,由默認構造函數創建的線程是不能被 join 的。另外,如果某個線程 已經執行完任務,但是沒有被 join 的話,該線程依然會被認為是一個活動的執行線程,因此也是可以被 join 的。

    #include <iostream>
    #include <thread>
    #include <chrono>
    
    void foo()
    {
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
    
    int main()
    {
        std::thread t;
        std::cout << "before starting, joinable: " << t.joinable() << '\n';
    
        t = std::thread(foo);
        std::cout << "after starting, joinable: " << t.joinable() << '\n';
    
        t.join();
    }
    
  • join: Join 線程,調用該函數會阻塞當前線程,直到由 *this 所標示的線程執行完畢 join 才返回。

    #include <iostream>
    #include <thread>
    #include <chrono>
    
    void foo()
    {
        // simulate expensive operation
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
    
    void bar()
    {
        // simulate expensive operation
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
    
    int main()
    {
        std::cout << "starting first helper...\n";
        std::thread helper1(foo);
    
        std::cout << "starting second helper...\n";
        std::thread helper2(bar);
    
        std::cout << "waiting for helpers to finish..." << std::endl;
        helper1.join();
        helper2.join();
    
        std::cout << "done!\n";
    }
    
  • detach: Detach 線程。 將當前線程對象所代表的執行實例與該線程對象分離,使得線程的執行可以單獨進行。一旦線程執行完畢,它所分配的資源將會被釋放。

調用 detach 函數之后:

  1. *this 不再代表任何的線程執行實例。
  2. joinable() == false
  3. get_id() == std::thread::id()

另外,如果出錯或者 joinable() == false,則會拋出 std::system_error.

    #include <iostream>
    #include <chrono>
    #include <thread>

    void independentThread() 
    {
        std::cout << "Starting concurrent thread.\n";
        std::this_thread::sleep_for(std::chrono::seconds(2));
        std::cout << "Exiting concurrent thread.\n";
    }

    void threadCaller() 
    {
        std::cout << "Starting thread caller.\n";
        std::thread t(independentThread);
        t.detach();
        std::this_thread::sleep_for(std::chrono::seconds(1));
        std::cout << "Exiting thread caller.\n";
    }

    int main() 
    {
        threadCaller();
        std::this_thread::sleep_for(std::chrono::seconds(5));
    }
  • swap: Swap 線程,交換兩個線程對象所代表的底層句柄(underlying handles)。

    #include <iostream>
    #include <thread>
    #include <chrono>
    
    void foo()
    {
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
    
    void bar()
    {
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
    
    int main()
    {
        std::thread t1(foo);
        std::thread t2(bar);
    
        std::cout << "thread 1 id: " << t1.get_id() << std::endl;
        std::cout << "thread 2 id: " << t2.get_id() << std::endl;
    
        std::swap(t1, t2);
    
        std::cout << "after std::swap(t1, t2):" << std::endl;
        std::cout << "thread 1 id: " << t1.get_id() << std::endl;
        std::cout << "thread 2 id: " << t2.get_id() << std::endl;
    
        t1.swap(t2);
    
        std::cout << "after t1.swap(t2):" << std::endl;
        std::cout << "thread 1 id: " << t1.get_id() << std::endl;
        std::cout << "thread 2 id: " << t2.get_id() << std::endl;
    
        t1.join();
        t2.join();
    }
    

執行結果如下:

thread 1 id: 1892
thread 2 id: 2584
after std::swap(t1, t2):
thread 1 id: 2584
thread 2 id: 1892
after t1.swap(t2):
thread 1 id: 1892
thread 2 id: 2584
  • native_handle: 返回 native handle(由於 std::thread 的實現和操作系統相關,因此該函數返回與 std::thread 具體實現相關的線程句柄,例如在符合 Posix 標准的平台下(如 Unix/Linux)是 Pthread 庫)。

    #include <thread>
    #include <iostream>
    #include <chrono>
    #include <cstring>
    #include <pthread.h>
    
    std::mutex iomutex;
    void f(int num)
    {
        std::this_thread::sleep_for(std::chrono::seconds(1));
    
       sched_param sch;
       int policy; 
       pthread_getschedparam(pthread_self(), &policy, &sch);
       std::lock_guard<std::mutex> lk(iomutex);
       std::cout << "Thread " << num << " is executing at priority "
                 << sch.sched_priority << '\n';
    }
    
    int main()
    {
        std::thread t1(f, 1), t2(f, 2);
    
        sched_param sch;
        int policy; 
        pthread_getschedparam(t1.native_handle(), &policy, &sch);
        sch.sched_priority = 20;
        if(pthread_setschedparam(t1.native_handle(), SCHED_FIFO, &sch)) {
            std::cout << "Failed to setschedparam: " << std::strerror(errno) << '\n';
        }
    
        t1.join();
        t2.join();
    }
    

執行結果如下:

Thread 2 is executing at priority 0
Thread 1 is executing at priority 20
  • hardware_concurrency [static]: 檢測硬件並發特性,返回當前平台的線程實現所支持的線程並發數目,但返回值僅僅只作為系統提示(hint)。

    #include <iostream>
    #include <thread>
    
    int main() {
        unsigned int n = std::thread::hardware_concurrency();
        std::cout << n << " concurrent threads are supported.\n";
    }
    

std::this_thread 命名空間中相關輔助函數介紹

  • get_id: 獲取線程 ID。

    #include <iostream>
    #include <thread>
    #include <chrono>
    #include <mutex>
    
    std::mutex g_display_mutex;
    
    void foo()
    {
        std::thread::id this_id = std::this_thread::get_id();
    
        g_display_mutex.lock();
        std::cout << "thread " << this_id << " sleeping...\n";
        g_display_mutex.unlock();
    
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
    
    int main()
    {
        std::thread t1(foo);
        std::thread t2(foo);
    
        t1.join();
        t2.join();
    }
    
  • yield: 當前線程放棄執行,操作系統調度另一線程繼續執行。

    #include <iostream>
    #include <chrono>
    #include <thread>
    
    // "busy sleep" while suggesting that other threads run 
    // for a small amount of time
    void little_sleep(std::chrono::microseconds us)
    {
        auto start = std::chrono::high_resolution_clock::now();
        auto end = start + us;
        do {
            std::this_thread::yield();
        } while (std::chrono::high_resolution_clock::now() < end);
    }
    
    int main()
    {
        auto start = std::chrono::high_resolution_clock::now();
    
        little_sleep(std::chrono::microseconds(100));
    
        auto elapsed = std::chrono::high_resolution_clock::now() - start;
        std::cout << "waited for "
                  << std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count()
                  << " microseconds\n";
    }
    
  • sleep_until: 線程休眠至某個指定的時刻(time point),該線程才被重新喚醒。

    template< class Clock, class Duration >
    void sleep_until( const std::chrono::time_point<Clock,Duration>& sleep_time );
    
  • sleep_for: 線程休眠某個指定的時間片(time span),該線程才被重新喚醒,不過由於線程調度等原因,實際休眠時間可能比sleep_duration 所表示的時間片更長。

    template< class Rep, class Period >
    void sleep_for( const std::chrono::duration<Rep,Period>& sleep_duration );
    
    #include <iostream>
    #include <chrono>
    #include <thread>
    
    int main()
    {
        std::cout << "Hello waiter" << std::endl;
        std::chrono::milliseconds dura( 2000 );
        std::this_thread::sleep_for( dura );
        std::cout << "Waited 2000 ms\n";
    }
    

執行結果如下:

Hello waiter
Waited 2000 ms


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM