4

操作が必要な行のリストを持つデータベースがあります。次のようになります。

id       remaining        delivered   locked
============================================
1        10               24          f 
2        6                0           f
3        0                14          f

私はRubyでDataMapperを使用していますが、実際には、これは私が使用している正確な実装に固有のものではない一般的なプログラミングの質問だと思います...

次のようなことを行う一連のワーカー スレッドを作成しています (疑似ルビー コード):

while true do
  t = any_row_in_database_where_remaining_greater_than_zero_and_unlocked
  t.lock   # update database to set locked = true
  t.do_some_stuff
  t.delivered += 1
  t.remaining -= 1
  t.unlock
end

もちろん、問題は、これらのスレッドが互いに競合し、全体が実際にはスレッド セーフではないことです。while ループの最初の行は、ロックされる機会を得る前に、複数のスレッドで同じ行を簡単に引き出すことができます。

1 つのスレッドが同時に 1 つの行でのみ動作していることを確認する必要があります。

これを行う最善の方法は何ですか?

4

2 に答える 2

6

重要なステップは、ロックされていない行をデータベースから選択し、ロック済みとしてマークするときです。それを安全に行うことができれば、あとはすべて問題ありません。

これを安全にすることができると私が知っている2つの方法は、悲観的ロックと楽観的ロックです。どちらも、並行性に関してはデータベースを最終的な保証人として依存しています。

悲観的ロック

悲観的ロックとは、操作する行を選択するときに事前にロックを取得して、他のユーザーがそれらを読み取れないようにすることを意味します。何かのようなもの

SELECT * from some_table WHERE ... FOR UPDATE

mysql と postgres (およびおそらく他のもの) で動作し、データベースへの他の接続が返された行を読み取るのを防ぎます (ロックの粒度は、使用するエンジン、インデックスなどによって異なります。データベースのドキュメントを確認してください)。同時実行性の問題が発生し、予防的にロックを取得すると想定しているため、悲観的と呼ばれます。これは、必要でない場合でもロックのコストを負担することを意味し、ロックの粒度によっては同時実行性が低下する可能性があります。

楽観的ロック

楽観的ロックとは、ほとんどの場合同時更新が行われないため、悲観的ロックの負担を回避する手法を指します (行を読み取った直後にロック フラグを true に設定して行を更新する場合)。 、ウィンドウは比較的小さい)。私の知る限り、これは一度に1行ずつ更新する場合にのみ機能します

最初にテーブルに整数列lock_versionを追加します。テーブルを更新するたびに、lock_version行っている他の更新とともに 1 ずつ増加します。現在が 3 であると仮定しlock_versionます。更新するときは、更新クエリを次のように変更します。

update some_table set ... where id=12345 and lock_version = 3

更新された行数を確認します (db ドライバーがこれを返します)。これで 1 行が更新された場合、すべてが正常であることがわかります。これで 0 行が更新された場合は、目的の行が削除されたか、そのロック バージョンが変更されているため、プロセスの手順 1 に戻り、作業する新しい行を検索します。

私はデータマッパーのユーザーではないので、それ/プラグインがこれらのアプローチをサポートするかどうかはわかりません。Active Record は両方をサポートしているため、データ マッパーがサポートしていない場合は、そこからインスピレーションを得ることができます。

于 2012-04-14T18:10:43.070 に答える
1

私は:を使用しMutexます

# outside your threads
worker_updater = Mutex.new

# inside each thread's updater
while true
  worker_updater.synchronize do
    # your code here
  end
  sleep 0.1 # Slow down there, mister!
end

これにより、一度に1つのスレッドのみがにコードを入力できることが保証されますsynchronize。最適なパフォーマンスを得るには、コードのどの部分をスレッドセーフにする必要があるか(最初の2行?)を検討し、その部分のみをミューテックスでラップします。

于 2012-04-15T14:10:26.873 に答える