知識鏈接:
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 各種構造函數例子如下:
- <span style="font-size:12px;">#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</span>
3、異步。
例如:
- <span style="font-size:12px;">#include<iostream>
- #include<thread>
- using namespace std;
- void show()
- {
- cout << "hello cplusplus!" << endl;
- }
- int main()
- {
- //棧上
- thread t1(show); //根據函數初始化執行
- thread t2(show);
- thread t3(show);
- //線程數組
- thread th[3]{thread(show), thread(show), thread(show)};
- //堆上
- thread *pt1(new thread(show));
- thread *pt2(new thread(show));
- thread *pt3(new thread(show));
- //線程指針數組
- thread *pth(new thread[3]{thread(show), thread(show), thread(show)});
- return 0;
- }</span>
4、多線程傳遞參數。
例如:
- <span style="font-size:12px;">#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;
- }
- 運行結果:
- 線程 1線程 2 :你好,C++!線程 3 :hello!
- :hello cplusplus!</span>
發現,線程 t1、t2、t3 都執行成功!
5、join、detach。
join例子如下:
- <span style="font-size:12px;">#include<iostream>
- #include<thread>
- #include<array>
- using namespace std;
- void show()
- {
- cout << "hello cplusplus!" << endl;
- }
- int main()
- {
- array<thread, 3> threads = { thread(show), thread(show), thread(show) };
- for (int i = 0; i < 3; i++)
- {
- cout << threads[i].joinable() << endl;//判斷線程是否可以join
- threads[i].join();//主線程等待當前線程執行完成再退出
- }
- return 0;
- }
- 運行結果:
- hello cplusplus!
- hello cplusplus!
- 1
- hello cplusplus!
- 1
- 1</span>
總結:
join 是讓當前主線程等待所有的子線程執行完,才能退出。
detach例子如下:
- <span style="font-size:12px;">#include<iostream>
- #include<thread>
- using namespace std;
- void show()
- {
- cout << "hello cplusplus!" << endl;
- }
- int main()
- {
- thread th(show);
- //th.join();
- th.detach();//脫離主線程的綁定,主線程掛了,子線程不報錯,子線程執行完自動退出。
- //detach以后,子線程會成為孤兒線程,線程之間將無法通信。
- cout << th.joinable() << endl;
- return 0;
- }
- 運行結果:
- hello cplusplus!
- 0</span>
結論:
線程 detach 脫離主線程的綁定,主線程掛了,子線程不報錯,子線程執行完自動退出。
線程 detach以后,子線程會成為孤兒線程,線程之間將無法通信。
6、獲取CPU核心個數。
例如:
- <span style="font-size:12px;">#include<iostream>
- #include<thread>
- using namespace std;
- int main()
- {
- auto n = thread::hardware_concurrency();//獲取cpu核心個數
- cout << n << endl;
- return 0;
- }
- 運行結果:
- 8</span>
結論:
通過 thread::hardware_concurrency() 獲取 CPU 核心的個數。
7、CPP原子變量與線程安全。
問題例如:
- <span style="font-size:12px;">#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;
- }
- 運行結果:
- num=143653419,用時 730 ms</span>
從上述代碼執行的結果,發現結果並不是我們預計的200000000,這是由於線程之間發生沖突,從而導致結果不正確。
為了解決此問題,有以下方法:
(1)互斥量。
例如:
- <span style="font-size:12px;">#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;
- }
- 運行結果:
- num=200000000,用時 128323 ms</span>
不難發現,通過互斥量后運算結果正確,但是計算速度很慢,原因主要是互斥量加解鎖需要時間。
互斥量詳細內容 請參考
C++11 並發之std::mutex。
(2)原子變量。
例如:
- <span style="font-size:12px;">#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;
- }
- 運行結果:
- num=200000000,用時 29732 ms</span>
不難發現,通過原子變量后運算結果正確,計算速度一般。
原子變量詳細內容 請參考C++11 並發之std::atomic。
(3)加入 join 。
例如:
- <span style="font-size:12px;">#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,用時 626 ms</span>
不難發現,通過原子變量后運算結果正確,計算速度也很理想。
8、lambda與多線程。
例如:
- <span style="font-size:12px;">#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;
- }
- 運行結果:
- hello world!
- hello beijing!</span>
9、時間等待相關問題。
例如:
- <span style="font-size:12px;">#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;
- }</span>
10、線程功能拓展。
例如:
- <span style="font-size:12px;">#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;
- }
- 運行結果:
- hello
- //運行calc
- hello this is MyThread
- //運行notepad</span>
11、多線程可變參數。
例如:
- <span style="font-size:12px;">#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;
- }
- 運行結果:
- hello world! 100 A 3.14159</span>
12、線程交換。
例如:
- <span style="font-size:12px;">#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;
- }
- 運行結果:
- thread1
- thread2
- thread1' id is 4836
- thread2' id is 4724
- swap after:
- thread1' id is 4724
- thread2' id is 4836</span>
兩個線程通過 swap 進行交換。
13、線程移動。
例如:
- <span style="font-size:12px;">#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;
- }
- 運行結果:
- thread1
- thread1' id is 5620
- thread2' id is 5620</span>
從上述代碼中,線程t2可以通過 move 移動 t1 來獲取 t1 的全部屬性,而 t1 卻銷毀了。