本文整理自:https://www.cnblogs.com/lidabo/p/7852033.html
1. C++中的並發與多線程
C++標准並沒有提供對多進程並發的原生支持,所以C++的多進程並發要靠其他API——這需要依賴相關平台。
C++11 標准提供了一個新的線程庫,內容包括了管理線程、保護共享數據、線程間的同步操作、低級原子操作等各種類。標准極大地提高了程序的可移植性,以前的多線程依賴於具體的平台,而現在有了統一的接口進行實現。
C++11 新標准中引入了幾個頭文件來支持多線程編程:
- < thread > :包含std::thread類以及std::this_thread命名空間。管理線程的函數和類在 中聲明.
- < atomic > :包含std::atomic和std::atomic_flag類,以及一套C風格的原子類型和與C兼容的原子操作的函數。
- < mutex > :包含了與互斥量相關的類以及其他類型和函數
- < future > :包含兩個Provider類(std::promise和std::package_task)和兩個Future類(std::future和std::shared_future)以及相關的類型和函數。
- < condition_variable > :包含與條件變量相關的類,包括std::condition_variable和std::condition_variable_any。
知識鏈接:
C++11 並發之std::atomic
本文概要:
1、成員類型和成員函數。
2、std::thread 構造函數。
3、異步。
4、多線程傳遞參數。
5、join、detach。
6、獲取CPU核心個數。
7、CPP原子變量與線程安全。
8、lambda與多線程。
9、時間等待相關問題。
10、線程功能拓展。
11、多線程可變參數。
12、線程交換。
13、線程移動。
std::thread 在 #include<thread> 頭文件中聲明,因此使用 std::thread 時需要包含 #include<thread> 頭文件。
1、成員類型和成員函數。
成員類型:
- id
- Thread id (public member type ) id
- native_handle_type
- Native handle type (public member type )
成員函數:
- (constructor)
- Construct thread (public member function ) 構造函數
- (destructor)
- Thread destructor (public member function ) 析構函數
- operator=
- Move-assign thread (public member function ) 賦值重載
- get_id
- Get thread id (public member function ) 獲取線程id
- joinable
- Check if joinable (public member function ) 判斷線程是否可以加入等待
- join
- Join thread (public member function ) 加入等待
- detach
- Detach thread (public member function ) 分離線程
- swap
- Swap threads (public member function ) 線程交換
- native_handle
- Get native handle (public member function ) 獲取線程句柄
- hardware_concurrency [static]
- Detect hardware concurrency (public static member function ) 檢測硬件並發特性
Non-member overloads:
- swap (thread)
- Swap threads (function )
2、std::thread 構造函數。
如下表:
- default (1)
- thread() noexcept;
- initialization(2)
- template <class Fn, class... Args> explicit thread (Fn&& fn, Args&&... args);
- copy [deleted] (3)
- thread (const thread&) = delete;
- move [4]
- hread (thread&& x) noexcept;
(1).默認構造函數,創建一個空的 thread 執行對象。
(2).初始化構造函數,創建一個 thread 對象,該 thread 對象可被 joinable,新產生的線程會調用 fn 函數,該函數的參數由 args 給出。
(3).拷貝構造函數(被禁用),意味着 thread 不可被拷貝構造。
(4).move 構造函數,move 構造函數,調用成功之后 x 不代表任何 thread 執行對象。
注意:可被 joinable 的 thread 對象必須在他們銷毀之前被主線程 join 或者將其設置為 detached。
std::thread 各種構造函數例子如下:
#include<iostream> #include<thread> #include<chrono> using namespace std; void fun1(int n) //初始化構造函數 { cout << "Thread " << n << " executing\n"; n += 10; this_thread::sleep_for(chrono::milliseconds(10)); } void fun2(int & n) //拷貝構造函數 { cout << "Thread " << n << " executing\n"; n += 20; this_thread::sleep_for(chrono::milliseconds(10)); } int main() { int n = 0; thread t1; //t1不是一個thread thread t2(fun1, n + 1); //按照值傳遞 t2.join(); cout << "n=" << n << '\n'; n = 10; thread t3(fun2, ref(n)); //引用 thread t4(move(t3)); //t4執行t3,t3不是thread t4.join(); cout << "n=" << n << '\n'; return 0; }
輸出結果:
Thread 1 executing
n=0
Thread 10 executing
n=30
3、異步。
例如:
1 #include<iostream> 2 #include<thread> 3 using namespace std; 4 void show() 5 { 6 cout << "hello cplusplus!" << endl; 7 } 8 int main() 9 { 10 //棧上 11 thread t1(show); //根據函數初始化執行 12 thread t2(show); 13 thread t3(show); 14 //線程數組 15 thread th[3]{thread(show), thread(show), thread(show)}; 16 //堆上 17 thread *pt1(new thread(show)); 18 thread *pt2(new thread(show)); 19 thread *pt3(new thread(show)); 20 //線程指針數組 21 thread *pth(new thread[3]{thread(show), thread(show), thread(show)}); 22 return 0; 23 }
4、多線程傳遞參數。
例如:
#include<iostream> #include<thread> using namespace std; void show(const char *str, const int id) { cout << "線程 " << id + 1 << " :" << str << endl; } int main() { thread t1(show, "hello cplusplus!", 0); thread t2(show, "你好,C++!", 1); thread t3(show, "hello!", 2); return 0; }
發現,線程 t1、t2、t3 都執行成功!
5、join、detach。
join例子如下:
1 #include<iostream> 2 #include<thread> 3 #include<array> 4 using namespace std; 5 void show() 6 { 7 cout << "hello cplusplus!" << endl; 8 } 9 int main() 10 { 11 array<thread, 3> threads = { thread(show), thread(show), thread(show) }; 12 for (int i = 0; i < 3; i++) 13 { 14 cout << threads[i].joinable() << endl;//判斷線程是否可以join 15 threads[i].join();//主線程等待當前線程執行完成再退出 16 } 17 return 0; 18 }
輸出結果:
hello cplusplus!
hello cplusplus!
hello cplusplus!
1
1
1
總結:
join 是讓當前主線程等待所有的子線程執行完,才能退出。
detach例子如下:
1 #include<iostream> 2 #include<thread> 3 using namespace std; 4 void show() 5 { 6 cout << "hello cplusplus!" << endl; 7 } 8 int main() 9 { 10 thread th(show); 11 //th.join(); 12 th.detach();//脫離主線程的綁定,主線程掛了,子線程不報錯,子線程執行完自動退出。 13 //detach以后,子線程會成為孤兒線程,線程之間將無法通信。 14 cout << th.joinable() << endl; 15 return 0; 16 }
輸出結果:
hello cplusplus!0
結論:
線程 detach 脫離主線程的綁定,主線程掛了,子線程不報錯,子線程執行完自動退出。
線程 detach以后,子線程會成為孤兒線程,線程之間將無法通信。
6、獲取CPU核心個數。
例如:
#include<iostream> #include<thread> using namespace std; int main() { auto n = thread::hardware_concurrency();//獲取cpu核心個數 cout << n << endl; return 0; }
結論:
通過 thread::hardware_concurrency() 獲取 CPU 核心的個數。
7、CPP原子變量與線程安全。
問題例如:
#include<iostream> #include<thread> using namespace std; const int N = 100000000; int num = 0; void run() { for (int i = 0; i < N; i++) { num++; } } int main() { clock_t start = clock(); thread t1(run); thread t2(run); t1.join(); t2.join(); clock_t end = clock(); cout << "num=" << num << ",用時 " << end - start << " ms" << endl; return 0; }
從上述代碼執行的結果,發現結果並不是我們預計的200000000,這是由於線程之間發生沖突,從而導致結果不正確。
為了解決此問題,有以下方法:
(1)互斥量。
例如:
#include<iostream> #include<thread> #include<mutex> using namespace std; const int N = 100000000; int num(0); mutex m; void run() { for (int i = 0; i < N; i++) { m.lock(); num++; m.unlock(); } } int main() { clock_t start = clock(); thread t1(run); thread t2(run); t1.join(); t2.join(); clock_t end = clock(); cout << "num=" << num << ",用時 " << end - start << " ms" << endl; return 0; }
不難發現,通過互斥量后運算結果正確,但是計算速度很慢,原因主要是互斥量加解鎖需要時間。
互斥量詳細內容 請參考
C++11 並發之std::mutex。
(2)原子變量。
例如:
#include<iostream> #include<thread> #include<atomic> using namespace std; const int N = 100000000; atomic_int num{ 0 };//不會發生線程沖突,線程安全 void run() { for (int i = 0; i < N; i++) { num++; } } int main() { clock_t start = clock(); thread t1(run); thread t2(run); t1.join(); t2.join(); clock_t end = clock(); cout << "num=" << num << ",用時 " << end - start << " ms" << endl; return 0; }
不難發現,通過原子變量后運算結果正確,計算速度一般。
原子變量詳細內容 請參考C++11 並發之std::atomic。
(3)加入 join 。
例如:
#include<iostream> #include<thread> using namespace std; const int N = 100000000; int num = 0; void run() { for (int i = 0; i < N; i++) { num++; } } int main() { clock_t start = clock(); thread t1(run); t1.join(); thread t2(run); t2.join(); clock_t end = clock(); cout << "num=" << num << ",用時 " << end - start << " ms" << endl; return 0; }
輸出結果:
num=200000000,用時 431 ms
不難發現,通過原子變量后運算結果正確,計算速度也很理想。
8、lambda與多線程。
例如:
#include<iostream> #include<thread> using namespace std; int main() { auto fun = [](const char *str) {cout << str << endl; }; thread t1(fun, "hello world!"); thread t2(fun, "hello beijing!"); return 0; }
9、時間等待相關問題。
例如:
#include<iostream> #include<thread> #include<chrono> using namespace std; int main() { thread th1([]() { //讓線程等待3秒 this_thread::sleep_for(chrono::seconds(3)); //讓cpu執行其他空閑的線程 this_thread::yield(); //線程id cout << this_thread::get_id() << endl; }); return 0; }
10、線程功能拓展。
例如:
#include<iostream> #include<thread> using namespace std; class MyThread :public thread //繼承thread { public: //子類MyThread()繼承thread()的構造函數 MyThread() : thread() { } //MyThread()初始化構造函數 template<typename T, typename...Args> MyThread(T&&func, Args&&...args) : thread(forward<T>(func), forward<Args>(args)...) { } void showcmd(const char *str) //運行system { system(str); } }; int main() { MyThread th1([]() { cout << "hello" << endl; }); th1.showcmd("calc"); //運行calc //lambda MyThread th2([](const char * str) { cout << "hello" << str << endl; }, " this is MyThread"); th2.showcmd("notepad");//運行notepad return 0; }
11、多線程可變參數。
例如:
#include<iostream> #include<thread> #include<cstdarg> using namespace std; int show(const char *fun, ...) { va_list ap;//指針 va_start(ap, fun);//開始 vprintf(fun, ap);//調用 va_end(ap); return 0; } int main() { thread t1(show, "%s %d %c %f", "hello world!", 100, 'A', 3.14159); return 0; }
12、線程交換。
例如:
#include<iostream> #include<thread> using namespace std; int main() { thread t1([]() { cout << "thread1" << endl; }); thread t2([]() { cout << "thread2" << endl; }); cout << "thread1' id is " << t1.get_id() << endl; cout << "thread2' id is " << t2.get_id() << endl; cout << "swap after:" << endl; swap(t1, t2);//線程交換 cout << "thread1' id is " << t1.get_id() << endl; cout << "thread2' id is " << t2.get_id() << endl; return 0; }
兩個線程通過 swap 進行交換。
13、線程移動。
例如:
#include<iostream> #include<thread> using namespace std; int main() { thread t1([]() { cout << "thread1" << endl; }); cout << "thread1' id is " << t1.get_id() << endl; thread t2 = move(t1);; cout << "thread2' id is " << t2.get_id() << endl; return 0; }