
join & detach
join和detach為最基本的用法,join可以使主線程(main函數)等待子線程(自定義的function_1函數)完成后再退出程序,而detach可以使子線程與主線程毫無關聯的獨立運行,當主線程執行完畢后直接退出程序,不管子線程是否執行完畢。
#include<iostream>
#include<thread>
using namespace std;// 子線程函數
void function_1()
{for(int i=10;i>0;i--) // 循環10次輸出cout << "=============Hello=============" << endl;
}int main()
{thread t1(function_1);//線程開始//t1.join();//方式1:結合(等待其完成)t1.detach();//方式2:分離(使其自行運行)(cout未來得及輸出完畢,主線程已結束)cout << "~~~~~~~~~~~World~~~~~~~~~~~" << endl;if (t1.joinable()){t1.join();}return 0;
}
detach方法的執行結果如下,可以看出子線程沒來得及執行完畢。
=============Hello=============
~~~~~~~~~~~World~~~~~~~~~~~
=請按任意鍵繼續. . .
如果換成join方法,則可以輸出10條Hello語句。
=============Hello=============
=============Hello=============
=============Hello=============
=============Hello=============
=============Hello=============
=============Hello=============
=============Hello=============
=============Hello=============
=============Hello=============
=============Hello=============
~~~~~~~~~~~World~~~~~~~~~~~
請按任意鍵繼續. . .
try-catch異常捕獲機制的使用
join可以使某些比較重要的函數執行完畢后再退出,但當程序出現異常時,程序仍會直接退出,join沒有起到應有的作用,這是可以通過try-catch異常捕獲機制,結合join方法,使某些函數(子線程)在程序出現異常時也能先執行完畢再退出,例子如下,通過OpenCV讀取顯示一張不存在的圖片產生異常。
#include<iostream>
#include<thread>
#include<opencv2/opencv.hpp>// 子線程函數(假定該函數比較重要,無論如何都要執行完畢再退出程序)
void function_1()
{for (int i = 0; i < 100; i++){std::cout << "========Hello=======" << i << std::endl;}
}int main()
{std::thread t1(function_1);//t1線程開始運行try //【捕獲異常的范圍】{cv::Mat img = cv::imread("1.jpg");//讀取一張不存在的圖片,使下句的圖片顯示出現異常cv::imshow("===", img);//此處將出現異常!?錯誤?//出現異常會導致整個程序直接退出//捕獲異常后,可以進行補救,如使t1子線程執行完畢。}catch (...)//捕獲所有異常{std::cout << "catch..............." << std::endl;t1.join();//使子線程執行完畢throw;}t1.join();std::cout << "主程序正常退出" << std::endl;return 0;
}
可以看出運行后產生了一個OpenCV Error,沒能輸出"主程序正常退出" ,但子線程在程序出現異常后依然可以繼續執行完畢。
========Hello=======OpenCV Error: Assertion failed (size.width>0 && size.height>0) in cv::imshow, file D:Tools1opencvopencvsourcesmoduleshighguisrcwindow.cpp, line 325
0
========Hello=======1catch...............========Hello=======2
========Hello=======3
========Hello=======4
========Hello=======5
此處省略...
========Hello=======98
========Hello=======99
通過類構造子線程 & ref方法傳參
C++開發中更常使用類作為子線程函數而不是單獨的某個函數。
注意一點在線程按引用傳遞參數時的寫法,需要使用std::ref方法。
#include<iostream>
#include<thread>
#include<string>class Fctor
{
public:void operator()(std::string& msg)//按【引用】傳遞參數{std::cout << "from t1:" << msg << std::endl; msg = "++++++++Hello+++++++++";//修改傳入的參數(用于后面的主線程輸出)}
};int main()
{std::string s = "-----------World-----------";//待傳入的參數(用于子線程輸出)// 方式1a:這種方式會自動復制一份參數傳進去//Fctor fct;//std::thread t1(fct,s);//t1線程開始運行// 方式1b:這種方式會自動復制一份參數傳進去//std::thread t1((Fctor()), s);//t1線程開始運行// 方式2a:按引用傳遞Fctor fct;std::thread t1(fct, std::ref(s));//t1線程開始運行// 方式2b:按引用傳遞//std::thread t1((Fctor()), std::ref(s));t1.join();std::cout << "from main:" << s << std::endl;return 0;
}
運行結果,方式1a或1b:
from t1:-----------World-----------
from main:-----------World-----------
請按任意鍵繼續. . .
方式2a或2b:
from t1:-----------World-----------
from main:++++++++Hello+++++++++
請按任意鍵繼續. . .
mov方法傳參 & 線程對象移動
除了使用ref方法對子線程進行傳參,還可以使用mov方法傳參,此外mov還可以移動線程對象。
#include<iostream>
#include<thread>
#include<string>class Fctor
{
public:void operator()(std::string& msg)//按引用傳遞參數{std::cout << "from t1:" << msg << std::endl;msg = "++++++++++++Hello++++++++++";}
};int main()
{std::string s = "----------------World---------------";std::cout << "Main_thread_ID:" << std::this_thread::get_id() << std::endl;//主線程IDstd::thread t1((Fctor()), std::move(s));//子線程1(將字符串從主線程移動到子線程)std::cout << "Sub_thread1_ID" << t1.get_id() << std::endl;//線程對象只能被移動,不能被復制。std::thread t2 = std::move(t1);//子線程2(接管子線程1,此時子線程1為空?!)std::cout << "Sub_thread2_ID" << t2.get_id() << std::endl;//可以看到兩個子線程的ID是相同的!t2.join();//等待子線程2結束//檢測硬件并發特性(此句只是用來顯示計算機支持的并發線程數量)std::cout << std::thread::hardware_concurrency() << std::endl;return 0;
}
運行結果如下,可以看出傳參無誤,并且兩個子線程的ID相同,說明子線程對象移動成功。
Main_thread_ID:36576
from t1:Sub_thread1_ID37472----------------World---------------Sub_thread2_ID37472
8
請按任意鍵繼續. . .
mutex & lock_guard
mutex即互斥量,可理解為一把鎖,訪問某些資源時先加鎖,訪問后解鎖。 另一進程訪問同一資源時,首先嘗試加鎖,如果鎖處于未釋放狀態則無法加鎖,需等待其它線程對鎖的釋放。
#include<iostream>
#include<thread>
#include<string>
#include<mutex>std::mutex mu;//【互斥對象】=》一把鎖通過函數調用cout,并為cout加鎖,防止同時訪問cout
void share_print(std::string msg, int id)
{mu.lock();std::cout << msg << id << std::endl;mu.unlock();
}//子線程函數
void function_1()
{for(int i = 0; i < 100; i++)share_print("==========from t1:" ,i );
}int main()//主線程
{std::thread t1(function_1);//t1線程開始運行for (int i = 0; i < 100; i++){share_print("+++++++++++++++++from main:", i);}t1.join();//等待子線程結束return 0;
}
運行結果類似如下:
==========from t1:0
+++++++++++++++++from main:0
==========from t1:1
+++++++++++++++++from main:1
==========from t1:2
==========from t1:3
==========from t1:4
==========from t1:5
省略...
如果未使用加鎖機制,兩線程會互相爭搶cout的使用權,從而導致輸出混亂,注釋掉mu.lock()與mu.unlock()后的輸出結果如下:
==========from t1:0+++++++++++++++++from main:0==========from t1:1+++++++++++++++++from main:1==========from t1:2+++++++++++++++++from main:2==========from t1:3
+++++++++++++++++from main:3==========from t1:4==========from t1:5+++++++++++++++++from main:4
省略...
由于lock()與unlock()必須成對出現,為方便管理,出現了lock_guard,它可以對mutex進行管理,自動實現lock()與unlock(),原理是在其構造與析構中自動調用。另外,還可有附加參數。
修改上面的share_print為如下,可實現同樣的效果。
void share_print(std::string msg, int id)
{std::lock_guard<std::mutex> guard(mu);std::cout << msg << id << std::endl;
}
下面的代碼是將share_print封裝到一個類中,并添加將輸出信息同時保存到文件中的功能:
#include<iostream>
#include<thread>
#include<string>
#include<mutex>
#include<fstream>class LofFile
{
public:LofFile(){ f.open("log.txt"); }~LofFile(){ f.close(); }void shared_print(std::string id, int value){std::lock_guard<std::mutex> locker(m_mutex);f << "from " << id << ":" << value << std::endl;//寫入文件std::cout << "from " << id << ":" << value << std::endl;//輸出}private://受保護的成員std::mutex m_mutex;//鎖std::ofstream f;//此時f完全在鎖的保護下
};void function_1(LofFile& log)
{for (int i = 0; i > -100; i--)log.shared_print("t1", i);
}int main()//主線程
{LofFile log;std::thread t1(function_1,std::ref(log));//t1線程開始運行for (int i = 0; i < 100; i++){log.shared_print("main", i);}t1.join();return 0;
}
死鎖 & adopt_lock
當某個資源被兩把以上的鎖嵌套加鎖,且鎖的順序不一致時,可能發生死鎖。
原因在于多個線程可能各自加了1把鎖后,同時在等待對方釋放剩余的鎖。
最簡單的解決方法是:只要鎖的順序一致,就不會死鎖。
#include<iostream>
#include<thread>
#include<string>
#include<mutex>
#include<fstream>class LogFile
{std::mutex m_mutex;//鎖1std::mutex m_mutex2;//鎖2std::ofstream f;
public:LogFile()//構造函數,初始化時新建一個txt文件{f.open("log.txt");}void shared_print(std::string id, int value){std::lock_guard<std::mutex> locker(m_mutex);//鎖住m_mutex成員std::lock_guard<std::mutex> locker2(m_mutex2);std::cout << id << ":" << value << std::endl;}void shared_print2(std::string id, int value){std::lock_guard<std::mutex> locker2(m_mutex2);//【出現死所,交換和下一行的位置即可】std::lock_guard<std::mutex> locker(m_mutex);//std::lock_guard<std::mutex> locker2(m_mutex2);std::cout << id << ":" << value << std::endl;}
};void function_1(LogFile& log)
{for (int i = 0; i > -1000; i--)log.shared_print(std::string("from t1:"), i);
}int main()//主線程
{LogFile log;std::thread t1(function_1, std::ref(log));//t1線程開始運行for (int i = 0; i < 1000; i++){log.shared_print2(std::string("from main:"), i);}t1.join();return 0;
}
某次運行結果如下,程序運行到某時刻卡住了:
from main::0
from main::1
省略...
from main::154
from main::155
from main::156
from main::157
from t1::0
當程序比較復雜時,手動方法管理加鎖順序可能相當麻煩,這是就出現了adopt_lock參數來解決。
lock+lock_guard的adopt_lock參數自動避免死鎖問題。
lock()可同時管理多個鎖,順序無影響,同時鎖住多個鎖,若不可,先釋放,然后繼續嘗試。 lock_guard()的adopt_lock參數即拋棄lock操作,因為前面(必須)已加鎖,只使用其自動unlock功能。
#include<iostream>
#include<thread>
#include<string>
#include<mutex>
#include<fstream>class LogFile
{std::mutex m_mutex;//鎖1std::mutex m_mutex2;//鎖2std::ofstream f;
public:LogFile(){f.open("log.txt");}void shared_print(std::string id, int value){std::lock(m_mutex, m_mutex2);//lock()同時管理多個鎖std::lock_guard<std::mutex> locker(m_mutex,std::adopt_lock);//adopt_lock即拋棄lock操作,因為上句已加鎖std::lock_guard<std::mutex> locker2(m_mutex2, std::adopt_lock);//在析構時自動unlock()std::cout << id << ":" << value << std::endl;}void shared_print2(std::string id, int value){std::lock(m_mutex, m_mutex2);std::lock_guard<std::mutex> locker2(m_mutex2, std::adopt_lock);std::lock_guard<std::mutex> locker(m_mutex, std::adopt_lock);std::cout << id << ":" << value << std::endl;}
};void function_1(LogFile& log)
{for (int i = 0; i > -1000; i--)log.shared_print(std::string("from t1:"), i);
}int main()//主線程
{LogFile log;std::thread t1(function_1, std::ref(log));//t1線程開始運行for (int i = 0; i < 1000; i++){log.shared_print2(std::string("from main:"), i);}t1.join();return 0;
}
運行結果如下,不會出現死鎖:
from t1::0
from main::0
from t1::-1
from main::1
省略...
from t1::-997
from main::994
from t1::-998
from main::995
from t1::-999
from main::996
from main::997
from main::998
from main::999
請按任意鍵繼續. . .