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:\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方法。
#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;//主线程ID
std::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;//锁1
std::mutex m_mutex2;//锁2
std::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;//锁1
std::mutex m_mutex2;//锁2
std::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
请按任意键继续. . .