UNIX(多線程):03--- 認識std::thread

2021-02-20 遊戲開發司機

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_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 <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),創建一個空的 std::thread 執行對象。

初始化構造函數(2),創建一個 std::thread 對象,該 std::thread 對象可被 joinable,新產生的線程會調用 fn 函數,該函數的參數由 args 給出。

拷貝構造函數(被禁用)(3),意味著 std::thread 對象不可拷貝構造。

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; std::thread t2(f1, n + 1); std::thread t3(f2, std::ref(n)); std::thread t4(std::move(t3)); 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;

Move 賦值操作(1),如果當前對象不可 joinable,需要傳遞一個右值引用(rhs)給 move 賦值操作;如果當前對象可被 joinable,則會調用 terminate() 報錯。

拷貝賦值操作(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(){std::this_thread::sleep_for(std::chrono::seconds(1)); }
void bar(){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 函數之後:

*this 不再代表任何的線程執行實例。

joinable() == false

get_id() == std::this_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)); }



執行結果如下:

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

執行結果如下:

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

相關焦點

  • 走進C++11(二十四)一統江湖之線程 -- std::thread
    thread的定義在如下網站:https://en.cppreference.com/w/cpp/thread/thread想使用thread要包含<thread>頭文件。此頭文件主要聲明了std::thread線程類。
  • UNIX(多線程):07---線程啟動、結束,創建線程多法、join,detach
    線程啟動、結束,創建線程多法、join,detach範例演示線程運行的開始和結束#include
  • UNIX(多線程):14---理解線程構造函數
    ::string m) {}std::thread t1(function_1);std::thread t2(function_2, 1);std::thread t3(function_3, 1, "hello");t1.join();t2.join();t3.join();實驗的時候還發現一個問題,如果將重載的函數作為線程的入口函數,會發生編譯錯誤!
  • UNIX(多線程):04---Mutex互斥量
    #include <iostream> // std::cout#include <thread> // std::thread#include <mutex> // std::mutex, std::lock_guard#include <stdexcept>
  • UNIX(多線程):10---線程unique_lock(下)
    , &obja);inMsgThread.join();outMsgThread.join();std::cout << "主線程結束" << std::endl;return 0;}unique_lock的第二個參數std::adopt_lockstd::lock_guard<std::mutex
  • UNIX(多線程):08---線程傳參詳解,detach()陷阱,成員函數做線程函數
    ;thread mythread(myprint, val, buf); mythread.join();std::cout << "主線程收尾" << std::endl;return 0;}要避免的陷阱(解釋1)
  • 線程安全之std::atomic探索
    多線程的數據訪問一直很讓程式設計師們頭大,有什麼簡便方法來實現多線程間的通呢?
  • C++ 線程局部變量thread_local
    (一)在Linux系統中使用C/C++進行多線程編程時,我們遇到最多的就是對同一變量的多線程讀寫問題,大多情況下遇到這類問題都是通過鎖機制來處理,但這對程序的性能帶來了很大的影響,當然對於那些系統原生支持原子操作的數據類型來說,我們可以使用原子操作來處理,這能對程序的性能會得到一定的提高。
  • 詳解 C++ 多線程的condition_variable
    當 std::condition_variable 對象的某個 wait 函數被調用的時候,它使用 std::unique_lock(通過 std::mutex) 來鎖住當前線程。當前線程會一直被阻塞,直到另外一個線程在相同的 std::condition_variable 對象上調用了 notification 函數來喚醒當前線程。
  • 多線程 | Rust學習筆記
    現代的 CPU基本都是多核結構,為了充分利用多核的能力,多線程都是繞不開的話題。無論是同步或是異步編程,與多線程相關的問題一直都是困難並且容易出錯的,本質上是因為多線程程序的複雜性,特別是競爭條件的錯誤,使得錯誤發生具備一定的隨機性,而隨著程序的規模越來越大,解決問題的難度也隨之越來越高。C/C++將同步互斥,以及線程通信的問題全部交給了程式設計師。
  • Python多線程—Thread和Threading
    又到周四,科普Time,今天給大家講講Python多線程中的兩個模塊,Thread和Threading。針對兩個模塊下文中會給出一些實例,給大家加深印象。 說到Python的多線程呢,先介紹一下一定繞不過去的坑,全局解釋器鎖GIL。
  • UNIX(多線程):09---線程unique_lock(上)
    互斥鎖保證了線程間的同步,但是卻將並行操作變成了串行操作,這對性能有很大的影響,所以我們要儘可能的
  • 轉載:多線程編程方法1-OpenMP框架
    OpenMP是使用多線程的接口。之所以四個線程的速度僅僅是單線程速度的3倍而不是4倍,這是因為多線程在線程的切換、合作等方面也需要花費一定的時間,所以只是到了3倍的差距,而沒有達到4倍的差距。但是Number of thread: 4永遠是在線程0之後出現,並且tid==0時的這個線程為主線程。3、之前使用的為c語言,下面改寫為c++。
  • C++實現線程池
    線程池是一種多線程處理方式,其維護著多個線程,由監督者進行可並發任務的分配執行,從而避免了處理短任務時創建、銷毀進程的代價。線程池的必要性多線程技術主要用以解決處理器單元內多個線程並發執行的問題,可以顯著減少處理器單元的閒置時間,增加處理器的並發能力。
  • C++並發與多線程__C++如何線程創建線程以及函數join()和detach()用法和區別
    主線程是從main()函數開始運行,main()函數就可以看做主線程的入口函數。1. C++如何創建一個線程。創建一個最簡單的線程只需要注意三點:第一:需要包含thread頭文件;第二:需要提供一個線程入口函數;第三:我們直接在main函數中創建thread。
  • 走進C++11(二十八) 一諾千金 std::promise
    對象 (公開成員函數)獲取結果 get_future返回與承諾的結果關聯的 future (公開成員函數)設置結果 set_value設置結果為指定值 (公開成員函數) set_value_at_thread_exit設置結果為指定值,同時僅在線程退出時分發提醒 (公開成員函數) set_exception
  • 深入淺出python多線程與多進程
    global_dict = {} defstd_thread(name):    std = Student(name)    # 把std放到全局變量global_dict中:    global_dict
  • Java多線程:帶你了解神秘的線程變量 ThreadLocal
    前言在 Java多線程中,線程變量ThreadLocal非常重要,但對於很多開發者來說,這並不容易理解,甚至覺得有點神秘今天,我將獻上一份 ThreadLocal的介紹 & 實戰攻略,}                System.out.println(name + ":" + threadLocal.get());            }        }    }測試結果線程1:線程1的threadLocal線程2:線程2的threadLocal
  • 圖文|Android 使用Thread 和多線程使用互斥鎖
    為什麼需要多線程進行開發?多線程不管是嵌入式系統RTOS,Linux,還是應用開發,中間件開發,都是必不可少的,做一個技術的時候,如果能做到舉一反三,下次使用的時候不會再遇到坑,我這次給出的例子是Android 的多線程開發。
  • C++11多線程編程(二)——互斥鎖mutex用法
    那麼為什麼要出現多線程鎖這個東西呢?一句話概括的話。計算機就是為了計算數據才誕生的,如果不能保證數據準確的話,任何技術都只是空中樓閣,多線程技術也是一樣,那麼為什麼多線程會讓數據不準確呢?大家可以看下以下的這個例子#include <iostream>#include <thread>#include <string>using namespace std;void thread_task(){ for (int i = 0; i < 10; i++) {