c++的併發操做(多線程)

C++11標準在標準庫中爲多線程提供了組件,這意味着使用C++編寫與平臺無關的多線程程序成爲可能,而C++程序的可移植性也獲得了有力的保證。另外,併發編程可提升應用的性能,這對對性能錙銖必較的C++程序員來講是值得關注的。ios

1. 何爲併發

併發指的是兩個或多個獨立的活動在同一時段內發生。生活中併發的例子並很多,例如在跑步的時候你可能同時在聽音樂;在看電腦顯示器的同時你的手指在敲擊鍵盤。這時咱們稱咱們大腦併發地處理這些事件,只不過咱們大腦的處理是有次重點的:有時候你會更關注你呼吸的頻率,而有時候你更多地被美妙的音樂旋律所吸引。這時咱們能夠說大腦是一種併發設計的結構。這種次重點在計算機程序設計中,體現爲某一個時刻只能處理一個操做。程序員

與併發相近的另外一個概念是並行。它們二者存在很大的差異。並行就是同時執行,計算機在同一時刻,在某個時間點上處理兩個或以上的操做。判斷一個程序是否並行執行,只須要看某個時刻上是否多兩個或以上的工做單位在運行。一個程序若是是單線程的,那麼它沒法並行地運行。利用多線程與多進程可使得計算機並行地處理程序(固然 ,前提是該計算機有多個處理核心)。編程

  • 併發:同一時間段內能夠交替處理多個操做:

圖中整個安檢系統是一個併發設計的結構。兩個安檢隊列隊首的人競爭這一個安檢窗口,兩個隊列可能約定交替着進行安檢,也多是你們同時競爭安檢窗口(通訊)。後一種方式可能引發衝突:由於沒法同時進行兩個安檢操做。在邏輯上看來,這個安檢窗口是同時處理這兩個隊列。promise

  • 並行:同一時刻內同時處理多個操做:

圖中整個安檢系統是一個並行的系統。在這裏,每一個隊列都有本身的安檢窗口,兩個隊列中間沒有競爭關係,隊列中的某個排隊者只需等待隊列前面的人安檢完成,而後再輪到本身安檢。在物理上,安檢窗口同時處理這兩個隊列。安全

併發的程序設計,提供了一種方式讓咱們可以設計出一種方案將問題(非必須地)並行地解決。若是咱們將程序的結構設計爲能夠併發執行的,那麼在支持並行的機器上,咱們能夠將程序並行地執行。所以,併發重點指的是程序的設計結構,而並行指的是程序運行的狀態。併發編程,是一種將一個程序分解成小片斷獨立執行的程序設計方法。多線程

2.併發的基本方式途徑

多線程與多進程是併發的兩種途徑。
想象兩個場景:併發

  • 場景一:你和小夥伴要開發一個項目,但小夥伴們放寒假都回家了,大家只能經過QQ聊天、手機通話、發送思惟導圖等方式來進行交流,總之大家沒法很方便地進行溝通。好處是大家各自工做時能夠互不打擾。
  • 場景二:你和小夥伴放假都呆在學校實驗室中開發項目,大家能夠聚在一塊兒使用頭腦風暴,可使用白板進行觀點的闡述,總之大家溝通變得更方便有效了。有點遺憾的是你在思考時可能有小夥伴過來問你問題,你受到了打擾。

這兩個場景描繪了併發的兩種基本途徑。每一個小夥伴表明一個線程,工做地點表明一個處理器。場景一中每一個小夥伴是一個單線程的進程,他們擁有獨立的處理器,多個進程同時執行;場景二中只有一個處理器,全部小夥伴都是屬於同一進程的線程。ide

2.1 多進程併發

多個進程獨立地運行,它們之間經過進程間常規的通訊渠道傳遞訊息(信號,套接字,文件,管道等),這種進程間通訊不是設置複雜就是速度慢,這是由於爲了不一個進程去修改另外一個進程,操做系統在進程間提供了必定的保護措施,固然,這也使得編寫安全的併發代碼更容易。
運行多個進程也須要固定的開銷:進程的啓動時間,進程管理的資源消耗。函數

2.2 多線程併發

在當個進程中運行多個線程也能夠併發。線程就像輕量級的進程,每一個線程相互獨立運行,但它們共享地址空間,全部線程訪問到的大部分數據如指針、對象引用或其餘數據能夠在線程之間進行傳遞,它們均可以訪問全局變量。進程之間一般共享內存,但這種共享一般難以創建且難以管理,缺乏線程間數據的保護。所以,在多線程編程中,咱們必須確保每一個線程鎖訪問到的數據是一致的。性能

3. C++中的併發與多線程

