多线程基础学习笔记

区别

进程&线程

  • 进程: 进行中的可执行程序
  • 线程:进程可以包含多个线程;
    - 主线程:从main函数开始,到执行完毕,即主线程结束,进程结束
    - 其他线程:需要用户自行创建,线程入口可以是某个函数/类/lambda表达式
    - 进程执行完毕的标志:如果主线程执行完毕,代表整个进程执行完毕,如果此时还有子线程没有执行完也会强行终止

多进程并发与多线程并发

  • 多进程并发:用一个进程采集slam数据,一个进程来处理数据
  • 多线程并发:一个进程中所有线程共享地址空间(共享内存);全局变量,全局内存,全局引用个都可以在线程之间传递,多线程开销远小于多进程

std::thread类的几个基本函数

成员函数含义适用场合
get_id()查看当前线程id
join()可理解为子程序与主程序汇合。如果调用这个函数的是线程a,就要等这个线程a都完成了,join()函数才会返回,回到主线程必须要得到这个线程的执行结果才能继续主线程时
detach()表示分离,主线程不再与子线程汇合,如果主线程执行完毕,不管这个子线程进展如何都会结束如果子线程什么时候结束对主线程影响都不大时可用
joinable()判断是否可以使用join(),可以则返回true,否则为false。如果这个子线程传入了某函数/类/lambda表达式,到执行join()之前,这个子线程都是joinable的

如果两个线程共享的内容较多,那么这两效率不会很高,独立的线程各自运行效率会更高。

怎么建立子线程?–>2种方法

  • 用函数的形式建立子线程
//括号里为函数名,如果这个函数是带有参数的,那么在后面直接再写传的参数即可
thread putThread(&putInData);
thread takeThread(&takeOutData);
  • 用类的形式建立子线程
//这里调用的就是DealData类中的成员函数,当前创建了DealData类的对象
DealData myData
thread putThread(&DealData::putInData, ref(myData));
thread takeThread(&DealData::takeOutData, ref(myData));

takeThread.join();
putThread.join();

std::mutex类的几个基本函数

mutex可以被翻译成互斥量,它的出现是为了在多线程中,可以安全的进行数据共享。什么叫不安全的数据呢?即同一个地址的数据又被读取又被改写,容易发生冲突崩溃。
创建mutex类的对象,这个对象可以理解为一把锁,多个线程同一时间只有一个线程能加锁或解锁(加锁解锁必须成对使用)。也就是说,正确使用这把锁,就能保护好对应的数据,就不会有多个线程同时对同一个数据进行处理。

举例:
一个线程a中,对一个mutex对象上了锁,即有myMutex.lock(),如果其他线程此时要操作同一个数据,就要等到线程a中解锁后myMutex.unlock()才可以。

成员函数含义
lock()给互斥量上锁
unlock()给互斥量解锁

上锁和解锁一定要成对使用! 举例:

//这里乍一看,unlock了两次,但是要注意实际上unlock只可能执行一次
//如果进入if作用域,那么解锁后就直接范围true了,如果没有进入if解锁后返回false
myMutex.lock();
if(!dataQueue.empty()){
dataQueue.pop_front();
myMutex.unlock();
return true;
}

myMutex.unlock();
return false;

std::lock_guard类的基本用法

从上面的例子,可以看到,使用上锁,解锁这两个函数起始很容易出问题,所以又可以提到一个类模板std::lock_guard,它相当于可以对互斥量在某一周期内进行上锁和解锁两个操作,生命周期由大括号限制。这种方法比直接上锁解锁更方便,但是有时候可能没那么灵活。

{
  //要先创建lock_guard类的对象
	lock_guard<mutex> dataOutGuard(myMutex); 
	if(!dataQueue.empty()){
	    dataQueue.pop_front();
	    return true;
	}
	return false;
} //表示生命周期

std::unique_lock类的基本函数

unique_lock更加方便,有更多的成员函数。

成员函数含义
lock()给互斥量上锁
unlock()给互斥量解锁
release()释放锁的所有权
  • release()的举例:释放锁的所有权
unique_lock<mutex> dataControl(myMutex);
//释放了dataControl对myMutex的所有权,由pControl接管
std::mutex* pControl = dataControl.release();
dataQueue.push_back(i);  //共享的数据
pControl->unlock();
  • 举例:转移锁的所有权
unique_lock<mutex> dataControl(myMutex);
//这里必须要用std::move(), 不能复制
unique_lock<mutex> dataControl2(std::move(dataControl));
dataQueue.push_back(i);  //共享的数据

死锁问题

什么是死锁?死锁是怎么产生的?
下面的配图可以进行说明。可以看到下图的右边,假设说线程a先成功上锁了myMutex1,接下来发生了上下文切换,到了线程b,此时试图去锁线程b的myMutex2, 由于这个之前没有被锁,所以此时可以成功上锁线程b的myMutex2,此时,死锁产生,无法继续运行。
因为如果继续在线程b运行,无法再上锁myMutex1(之前已经被锁住),也没办法再切换回线程a继续,因为myMutex2也已经被上锁了。的
解决方法:
在不同的线程给多个(至少2个) 互斥量上锁的时候,一定要保持上锁的顺序一致。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值