join & detach join和detach为最基本的用法,join可以使主线程(main函数)等待子线程(自定义的function_1函数)完成后再退出程序,而detach可以使子线程与主线程毫无关联的独立运行,当主线程执行完毕后直接退出程序,不管子线程是否执行完毕。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <iostream> #include <thread> using namespace std;void function_1 () { for (int i=10 ;i>0 ;i--) cout << "=============Hello=============" << endl; } int main () { thread t1 (function_1) ; t1.detach (); cout << "~~~~~~~~~~~World~~~~~~~~~~~" << endl; if (t1.joinable ()) { t1.join (); } return 0 ; }
detach方法的执行结果如下,可以看出子线程没来得及执行完毕。
1 2 3 =============Hello============= ~~~~~~~~~~~World~~~~~~~~~~~ =请按任意键继续. . .
如果换成join方法,则可以输出10条Hello语句。
1 2 3 4 5 6 7 8 9 10 11 12 =============Hello============= =============Hello============= =============Hello============= =============Hello============= =============Hello============= =============Hello============= =============Hello============= =============Hello============= =============Hello============= =============Hello============= ~~~~~~~~~~~World~~~~~~~~~~~ 请按任意键继续. . .
try-catch异常捕获机制的使用 join可以使某些比较重要的函数执行完毕后再退出,但当程序出现异常时,程序仍会直接退出,join没有起到应有的作用,这是可以通过try-catch异常捕获机制,结合join方法,使某些函数(子线程)在程序出现异常时也能先执行完毕再退出,例子如下,通过OpenCV读取显示一张不存在的图片产生异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #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) ; try { cv::Mat img = cv::imread ("1.jpg" ); cv::imshow ("===" , img); } catch (...) { std::cout << "catch..............." << std::endl; t1.join (); throw ; } t1.join (); std::cout << "主程序正常退出" << std::endl; return 0 ; }
可以看出运行后产生了一个OpenCV Error,没能输出”主程序正常退出” ,但子线程在程序出现异常后依然可以继续执行完毕。
1 2 3 4 5 6 7 8 9 10 11 ========Hello=======OpenCV Error: Assertion failed (size.width>0 && size.height>0) in cv::imshow, file D:\Tools1\opencv\opencv\sources\modules\highgui\src\window.cpp, line 325 0 ========Hello=======1catch............... ========Hello=======2 ========Hello=======3 ========Hello=======4 ========Hello=======5 此处省略... ========Hello=======98 ========Hello=======99
通过类构造子线程 & ref方法传参 C++开发中更常使用类作为子线程函数而不是单独的某个函数。
注意一点在线程按引用传递参数时的写法,需要使用std::ref方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #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-----------" ; Fctor fct; std::thread t1 (fct, std::ref(s)) ; t1.join (); std::cout << "from main:" << s << std::endl; return 0 ; }
运行结果,方式1a或1b:
1 2 3 from t1:-----------World----------- from main:-----------World----------- 请按任意键继续. . .
方式2a或2b:
1 2 3 from t1:-----------World----------- from main:++++++++Hello+++++++++ 请按任意键继续. . .
mov方法传参 & 线程对象移动 除了使用ref方法对子线程进行传参,还可以使用mov方法传参,此外mov还可以移动线程对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #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; std::thread t1 ((Fctor()), std::move(s)) ; std::cout << "Sub_thread1_ID" << t1.get_id () << std::endl; std::thread t2 = std::move (t1); std::cout << "Sub_thread2_ID" << t2.get_id () << std::endl; t2.join (); std::cout << std::thread::hardware_concurrency () << std::endl; return 0 ; }
运行结果如下,可以看出传参无误,并且两个子线程的ID相同,说明子线程对象移动成功。
1 2 3 4 5 6 Main_thread_ID:36576 from t1:Sub_thread1_ID37472----------------World--------------- Sub_thread2_ID37472 8 请按任意键继续. . .
mutex & lock_guard mutex即互斥量,可理解为一把锁,访问某些资源时先加锁,访问后解锁。 另一进程访问同一资源时,首先尝试加锁,如果锁处于未释放状态则无法加锁,需等待其它线程对锁的释放。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #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) ; for (int i = 0 ; i < 100 ; i++) { share_print ("+++++++++++++++++from main:" , i); } t1.join (); return 0 ; }
运行结果类似如下:
1 2 3 4 5 6 7 8 9 ==========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()后的输出结果如下:
1 2 3 4 5 6 7 8 9 10 11 ==========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为如下,可实现同样的效果。
1 2 3 4 5 void share_print (std::string msg, int id) { std::lock_guard<std::mutex> guard (mu) ; std::cout << msg << id << std::endl; }
下面的代码是将share_print封装到一个类中,并添加将输出信息同时保存到文件中的功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 #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; }; 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)) ; for (int i = 0 ; i < 100 ; i++) { log.shared_print ("main" , i); } t1.join (); return 0 ; }
死锁 & adopt_lock 当某个资源被两把以上的锁嵌套加锁,且锁的顺序不一致时,可能发生死锁。
原因在于多个线程可能各自加了1把锁后,同时在等待对方释放剩余的锁。
最简单的解决方法是:只要锁的顺序一致,就不会死锁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #include <iostream> #include <thread> #include <string> #include <mutex> #include <fstream> class LogFile { std::mutex m_mutex; std::mutex m_mutex2; std::ofstream f; public : LogFile () { f.open ("log.txt" ); } void shared_print (std::string id, int value) { std::lock_guard<std::mutex> locker (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::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)) ; for (int i = 0 ; i < 1000 ; i++) { log.shared_print2 (std::string ("from main:" ), i); } t1.join (); return 0 ; }
某次运行结果如下,程序运行到某时刻卡住了:
1 2 3 4 5 6 7 8 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功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #include <iostream> #include <thread> #include <string> #include <mutex> #include <fstream> class LogFile { std::mutex m_mutex; std::mutex m_mutex2; std::ofstream f; public : LogFile () { f.open ("log.txt" ); } void shared_print (std::string id, int value) { std::lock (m_mutex, m_mutex2); std::lock_guard<std::mutex> locker (m_mutex,std::adopt_lock) ; std::lock_guard<std::mutex> locker2 (m_mutex2, std::adopt_lock) ; 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)) ; for (int i = 0 ; i < 1000 ; i++) { log.shared_print2 (std::string ("from main:" ), i); } t1.join (); return 0 ; }
运行结果如下,不会出现死锁:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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 请按任意键继续. . .