C++標準並無提供對多進程併發的原生支持,因此C++的多進程併發要靠其餘API——這須要依賴相關平臺。
C++11 標準提供了一個新的線程庫,內容包括了管理線程、保護共享數據、線程間的同步操做、低級原子操做等各類類。標準極大地提升了程序的可移植性,之前的多線程依賴於具體的平臺,而如今有了統一的接口進行實現。

C++11 新標準中引入了幾個頭文件來支持多線程編程:(因此咱們能夠再也不使用 CreateThread 來建立線程,簡簡單單地使用 std::thread 便可。)

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

3.1 初試多線程

   一、主進程等待子線程

  

#include <iostream>
#include <thread>
#include <Windows.h>
 
using namespace std;
 
void thread01()
{
	for (int i = 0; i < 5; i++)
	{
		cout << "Thread 01 is working !" << endl;
		Sleep(100);
	}
}
void thread02()
{
	for (int i = 0; i < 5; i++)
	{
		cout << "Thread 02 is working !" << endl;
		Sleep(200);
	}
}
 
int main()
{
	thread task01(thread01);
	thread task02(thread02);
	task01.join();
	task02.join();
 
	for (int i = 0; i < 5; i++)
	{
		cout << "Main thread is working !" << endl;
		Sleep(200);
	}
	system("pause");
}

  2.主進程和子進程互不干擾

  

#include <iostream>
#include <thread>
#include <Windows.h>
 
using namespace std;
 
void thread01()
{
	for (int i = 0; i < 5; i++)
	{
		cout << "Thread 01 is working !" << endl;
		Sleep(100);
	}
}
void thread02()
{
	for (int i = 0; i < 5; i++)
	{
		cout << "Thread 02 is working !" << endl;
		Sleep(200);
	}
}
 
int main()
{
	thread task01(thread01);
	thread task02(thread02);
	task01.detach();
	task02.detach();
 
	for (int i = 0; i < 5; i++)
	{
		cout << "Main thread is working !" << endl;
		Sleep(200);
	}
	system("pause");
}

  3.帶參數的子線程

#include <iostream>
#include <thread>
#include <Windows.h>
 
using namespace std;
 
//定義帶參數子線程
void thread01(int num)
{
	for (int i = 0; i < num; i++)
	{
		cout << "Thread 01 is working !" << endl;
		Sleep(100);
	}
}
void thread02(int num)
{
	for (int i = 0; i < num; i++)
	{
		cout << "Thread 02 is working !" << endl;
		Sleep(200);
	}
}
 
int main()
{
	thread task01(thread01, 5);  //帶參數子線程
	thread task02(thread02, 5);
	task01.detach();
	task02.detach();
 
	for (int i = 0; i < 5; i++)
	{
		cout << "Main thread is working !" << endl;
		Sleep(200);
	}
	system("pause");
}

  4.多線程競爭的狀況

有兩個問題,一是有不少變量被重複輸出了,而有的變量沒有被輸出;二是正常狀況下每一個線程輸出的數據後應該緊跟一個換行符,但這裏大部分倒是另外一個線程的輸出。

這是因爲第一個線程對變量操做的過程當中,第二個線程也對同一個變量進行各操做,致使第一個線程處理完後的輸出有多是線程二操做的結果。針對這種數據競爭的狀況,可使用線程互斥對象mutex保持數據同步。

mutex類的使用須要包含頭文件mutex:

#include <iostream>
#include <thread>
#include <Windows.h>
#include <mutex>
 
using namespace std;
 
mutex mu;  //線程互斥對象
 
int totalNum = 100;
 
void thread01()
{
	while (totalNum > 0)
	{
		mu.lock(); //同步數據鎖
		cout << totalNum << endl;
		totalNum--;
		Sleep(100);
		mu.unlock();  //解除鎖定
	}
}
void thread02()
{
	while (totalNum > 0)
	{
		mu.lock();
		cout << totalNum << endl;
		totalNum--;
		Sleep(100);
		mu.unlock();
	}
}
 
int main()
{
	thread task01(thread01);
	thread task02(thread02);
	task01.detach();
	task02.detach();
	system("pause");
}

  

 3.2 在類中使用子線程的一個問題

  當咱們再類中使用子線程咱們會發現,咱們不能把初始函數設置爲類的成員函數,必需要把成員函數設置成static類型的才能夠,可是這有設計到一個問題,就是static的類成員函數不能調用非static的變量成員,下面是一個一箭雙鵰的方法:

thread sendtask(bind(&client::sendata, this));//其中client是類的名字

  這樣就能夠解決咱們的問題。

相關文章
相關標籤/搜索