Top Banner
LockfreePriorityQueue
25

Lockfree Priority Queue

Jun 22, 2015

Download

Technology

Kumazaki Hiroki

Lockfree priority queue
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Lockfree Priority Queue

LockfreePriorityQueue

Page 2: Lockfree Priority Queue

PriorityQueueとは?• 順序関係のあるアイテム群に、新規アイテムを O(log n)で挿入でき、最小の物をO(log n)で取り出せるデータ構造

• ヒープとも言う• 最小の物以外を検索して取り出すコストは高い

Page 3: Lockfree Priority Queue

Lockfree PriorityQueueとは• 複数のスレッドからロック無しで同時に挿入・取り出しを行ってもきちんと動くPriorityQueue

• マルチコアでダイクストラや A*などを走らせるのに便利かも?

Page 4: Lockfree Priority Queue

構造• 2分木を用意する• 木の節ごとに atomicなカウンタが付いている• 木の先端部にアイテムが保持される

0

0 0

0000

1 2 3 4 5 6 7 8

アイテムを保持

Atomicに操作できるカウンタ

Page 5: Lockfree Priority Queue

挿入1. 優先順位の番号に対応する先端部分にデータを保存2. 先端から根に向けて節を辿っていき、節から見て左の枝なら節のカウンタをインクリメント

3. 根に達したら終了

0

0 0

0000

1 2 3 4 5 6 7 8

Page 6: Lockfree Priority Queue

挿入1. 優先順位の番号に対応する先端部分にデータを保存2. 先端から根に向けて節を辿っていき、節から見て左の枝なら節のカウンタをインクリメント

3. 根に達したら終了

0

0 0

0010

1 2 3 4 5 6 7 8

Page 7: Lockfree Priority Queue

挿入1. 優先順位の番号に対応する先端部分にデータを保存2. 先端から根に向けて節を辿っていき、節から見て左の枝なら節のカウンタをインクリメント

3. 根に達したら終了

0

0 0

0010

1 2 3 4 5 6 7 8

Page 8: Lockfree Priority Queue

挿入1. 優先順位の番号に対応する先端部分にデータを保存2. 先端から根に向けて節を辿り、節から見て左の枝なら節のカウンタをインクリメント

3. 根に達したら終了

1

0 0

0010

1 2 3 4 5 6 7 8

Page 9: Lockfree Priority Queue

挿入1. 優先順位の番号に対応する先端部分にデータを保存2. 先端から根に向けて節を辿り、節から見て左の枝なら節のカウンタをインクリメント

3. 根に達したら終了

1

0 0

0010

1 2 3 4 5 6 7 8

完了!

Page 10: Lockfree Priority Queue

挿入操作同士の干渉• カウンタは atomicに操作するため共有しても問題ない• 同一の先端部分に複数のアイテムを保存する場合だけが問題

– 具体的には先端部分に並行 FIFOなどを取り付ければ解決

1

0 0

0010

1 2 3 4 5 6 7 8

Page 11: Lockfree Priority Queue

取り出し• 先ほどの手順でいくつか挿入した状態を例にします• オレンジ色はアイテムが挿入された印• つまり、節のカウンタは、その節より左にいくつアイテムがあるか数えている

2

1 1

0010

1 2 3 4 5 6 7 8

Page 12: Lockfree Priority Queue

取り出し1. 根から順に下に向かって辿る2. 節点のカウンタが 1以上ならデクリメントして左へ辿る3. 節点のカウンタが 0なら何もせず右へ辿る4. 以後繰り返し

2

1 1

0010

1 2 3 4 5 6 7 8

Page 13: Lockfree Priority Queue

取り出し1. 根から順に下に向かって辿る2. 節点のカウンタが 1以上ならデクリメントして左へ辿る3. 節点のカウンタが 0なら何もせず右へ辿る4. 以後繰り返し

1

1 1

0010

1 2 3 4 5 6 7 8

Page 14: Lockfree Priority Queue

取り出し1. 根から順に下に向かって辿る2. 節点のカウンタが 1以上ならデクリメントして左へ辿る3. 節点のカウンタが 0なら何もせず右へ辿る4. 以後繰り返し

