深入解析 Hazard Pointer (中)

看过上篇的朋友,可能会认为:这不就是Smart Pointer么?于是可能写出这样的代码:

 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
#include<iostream>
#include<thread>
using namespace std;
class SmartPointer
{
public:
  SmartPointer(int *p)
  {
    pointee_ = p;
    ref_counts_ = 0;
  }
  int *pointee_;
  int ref_counts_;
};
SmartPointer sp(new int(1));
void Reader()
{
  ++sp.ref_counts_;
  cout<<*(sp.pointee_)<<endl;
  --sp.ref_counts_;
}
void Writer()
{
  if (sp.ref_counts_ == 0)
    delete sp.pointee_;
}
int main()
{
  thread t1(Reader);
  thread t2(Reader);
  thread t3(Reader);
  thread t4(Writer);
  t1.join();
  t2.join();
  t3.join();
  t4.join();
  return 0;
}

然而事实上,这样做是错的。其中的race condition请读者自行分析。

那么,Hazard Pointer(HP)和Smart Pointer(SP)有什么区别呢?它们的共同点就是管理对应对象的生命周期,然而这两者有本质的区别,HP是线程安全的,而SP不是。

HP中,每个读线程维护着自己的HP list,这个list,只由该线程写。因此,它是线程安全的。该list会(可以)被其他线程读。

每个写线程维护自己的retire list,该retire list只由该写线程进行读写。由于写线程可以读其他所有读线程的HP list,这样,差集(在自己的retire list,但是不在所有读线程的HP list里的指针),就是可以安全释放的指针。

SP,则被多个线程读写,18、19两行也无法做成原子操作,因此,SPHP有本质的区别,使用SP的程序往往需要搭配使用锁等设施来保证线程安全。

到这里,读者诸君对HP应该有了一定的了解。下集,我们将介绍工业级代码里实现HP的一些思路和技巧,敬请期待。