c++ map find方法源码解析

1.背景

最近,发生了一次奇怪的问题:

用const char*作为map的key,定制比较器(采用strcmp实现),同时用了多线程的技术,在map.find()时候有几率发生死循环,通过windbg调试定位问题,发现是

在map.find()时候发生了死循环,进而导致执行该过程的线程拿到锁了,但无法释放,后续其他线程一直等待该锁,从而发生假的“死锁”(实际为死循环)。

结合map的源码、对应DMP文件的堆栈和相关变量额值(主要为发生死循环的map)以及map的数据结构来分析死循环的原因。

2.map源码

2.1.先看堆栈

在windbg中使用~*kb显示所有线程的堆栈信息,

c++ map find方法源码解析_第1张图片

接下来需要找到源码xtree中的1424/1481/2056位置和1424/1481/2050/2032位置,

tips:

该问题最直观的表现是发生了“死锁”,但通过windbg附加到进程(也可以直接用VS打开DMP文件,VS更香,连局部变量值都给你显示出来了,和在VS中调试代码别无二致!)看堆栈,发现是拿到锁的线程一直卡在map.find中,

导致其他线程一直在等待锁,然后用windbg中的调试命令go、step over、step out等调试命令让程序继续运行,(如果使用了go,需要用break使程序暂停)再输入~*kb输出堆栈信息,发现还是上述位置,那么即可根据源码,分析为什么堆栈会是这样的。

2.2.源码

C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\VC\Tools\MSVC\14.16.27023\include\xtree

c++ map find方法源码解析_第2张图片

c++ map find方法源码解析_第3张图片

c++ map find方法源码解析_第4张图片

结合源码和注释可以看到,find的实现思路是从红黑树上找到大于_Keyval且在最左边(也是是大于_Keyval的节点中最小的那个)那个迭代器,

然后判断如果返回的迭代器_Where是end或者大于_Keyval(即_Keyval小于_Where)那么就是没找到等于_Keyval的元素,此时返回end(),否则就是找到了,返回目标迭代器。

接下来我们看看是怎么从红黑树上找到大于_Keyval且在最左边(也是是大于_Keyval的节点中最小的那个)那个迭代器,也就是lower_bound()函数的实现,

发现它仅调用了_Lbound()函数,那么再来看_Lbound()函数的实现,

  • 先取出_Myhead的值赋给_Wherenode作为记录,取_Myhead->_Parent作为当前的_Pnode;
  • 接下来是一个循环,循环条件是当前的_Pnode是否是叶子结点,如果是就执行循环体;
  • 进入循环体,比较当前的_Pnode和目标_Keyval的大小,
  1. 如果_Pnode和<_Keyval,_Pnode赋值为_Pnode->_Right;
  2. 如果_Pnode和>=_Keyval,更新_Wherenode的值为_Pnode(_Wherenode即为到当前为止符合要求的节点),并对_Pnode赋值为_Pnode->_Left;
  3. 不断的执行上述1和2,直到当前的_Pnode是叶子结点,退出循环体
  • 退出循环体后返回记录的_Wherenode,即为最符合要求的节点;

 然后执行上述判断,返回end()或目标迭代器。

c++ map find方法源码解析_第5张图片

上图显示了几种情况的执行结果,对于数据正常的map(红黑树),find()是没有问题的,因为find()依赖的是红黑树的结构,其复杂度为o(logn);

2.3.map数据结构

当然仅知道find()的源码还是不够的,还需要对照着map的数据结构来理解,map有_Myhead是end(),_Myhead的_Parent是红黑树的根节点,

详细可以参考我的另一篇文章c++ map部分用法关键点的记录

3.结论

3.1.本文所涉及问题结论

本文所涉及问题是map.find()时候发生了死循环,原因是map(红黑树)结构出现了问题:节点的左子树大于该节点了、且出现了循环指向的问题(比如节点3的left指向了节点5,节点5的left指向了节点3,循环指向),从而导致find()时候发生了死循环,一直出不来,由于又用了多线程,表现出来就像线程死锁一样,虽然实际上并不是死锁(windbg通过!locks也可以验证,没有临界区的资源被锁住)。当然再深层次原因及解决方案请参考我的另一篇文章。

3.2.需深入理解STL相关类和接口的原理

有时候仅仅知道STL库相关类和接口的功能是不够的,在排查问题或者分析效率的时候我们还需要知道它的实现方式,思路或者说原理,这样我们才能更好的使用它们,发挥出它们的优势和长处,

当然也发挥了我们的长处。

 

 

 

你可能感兴趣的:(C++,map,find,红黑树,STL容器,比较器)