1

0 1

0010

1 2 3 4 5 6 7 8

Page 15: Lockfree Priority Queue

取り出し1. 根から順に下に向かって辿る2. 節点のカウンタが 1以上ならデクリメントして左へ辿る3. 節点のカウンタが 0なら何もせず右へ辿る4. 以後繰り返し

1

0 1

0010

1 2 3 4 5 6 7 8

発見!

Page 16: Lockfree Priority Queue

取り出し操作同士の干渉• 節のカウンタを共有しているため同一のアイテムに辿り着く事は起こりえない

• 1つもアイテムが挿入されていない場合にこの図では8の葉に辿り着く。ここだけは特別に取り扱う必要があるが今回は省略

1

0 1

0010

1 2 3 4 5 6 7 8

Page 17: Lockfree Priority Queue

取り出しと挿入の衝突• 1に新規アイテム追加と同時に、取り出し処理も行うとする

1

0 1

0010

1 2 3 4 5 6 7 8

Page 18: Lockfree Priority Queue

取り出しと挿入の衝突• 1に新規アイテム追加と同時に、取り出し処理も行うとする

0

0 1

0011

1 2 3 4 5 6 7 8

inc

dec

Page 19: Lockfree Priority Queue

取り出しと挿入の衝突• 新規に追加した物はインクリメントしながら木を根に向かって辿る

0

1 1

0011

1 2 3 4 5 6 7 8

inc

Page 20: Lockfree Priority Queue

取り出しと挿入の衝突• 挿入処理が根に達して居なくても、カウンタがインクリメントされているならば検索に反映される

• なので取り出し側のスレッドは挿入されたばかりのアイテムに向かってくれる

1

0 1

0011

1 2 3 4 5 6 7 8

inc

dec

Page 21: Lockfree Priority Queue

取り出しと挿入の衝突• 取り出しは他のスレッドが挿入処理の最中であってもカウンタの値を見てその瞬間での最良の判断をしてくれる

• 挿入処理が根に達すれば「節のカウンタはそれより左に有るアイテムの数を表す」というルールは満たされる

• 結論から言うと「上手くすれ違うから大丈夫」

1

0 1

0010

1 2 3 4 5 6 7 8

dec

Page 22: Lockfree Priority Queue

実装• 木を配列で表現、データ実体も配列で保持• Priority Queue のように配列の 2 番目を根として木を表

現。上へ行くときは 2 で割り、下へ行くときは 2 倍した後で、右なら 1 足す。左ならそのまま。

• 同じ場所に複数挿入できるよう、 X[n] は FIFO で実装

h[1]

h[2] h[3]

h[7]h[6]h[5]h[4]

X[0] X[1] X[2] X[3] X[4] X[5] X[6] X[7]

Page 23: Lockfree Priority Queue

実装(挿入処理)void push(const T& item){

int priority = item.GetPriority(); // 0 ~ 8 の値が出るitems[priority].enque(item); // 対応するアイテムリストに挿入int index = priority + MAX; // 木のインデックスを獲得while(index > 1){   // 根に達するまで木を登る

if((index & 1) == 0) {// 左から登るのならatomic_inc(&heap[index / 2]); // カウンタをインク

リメント }

index /= 2; // 1 段上る}

}

案外簡単!

Page 24: Lockfree Priority Queue

実装(取り出し処理)T getMin(){

int index = 1;while(index < MAX){ // 末端に続くまで続ける

int counter = heap[index];if(counter > 0){ // カウンタが大きいなら

if(CAS(&heap[index], counter, counter-1)){index = index / 2; // 左下の枝へ

}// CAS 失敗したらカウンタの取得からやり直し}else{

index = index / 2 + 1; // 右下の枝へ}

}return X[index].deque();

} 案外簡単!

Page 25: Lockfree Priority Queue

まとめ• Lockfree な PriorityQueue について解説• Priority の最大値に制限がある

• 制限が無いバージョンも有るらしいので調べます

• 挿入・取り出しがどんなタイミングでオーバーラップしてもデータ構造が壊れません