私はライブロックが何であるかを理解していますが、誰かがコードベースの良い例を持っているかどうか疑問に思っていましたか? また、コードベースとは、「2 人が廊下でお互いを乗り越えようとしている」という意味ではありません。もう一度読んだら、昼食を失います。
11 に答える
これは、夫と妻がスープを食べようとしているライブロックの非常に単純な Java の例ですが、2 人の間にスプーン 1 杯しかありません。各配偶者は礼儀正しく、相手がまだ食べていない場合はスプーンを渡します。
public class Livelock {
static class Spoon {
private Diner owner;
public Spoon(Diner d) { owner = d; }
public Diner getOwner() { return owner; }
public synchronized void setOwner(Diner d) { owner = d; }
public synchronized void use() {
System.out.printf("%s has eaten!", owner.name);
}
}
static class Diner {
private String name;
private boolean isHungry;
public Diner(String n) { name = n; isHungry = true; }
public String getName() { return name; }
public boolean isHungry() { return isHungry; }
public void eatWith(Spoon spoon, Diner spouse) {
while (isHungry) {
// Don't have the spoon, so wait patiently for spouse.
if (spoon.owner != this) {
try { Thread.sleep(1); }
catch(InterruptedException e) { continue; }
continue;
}
// If spouse is hungry, insist upon passing the spoon.
if (spouse.isHungry()) {
System.out.printf(
"%s: You eat first my darling %s!%n",
name, spouse.getName());
spoon.setOwner(spouse);
continue;
}
// Spouse wasn't hungry, so finally eat
spoon.use();
isHungry = false;
System.out.printf(
"%s: I am stuffed, my darling %s!%n",
name, spouse.getName());
spoon.setOwner(spouse);
}
}
}
public static void main(String[] args) {
final Diner husband = new Diner("Bob");
final Diner wife = new Diner("Alice");
final Spoon s = new Spoon(husband);
new Thread(new Runnable() {
public void run() { husband.eatWith(s, wife); }
}).start();
new Thread(new Runnable() {
public void run() { wife.eatWith(s, husband); }
}).start();
}
}
プログラムを実行すると、次のようになります。
Bob: You eat first my darling Alice!
Alice: You eat first my darling Bob!
Bob: You eat first my darling Alice!
Alice: You eat first my darling Bob!
Bob: You eat first my darling Alice!
Alice: You eat first my darling Bob!
...
途切れなければ永遠に続きます。アリスとボブの両方が無限ループで最初に行くようにお互いに繰り返し要求しているため、これはライブロックです (つまりlive )。行き詰まりの状況では、Alice と Bob の両方が単に凍結され、お互いが先に進むのを待っているだけです。
Flippant のコメントはさておき、出てくることが知られている 1 つの例は、デッドロック状況を検出して処理しようとするコードです。2 つのスレッドがデッドロックを検出し、互いに「退避」しようとすると、注意を怠ると、常に「退避」してループに陥り、決して前に進むことができなくなります。
「脇に置く」とは、ロックを解放し、他の人にロックを取得させようとすることを意味します。2 つのスレッドがこれを行う状況を想像するかもしれません (疑似コード):
// thread 1
getLocks12(lock1, lock2)
{
lock1.lock();
while (lock2.locked())
{
// attempt to step aside for the other thread
lock1.unlock();
wait();
lock1.lock();
}
lock2.lock();
}
// thread 2
getLocks21(lock1, lock2)
{
lock2.lock();
while (lock1.locked())
{
// attempt to step aside for the other thread
lock2.unlock();
wait();
lock2.lock();
}
lock1.lock();
}
競合状態はさておき、ここにあるのは、両方のスレッドが同時に入ると、先に進まずに内側のループで実行されるという状況です。明らかに、これは単純化された例です。単純な修正は、スレッドが待機する時間にある種のランダム性を加えることです。
適切な修正は、常にロック階層を尊重することです。ロックを取得する順序を選択し、それを守ります。たとえば、両方のスレッドが常に lock2 の前に lock1 を取得する場合、デッドロックの可能性はありません。
承認済みの回答としてマークされた回答がないため、ライブロックの例を作成しようとしました。
オリジナルのプログラムは、マルチスレッドのさまざまな概念を学習するために、2012 年 4 月に私が作成しました。今回はデッドロック、レースコンディション、ライブロックなどを作成するように修正しました。
それでは、まず問題のステートメントを理解しましょう。
クッキーメーカーの問題
ChocoPowederContainer、WheatPowderContainerなど、いくつかの材料コンテナーがあります。CookieMakerは、 Cookieを焼くために材料コンテナからある程度の量の粉末を取り出します。Cookie メーカーが空のコンテナーを検出すると、時間を節約するために別のコンテナーをチェックします。そして、 Fillerが必要なコンテナーを満たすまで待機します。定期的にコンテナをチェックし、コンテナが必要な場合はある程度の量を充填するフィラーがいます。
githubで完全なコードを確認してください。
実装について簡単に説明します。
- Fillerをデーモン スレッドとして起動します。そのため、一定の間隔でコンテナを満たし続けます。コンテナを最初に充填するには、コンテナをロックします -> 粉末が必要かどうかを確認します -> 充填します -> それを待っているすべてのメーカーに信号を送ります -> コンテナのロックを解除します。
- 私はCookieMakerを作成し、最大 8 個のクッキーを並行して焼くことができるように設定しています。そして、クッキーを焼くために 8 つのスレッドを開始します。
- 各メーカー スレッドは、コンテナーからパウダーを取得するために 2 つの呼び出し可能なサブスレッドを作成します。
- サブスレッドはコンテナーをロックし、十分なパウダーがあるかどうかを確認します。そうでない場合は、しばらく待ちます。フィラーがコンテナを満たすと、粉末が取り込まれ、コンテナのロックが解除されます。
- これで、混合物の作成やベーキングなどの他のアクティビティが完了します。
コードを見てみましょう。
CookieMaker.java
private Integer getMaterial(final Ingredient ingredient) throws Exception{
:
container.lock();
while (!container.getIngredient(quantity)) {
container.empty.await(1000, TimeUnit.MILLISECONDS);
//Thread.sleep(500); //For deadlock
}
container.unlock();
:
}
成分コンテナ.java
public boolean getIngredient(int n) throws Exception {
:
lock();
if (quantityHeld >= n) {
TimeUnit.SECONDS.sleep(2);
quantityHeld -= n;
unlock();
return true;
}
unlock();
return false;
}
Fillerがコンテナーに充填されるまで、すべてが正常に実行されます。しかし、フィラーを開始するのを忘れた場合、またはフィラーが予期しない休暇になった場合、サブスレッドは状態を変更し続け、他のメーカーがコンテナーをチェックできるようにします。
また、スレッドの状態とデッドロックを監視するデーモンThreadTracerも作成しました。これはコンソールからの出力です。
2016-09-12 21:31:45.065 :: [Maker_0:WAITING, Maker_1:WAITING, Maker_2:WAITING, Maker_3:WAITING, Maker_4:WAITING, Maker_5:WAITING, Maker_6:WAITING, Maker_7:WAITING, pool-7-thread-1:TIMED_WAITING, pool-7-thread-2:TIMED_WAITING, pool-8-thread-1:TIMED_WAITING, pool-8-thread-2:TIMED_WAITING, pool-6-thread-1:TIMED_WAITING, pool-6-thread-2:TIMED_WAITING, pool-5-thread-1:TIMED_WAITING, pool-5-thread-2:TIMED_WAITING, pool-1-thread-1:TIMED_WAITING, pool-3-thread-1:TIMED_WAITING, pool-2-thread-1:TIMED_WAITING, pool-1-thread-2:TIMED_WAITING, pool-4-thread-1:TIMED_WAITING, pool-4-thread-2:RUNNABLE, pool-3-thread-2:TIMED_WAITING, pool-2-thread-2:TIMED_WAITING]
2016-09-12 21:31:45.065 :: [Maker_0:WAITING, Maker_1:WAITING, Maker_2:WAITING, Maker_3:WAITING, Maker_4:WAITING, Maker_5:WAITING, Maker_6:WAITING, Maker_7:WAITING, pool-7-thread-1:TIMED_WAITING, pool-7-thread-2:TIMED_WAITING, pool-8-thread-1:TIMED_WAITING, pool-8-thread-2:TIMED_WAITING, pool-6-thread-1:TIMED_WAITING, pool-6-thread-2:TIMED_WAITING, pool-5-thread-1:TIMED_WAITING, pool-5-thread-2:TIMED_WAITING, pool-1-thread-1:TIMED_WAITING, pool-3-thread-1:TIMED_WAITING, pool-2-thread-1:TIMED_WAITING, pool-1-thread-2:TIMED_WAITING, pool-4-thread-1:TIMED_WAITING, pool-4-thread-2:TIMED_WAITING, pool-3-thread-2:TIMED_WAITING, pool-2-thread-2:TIMED_WAITING]
WheatPowder Container has 0 only.
2016-09-12 21:31:45.082 :: [Maker_0:WAITING, Maker_1:WAITING, Maker_2:WAITING, Maker_3:WAITING, Maker_4:WAITING, Maker_5:WAITING, Maker_6:WAITING, Maker_7:WAITING, pool-7-thread-1:TIMED_WAITING, pool-7-thread-2:TIMED_WAITING, pool-8-thread-1:TIMED_WAITING, pool-8-thread-2:TIMED_WAITING, pool-6-thread-1:TIMED_WAITING, pool-6-thread-2:TIMED_WAITING, pool-5-thread-1:TIMED_WAITING, pool-5-thread-2:TIMED_WAITING, pool-1-thread-1:TIMED_WAITING, pool-3-thread-1:TIMED_WAITING, pool-2-thread-1:TIMED_WAITING, pool-1-thread-2:TIMED_WAITING, pool-4-thread-1:TIMED_WAITING, pool-4-thread-2:TIMED_WAITING, pool-3-thread-2:TIMED_WAITING, pool-2-thread-2:RUNNABLE]
2016-09-12 21:31:45.082 :: [Maker_0:WAITING, Maker_1:WAITING, Maker_2:WAITING, Maker_3:WAITING, Maker_4:WAITING, Maker_5:WAITING, Maker_6:WAITING, Maker_7:WAITING, pool-7-thread-1:TIMED_WAITING, pool-7-thread-2:TIMED_WAITING, pool-8-thread-1:TIMED_WAITING, pool-8-thread-2:TIMED_WAITING, pool-6-thread-1:TIMED_WAITING, pool-6-thread-2:TIMED_WAITING, pool-5-thread-1:TIMED_WAITING, pool-5-thread-2:TIMED_WAITING, pool-1-thread-1:TIMED_WAITING, pool-3-thread-1:TIMED_WAITING, pool-2-thread-1:TIMED_WAITING, pool-1-thread-2:TIMED_WAITING, pool-4-thread-1:TIMED_WAITING, pool-4-thread-2:TIMED_WAITING, pool-3-thread-2:TIMED_WAITING, pool-2-thread-2:TIMED_WAITING]
サブスレッドとその状態の変更と待機に気付くでしょう。
実際の(正確なコードはありませんが)例は、SQLサーバーのデッドロックを修正するために、2つの競合するプロセスがライブロックし、各プロセスが同じ待機-再試行アルゴリズムを使用して再試行することです。タイミングは運が良かったのですが、EMSトピックに追加されたメッセージ(単一のオブジェクトグラフの更新を複数回保存するなど)に応じて、同様のパフォーマンス特性を持つ別々のマシンでこれが発生し、制御できないことがわかりました。ロックの順序。
この場合の適切な解決策は、競合するコンシューマーを用意することです(無関係なオブジェクトで作業を分割することにより、チェーンの上位で重複処理を防止します)。
あまり望ましくない(OK、ダーティハック)解決策は、タイミングの不運(処理における力の違いの一種)を事前に解消するか、デッドロック後にさまざまなアルゴリズムまたはランダム性の要素を使用して解消することです。ロック取得の順序がプロセスごとに「スティッキー」である可能性があるため、これにはまだ問題がある可能性があります。これには、待機再試行で考慮されていない特定の最小時間がかかります。
さらに別の解決策(少なくともSQL Serverの場合)は、別の分離レベル(スナップショットなど)を試すことです。
jelbourn のコードの C# バージョン:
using System;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
namespace LiveLockExample
{
static class Program
{
public static void Main(string[] args)
{
var husband = new Diner("Bob");
var wife = new Diner("Alice");
var s = new Spoon(husband);
Task.WaitAll(
Task.Run(() => husband.EatWith(s, wife)),
Task.Run(() => wife.EatWith(s, husband))
);
}
public class Spoon
{
public Spoon(Diner diner)
{
Owner = diner;
}
public Diner Owner { get; private set; }
[MethodImpl(MethodImplOptions.Synchronized)]
public void SetOwner(Diner d) { Owner = d; }
[MethodImpl(MethodImplOptions.Synchronized)]
public void Use()
{
Console.WriteLine("{0} has eaten!", Owner.Name);
}
}
public class Diner
{
public Diner(string n)
{
Name = n;
IsHungry = true;
}
public string Name { get; private set; }
private bool IsHungry { get; set; }
public void EatWith(Spoon spoon, Diner spouse)
{
while (IsHungry)
{
// Don't have the spoon, so wait patiently for spouse.
if (spoon.Owner != this)
{
try
{
Thread.Sleep(1);
}
catch (ThreadInterruptedException e)
{
}
continue;
}
// If spouse is hungry, insist upon passing the spoon.
if (spouse.IsHungry)
{
Console.WriteLine("{0}: You eat first my darling {1}!", Name, spouse.Name);
spoon.SetOwner(spouse);
continue;
}
// Spouse wasn't hungry, so finally eat
spoon.Use();
IsHungry = false;
Console.WriteLine("{0}: I am stuffed, my darling {1}!", Name, spouse.Name);
spoon.SetOwner(spouse);
}
}
}
}
}
50 個のプロセス スロットを持つ UNIX システムを考えてみましょう。
10 個のプログラムが実行されており、それぞれが 6 つの (サブ) プロセスを作成する必要があります。
各プロセスが 4 つのプロセスを作成した後、10 個の元のプロセスと 40 個の新しいプロセスがテーブルを使い果たしました。10 個の元のプロセスのそれぞれが、分岐と失敗の無限ループに陥っています。これは、まさにライブロックの状況です。これが発生する可能性は非常に低いですが、発生する可能性があります。
jelbourn のコードの Python バージョン:
import threading
import time
lock = threading.Lock()
class Spoon:
def __init__(self, diner):
self.owner = diner
def setOwner(self, diner):
with lock:
self.owner = diner
def use(self):
with lock:
"{0} has eaten".format(self.owner)
class Diner:
def __init__(self, name):
self.name = name
self.hungry = True
def eatsWith(self, spoon, spouse):
while(self.hungry):
if self != spoon.owner:
time.sleep(1) # blocks thread, not process
continue
if spouse.hungry:
print "{0}: you eat first, {1}".format(self.name, spouse.name)
spoon.setOwner(spouse)
continue
# Spouse was not hungry, eat
spoon.use()
print "{0}: I'm stuffed, {1}".format(self.name, spouse.name)
spoon.setOwner(spouse)
def main():
husband = Diner("Bob")
wife = Diner("Alice")
spoon = Spoon(husband)
t0 = threading.Thread(target=husband.eatsWith, args=(spoon, wife))
t1 = threading.Thread(target=wife.eatsWith, args=(spoon, husband))
t0.start()
t1.start()
t0.join()
t1.join()
if __name__ == "__main__":
main()
ここでの 1 つの例は、時間制限のある tryLock を使用して複数のロックを取得し、それらすべてを取得できない場合は、バックオフして再試行することです。
boolean tryLockAll(Collection<Lock> locks) {
boolean grabbedAllLocks = false;
for(int i=0; i<locks.size(); i++) {
Lock lock = locks.get(i);
if(!lock.tryLock(5, TimeUnit.SECONDS)) {
grabbedAllLocks = false;
// undo the locks I already took in reverse order
for(int j=i-1; j >= 0; j--) {
lock.unlock();
}
}
}
}
多くのスレッドが衝突して一連のロックを取得するのを待っているため、このようなコードには問題があると想像できます。しかし、これが単純な例として私にとって非常に説得力があるかどうかはわかりません。
@jelbourn の回答を修正します。そのうちの 1 人がもう 1 人がお腹が空いたことに気づいたら、スプーンを離して別の通知を待つ必要があるため、ライブロックが発生します。
public class LiveLock {
static class Spoon {
Diner owner;
public String getOwnerName() {
return owner.getName();
}
public void setOwner(Diner diner) {
this.owner = diner;
}
public Spoon(Diner diner) {
this.owner = diner;
}
public void use() {
System.out.println(owner.getName() + " use this spoon and finish eat.");
}
}
static class Diner {
public Diner(boolean isHungry, String name) {
this.isHungry = isHungry;
this.name = name;
}
private boolean isHungry;
private String name;
public String getName() {
return name;
}
public void eatWith(Diner spouse, Spoon sharedSpoon) {
try {
synchronized (sharedSpoon) {
while (isHungry) {
while (!sharedSpoon.getOwnerName().equals(name)) {
sharedSpoon.wait();
//System.out.println("sharedSpoon belongs to" + sharedSpoon.getOwnerName())
}
if (spouse.isHungry) {
System.out.println(spouse.getName() + "is hungry,I should give it to him(her).");
sharedSpoon.setOwner(spouse);
sharedSpoon.notifyAll();
} else {
sharedSpoon.use();
sharedSpoon.setOwner(spouse);
isHungry = false;
}
Thread.sleep(500);
}
}
} catch (InterruptedException e) {
System.out.println(name + " is interrupted.");
}
}
}
public static void main(String[] args) {
final Diner husband = new Diner(true, "husband");
final Diner wife = new Diner(true, "wife");
final Spoon sharedSpoon = new Spoon(wife);
Thread h = new Thread() {
@Override
public void run() {
husband.eatWith(wife, sharedSpoon);
}
};
h.start();
Thread w = new Thread() {
@Override
public void run() {
wife.eatWith(husband, sharedSpoon);
}
};
w.start();
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
h.interrupt();
w.interrupt();
try {
h.join();
w.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
package concurrently.deadlock;
import static java.lang.System.out;
/* This is an example of livelock */
public class Dinner {
public static void main(String[] args) {
Spoon spoon = new Spoon();
Dish dish = new Dish();
new Thread(new Husband(spoon, dish)).start();
new Thread(new Wife(spoon, dish)).start();
}
}
class Spoon {
boolean isLocked;
}
class Dish {
boolean isLocked;
}
class Husband implements Runnable {
Spoon spoon;
Dish dish;
Husband(Spoon spoon, Dish dish) {
this.spoon = spoon;
this.dish = dish;
}
@Override
public void run() {
while (true) {
synchronized (spoon) {
spoon.isLocked = true;
out.println("husband get spoon");
try { Thread.sleep(2000); } catch (InterruptedException e) {}
if (dish.isLocked == true) {
spoon.isLocked = false; // give away spoon
out.println("husband pass away spoon");
continue;
}
synchronized (dish) {
dish.isLocked = true;
out.println("Husband is eating!");
}
dish.isLocked = false;
}
spoon.isLocked = false;
}
}
}
class Wife implements Runnable {
Spoon spoon;
Dish dish;
Wife(Spoon spoon, Dish dish) {
this.spoon = spoon;
this.dish = dish;
}
@Override
public void run() {
while (true) {
synchronized (dish) {
dish.isLocked = true;
out.println("wife get dish");
try { Thread.sleep(2000); } catch (InterruptedException e) {}
if (spoon.isLocked == true) {
dish.isLocked = false; // give away dish
out.println("wife pass away dish");
continue;
}
synchronized (spoon) {
spoon.isLocked = true;
out.println("Wife is eating!");
}
spoon.isLocked = false;
}
dish.isLocked = false;
}
}
}