二进制搜索树(BST)中的多线程插入

时间:2016-10-23 02:21:34

标签: java multithreading concurrency binary-search-tree

我有一个将多个元素插入BST的任务。必须通过使用多个线程来优化任务。可以启动多少个线程没有限制。

这是我的方法。这是一种理论方法。我没有尝试过它,也不知道它的工作程度。请提出您对此想法的意见。

BST节点看起来像这样:

class BSTNode {
    int val;
    BSTNode left, right;
    boolean leftLock, rightLock;
    Queue<BSTNode> leftQ, rightQ;
}

我没有使用任何Java锁,而是使用两个布尔变量来表示锁定状态。

由于插入任何元素首先要求我们找到相关位置,因此可以并行执行此任务,因为这不会修改树。该任务只能在路径上的节点解锁之前工作。如果某个特定节点被锁定,那么该特定插入线程将被放入sleep()并添加到相应的左或右队列,并在释放锁定时再次唤醒。

另一方面,如果路径上没有节点锁定它们,我们可以继续进行插入。在插入和修改父对应的指针之前,必须在父对象上获取锁。

有人可以就这种实施方法提出自己的看法吗?

3 个答案:

答案 0 :(得分:1)

这实际上是尝试实现自己的锁定的练习。你所做的是创建一个解包锁(boolean,waitingQueue)。但是,这种方法安全工作的唯一方法是外部同步访问'lock'布尔变量和队列。因此,要使此非锁定代码成功运行,您必须使用锁定。

如果你没有使用锁,你会遇到几个与并发有关的问题:

  • 在设置节点中的任何值之间没有发生之前的关系。也就是说,其他任何线程都不会看到任何字段的更新值。仅这一点就可能引发各种麻烦。但是,还有更具体的例子。
  • 没有线程知道布尔锁的赋值是因为它改变了值还是任何其他线程数改变了值(竞争条件)。基本上,没有线程会知道它是否“拥有”锁。使用内置类可以解决这个问题,但还有其他问题,这是不值得坚持的。
  • 检查锁定并将自己插入队列之间还有另一种竞争条件。一个线程可能会看到另一个线程'拥有'锁(给定第二个点是可疑的),并将自己添加到队列中。但是当它将自己添加到队列时,锁可以被解锁,并且如果没有其他线程接触树的那一部分,它可以等待无限时间。
  • 表现不佳。每个线程一次只能在节点上查看。即使您将布尔/队列构造转换为锁定,您也可能无法获得良好的性能,因为即使树上的search()类型操作也需要使用锁来确保正确的内存可见性和之前发生的关系。

如果您想要一个具有线性搜索时间的线程安全,有序,可变容器,请使用ConcurrentSkipListSet&lt; Integer&gt;

答案 1 :(得分:1)

一个有趣的问题。还有一些要点,你必须重新定义你的方法。

  

由于任何元素的插入首先要求我们找到相关的元素   这个任务可以并行执行

  • 其实这是错误的。确实它不会修改树,但由于后台有一些线程试图修改这个树(插入一个节点),你必须在这里应用一个Lock / semaphore。
  • 您必须通过一次操作insert执行finding a suitable place + actual insertion。原因是,在一个线程(比如t1)完成finding a suitable place然后尝试actual insertion的情况下,但是必须坚持,因为另一个线程(比如说t2)正在执行{{1}然后第一个线程(t1)将不得不再次进行位置计算,因为树在第二个线程(t2)actual insertion之后发生了变化。 (我想你得到我说的话)

总而言之,二进制搜索树 的并行插入不会使 受益,因为二进制搜索树插入不能独立于另一个插入执行。

答案 2 :(得分:1)

我试图解释一个问题,这个问题显示了这种方法的基本漏洞。

假设您现在仅支持插入操作,而不支持任何其他操作。以下可能是插入操作的实现:

//Using C
BSTNode* insert(BSTNode* root,int value)
{
1    if(root == NULL){
2       return createNewNode(value);
3    }
4    
5    if(root->data == value){
6        return root;
7    } 
8    else if(root->data > value){
9        while(root->leftLock);
10        if(!root->left){
11            root->leftLock = true;
12            root->left = insert(root->left,value);
13            root->leftLock = false;
14        }
15        else{
16            root->left = insert(root->left,value);
17        }
18    }
19    else{
20        while(root->rightLock);
21        if(!root->right){
22            root->rightLock = true;
23            root->right = insert(root->right,value);
24            root->rightLock = false;
25        }
26        else{
27            root->right = insert(root->right,value);
28        }
29    }
30    
31    return root;
32    
}

在这种方法中,由于只有最后一个节点(叶节点)的子节点在插入值时才会更新,因此我们在更新父节点时(重复返回时)不会进行任何锁定。

我正在避免插入请求排队和仅使用自旋锁来保持它有点简单。不过,我要提出的观点也是如此......

考虑一下这个BST:

    10
   /  \
  5    15
 / \  /  \
2   6 13  20

假设同时调用2个线程t1和t2,试图分别插入值25和26,并且当前位于BSTNode,值为20。 (最右边的节点)。

现在让我们通过线程之间的上下文切换来执行上面的代码:

a. t1:
          1. if(root == NULL)  //not true, will go to line 5.
          //switch

b. t2:
          1. if(root == NULL)  //not true, will go to line 5.
          //switch

c. t1:
          5. if(root->data == value){  //not true, will go to line 8.
          8. else if(root->data > value) //not true, will go to line 19.
          //switch

d. t2:
          5. if(root->data == value){  //not true, will go to line 8.
          //switch

e. t1:
          19    else{
          20        while(root->rightLock);  // lock is not held by anyone, so continue.
          21        if(!root->right){
          //switch
f. t2:
          8. else if(root->data > value) //not true, will go to line 19.
          19    else{
          20        while(root->rightLock);  // lock is not helpd by anyone, so continue.
          21        if(!root->right){
          22            root->rightLock = true;
          //switch

g. t1:
          22            root->rightLock = true;
          23            root->right = insert(root->right,value);
          //switch

h. t2: 
          23            root->right = insert(root->right,value);
          24            root->rightLock = false;
          //switch

假设第23行涵盖该行的完整执行。

正如您在 f g h 一节中所见, t1 t2 正在进入关键部分而不知道彼此的存在。代码不应该允许这样做。

问题是什么?

问题是有一段代码应该一次性执行:

20        while(root->rightLock);
21        if(!root->right){
22            root->rightLock = true;

所以我们可能需要一些硬件控制来制作我们自己的不间断指令,它一起执行上面提到的所有3个任务。