目录
线程的创建
线程的创建需要#include <thread>,格式为thread mythread(....)
1.对函数创建线程
格式为thread mythread(function,variable1,variable2,...)
#include <iostream>
#include <thread>
using namespace std;
void fuction(int a, int b) {
cout << "enter the subthread:";
cout << a << b << endl;
}
int main(void) {
cout << "in main thread" << endl;
thread mythread(fuction, 2, 9);
mythread.join();
cout << "in main thread" << endl;
}
2.对类里面创建线程
格式为thread mythread(classvariable,variables);这里似乎一定要重载括号。
#include <iostream>
#include <thread>
#include <string>
using namespace std;
class X
{
public:
X() { cout << "constructor!" << endl; }
X(const X& m) { cout << "copy constructor called !" << endl; }
void operator()(int a) // 重载括号运算符。如果无参则为void operator()(){...}
{
cout << "start!" << a << endl;
}
};
int main()
{
X a;
thread p(a, 4); //以对象a创建线程,并传递参数4,无参的话直接省略第二个参数,更多的参数直接加在后面
p.join();
return 0;
}
3.对成员函数创建变量
格式为thread mythread(&class::function,&classvariable,variables_of_function)
#include <iostream>
#include <thread>
#include <string>
using namespace std;
class X
{
public:
X() { cout << "constructor!" << endl; }
X(const X& m) { cout << "copy constructor called !" << endl; }
void coutdata(int a, int d) {
cout << a << '&' << d << endl;
}
};
int main()
{
X a;
thread p(&X::coutdata,&a,3,4); //以对象coutdata创建线程,并传递参数3、4,
//无参的话直接省略第二个参数,更多的参数直接加在后面
p.join();
return 0;
}
4.多线程的创立
只要同时使用多个thread创建,再逐一join便可。mythread.join();表示阻塞主线程,等子线程运行完再运行主线程,可join多个子线程。这时便进行了多线程运行,如果两个线程同时对一个变量进行操作则需要下面的mutex。C++ 多线程join()和detach()的理解_皮皮#2500-CSDN博客_c++线程detach
Mutex
锁是把在lock和unlock之间的代码锁起来,如果涉及到几个线程共同涉及到一个变量,锁便起了大作用。需要#include <mutex>。没有加锁:
#include <iostream>
#include <thread>
#include <string>
#include <mutex>
#include <list>
using namespace std;
class List {
private:
list<int> mylist;
mutex mlock;
int i = 0;
public:
void WriteList()
{
while (i < 1000)
{
mylist.push_back(i++);
}
return;
}
void showList()
{
for (auto p = mylist.begin(); p != mylist.end(); p++)
{
cout << (*p) << " ";
}
cout << endl;
cout << "size of list : " << mylist.size() << endl;
return;
}
};
int main()
{
List mlist;
thread pwrite0(&List::WriteList, &mlist);
thread pwrite1(&List::WriteList, &mlist);
pwrite0.join();
pwrite1.join();
cout << "threads end!" << endl;
mlist.showList(); //子线程结束后主线程打印list
return 0;
}
需要注意的是:这里的.join()只是阻塞主线程,不对其他线程进行阻塞,只与出来的顺序有关系。
1.一般互斥锁
1. 原子性:把一个互斥量锁定为一个原子操作,这意味着如果一个线程锁定了一个互斥量,没有其他线程在同一时间可以成功锁定这个互斥量;
2. 唯一性:如果一个线程锁定了一个互斥量,在它解除锁定之前,没有其他线程可以锁定这个互斥量;
3. 非繁忙等待:如果一个线程已经锁定了一个互斥量,第二个线程又试图去锁定这个互斥量,则第二个线程将被挂起(不占用任何cpu资源),直到第一个线程解除对这个互斥量的锁定为止,第二个线程则被唤醒并继续执行,同时锁定这个互斥量。
互斥锁将mlock.lock()和mlock.unlock()之间锁住;
#include <iostream>
#include <thread>
#include <string>
#include <mutex>
#include <list>
using namespace std;
class List {
private:
list<int> mylist;
mutex mlock;
int i = 0;
public:
void WriteList()
{
while (i < 1000)
{
mlock.lock();
if (i >= 10000)
{
mlock.unlock(); //退出之前必须先解锁
break;
}
mylist.push_back(i++);
mlock.unlock();
}
return;
}
void showList()
{
for (auto p = mylist.begin(); p != mylist.end(); p++)
{
cout << (*p) << " ";
}
cout << endl;
cout << "size of list : " << mylist.size() << endl;
return;
}
};
int main()
{
List mlist;
thread pwrite0(&List::WriteList, &mlist);
thread pwrite1(&List::WriteList, &mlist);
pwrite0.join();
pwrite1.join();
cout << "threads end!" << endl;
mlist.showList(); //子线程结束后主线程打印list
return 0;
}
2.lock_guard
lock_guard就是一个类,它会在lock_guard的构造函数中加锁,而在析构函数中解锁,也就是说,只要创建一个lock_guard的对象,就相当于lock()了,而该对象析构时,就自动调用unlock()了。
void WriteList()
{
while(i<10000)
{
lock_guard<mutex> guard(mlock); //创建lock_guard的类对象guard,用互斥量m来构造
//mlock.lock();
if (i >= 10000)
{
//mlock.unlock(); //由于有了guard,这里就无需unlock()了
break;
}
mylist.push_back(i++);
//mlock.unlock();
}
return;
}
3.unique_lock
unique_lock和上面的规则差不多,不过他生命期内允许手动加锁和释放锁。图来自:
这里说明一下unique_lock是给原先有的一个mutex类型(比如叫mlock)再套上一层,
unique<mutex> locker(mlock)。使mlock变为名字为locker的特殊锁,对其解锁时用的是:
locker.unlock();而非mlock.unlock();
条件变量
条件变量使用“通知—唤醒”模型,生产者生产出一个数据后通知消费者使用,消费者在未接到通知前处于休眠状态节约CPU资源;当消费者收到通知后,赶紧从休眠状态被唤醒来处理数据,使用了事件驱动模型,在保证不误事儿的情况下尽可能减少无用功降低对资源的消耗。
- 那个通知“条件已满足”的线程(或多个线程之一)必须调用notify_one()或notify_all(),以便条件满足时唤醒处于等待中的一个条件变量;
- 那个等待"条件被满足"的线程必须调用wait(),可以让线程在条件未被满足时陷入休眠状态,当接收到通知时被唤醒去处理相应的任务
#include <iostream>
#include <deque> //双端队列
#include <thread>
#include <mutex>
using namespace std;
deque<int> q;
mutex mu;
condition_variable cond;
void productor() {
int count = 10;
while (count > 0) {
unique_lock<mutex> locker(mu);
q.push_front(count);
locker.unlock();
cond.notify_one();
this_thread::sleep_for(chrono::seconds(1));
count--;
}
}
void consumer() {
int data = 0;
while (data != 1) {
unique_lock<mutex> locker(mu);
while (q.empty()) {
cond.wait(locker);
}
data = q.back();
q.pop_back();
locker.unlock();
std::cout << "t2 got a value from t1: " << data << std::endl;
}
}
int main(void) {
thread t1(productor);
thread t2(consumer);
t1.join();
t2.join();
return 0;
}
真实工程举例
在高翔博士orbslam2的稠密建图的版本中用到了很多锁。
这里的unique_lock以一个{}之内的东西认为是其作用区域,处理{}之后就析构。第一个锁是抢了关闭权,第二个conditional_variable等待关键帧的插入,第三个防止计算关键帧出错。。。
void PointCloudMapping::viewer()
{
pcl::visualization::CloudViewer viewer("viewer");
while(1)
{
{
unique_lock<mutex> lck_shutdown( shutDownMutex );
if (shutDownFlag)
{
break;
}
}
{//锁:插入的时候我不进行
unique_lock<mutex> lck_keyframeUpdated( keyFrameUpdateMutex );
keyFrameUpdated.wait( lck_keyframeUpdated );
}
// keyframe is updated
size_t N=0;
{//锁:防止计算出错
unique_lock<mutex> lck( keyframeMutex );
N = keyframes.size();
}
for ( size_t i=lastKeyframeSize; i<N ; i++ )
{
PointCloud::Ptr p = generatePointCloud( keyframes[i], colorImgs[i], depthImgs[i] );
*globalMap += *p;
}
PointCloud::Ptr tmp(new PointCloud());
voxel.setInputCloud( globalMap );
voxel.filter( *tmp );
globalMap->swap( *tmp );
viewer.showCloud( globalMap );
cout << "show global map, size=" << globalMap->points.size() << endl;
lastKeyframeSize = N;
}
}