轉載
一. 多線程併發
1. 與 C++11 多線程相關的頭文件
C++11
語言本身支持多線程,和平臺無關;
c++11
新標準中引入了四個頭文件來支持多線程編程,他們分別是<atomic>
, <thread>
, <mutex>
, <condition_variable>
和<future>
;
<atomic>
:該頭文主要聲明瞭兩個類, ·std::atomic· 和 ·std::atomic_flag·,另外還聲明瞭一套 C 風格的原子類型和與 C 兼容的原子操作的函數。
<thread>
:該頭文件主要聲明瞭 std::thread
類,另外 std::this_thread
命名空間也在該頭文件中。
<mutex>
:該頭文件主要聲明瞭與互斥量(mutex)
相關的類,包括 std::mutex
系列類,std::lock_guard
, std::unique_lock
, 以及其他的類型和函數。
<condition_variable>
:該頭文件主要聲明瞭與條件變量相關的類,包括 std::condition_variable
和 std::condition_variable_any
。
<future>
:該頭文件主要聲明瞭 std::promise, std::package_task
兩個 Provider
類,以及 std::future
和 std::shared_future
兩個 Future
類,另外還有一些與之相關的類型和函數,std::async()
函數就聲明在此頭文件中。
- 和有些語言中定義的線程不同,C++11 所定義的線程是和操作系的線程是一一對應的,也就是說我們生成的線程都是直接接受操作系統的調度的,通過操作系統的相關命令(比如 ps -M 命令)是可以看到的,一個進程所能創建的線程數目以及一個操作系統所能創建的總的線程數目等都由運行時操作系統限定。
thread
類是一個特殊的類,它不能被拷貝,只能被轉移或者互換
- 線程的轉移使用
move
函數,如thread t2 = move(t);
thread t2 = move(t); // 改爲 t2 = t 將不能編譯。
t2.join();
- 如果將 t2.join() 改爲 t.join() 將會導致整個進程被結束,因爲忘記了調用 t2 也就是被轉移的線程的 join() 方法,從而導致整個進程被結束,而 t 則因爲已經被轉移,其 id 已被置空。
- 線程實例互換使用
swap
函數,如線程實例互換使用 swap 函數
。
- 在進行線程實例轉移的時候,要注意判斷目的實例的
id
是否爲空值。
std::this_thread::sleep_for(chrono::milliseconds(10))
:表示當前線程休眠一段時間(10ms),休眠期間不與其他線程競爭 CPU,根據線程需求,等待若干時間。需要引入頭文件#include <chrono>
。
2. join() 函數與 detach() 函數
join()
函數是一個等待線程完成函數,主線程需要等待子線程運行結束了纔可以結束;
detach()
函數稱爲分離線程函數,使用detach()
函數會讓線程在後臺運行,即說明主線程不會等待子線程運行結束才結束。
- 用
join()
函數,主線程就會等待子線程運行結束,然後主線程再運行,直到結束,傳統編程就是如此;
detach()
函數,detach
即分離的意思,一旦線程detach
,也就是說將子線程交給系統託管,與進程,主線程無關了,這種情況下,就很可能主線程結束,子線程還在運行,所以使用detach()
就引發出問題,編程的難度也加大了。
- 線程
detach
以後,子線程會成爲孤兒線程,線程之間將無法通信。
auto n = thread::hardware_concurrency();//獲取cpu核心個數
3. mutex 互斥量
- 引入頭文件
#include <mutex>
- 定義兩個全局變量
int num(0);
和mutex m;
- 在線程中要對
num
操作前後,分別對m
加鎖和解鎖。
void run() {
m.lock();
num++;
m.unlock();
}
-
- 通過互斥量後運算結果正確,但是計算速度很慢,原因主要是互斥量加解鎖需要時間。
#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;
}
4. 原子變量
- 引入頭文件
#include <atomic>
- 定義一個全局的原子變量:
atomic_int num{0};
則不會發生線程衝突,即線程安全。
void run() {
for(int i = 0; i < 8; i++)
num++;
}
#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;
}
5. 使用 join() 函數
- 定義全局變量
int num = 0;
- 在
mian()
函數中對線程t1
使用join()
函數
int main() {
...
thread t1(run);
t1.join();
...
return 0;
}
6. 時間等待相關問題
#include<iostream>
#include<thread>
#include<chrono>
using namespace std;
int main()
{
thread th1([]()
{
this_thread::sleep_for(chrono::seconds(3));
this_thread::yield();
cout << this_thread::get_id() << endl;
});
return 0;
}