Хороший приклад лайвока?


141

Я розумію, що таке лайволак, але мені було цікаво, чи хтось має хороший приклад на основі коду? І під кодом я не маю на увазі "двоє людей, які намагаються пройти один одного в коридорі". Якщо я прочитаю це ще раз, я втрачу обід.


96
Як щодо програмного моделювання двох людей, які намагаються подолати один одного в коридорі?
1800 р. ІНФОРМАЦІЯ

36
Проклинаю тебе! Я програв обід!
Алекс Міллер

3
Як не дивно: seuss.wikia.com/wiki/The_Zax
NotMe

Жарт з цікавими людьми
Джорджон

4
Двоє людей, які намагаються пройти повз один одного в коридорі: gist.github.com/deepankarb/d2dd6f21bc49902376e614d3746b8965 : p
iceman

Відповіді:


119

Ось дуже простий приклад Java з коханого, де чоловік і дружина намагаються їсти суп, але мають лише одну ложку між ними. Кожен з подружжя занадто ввічливий і передасть ложку, якщо інший ще не їв.

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();
    }
}

6
Чи getOwnerметод також не повинен бути синхронізований? З ефективної Java " синхронізація не має ефекту, якщо не читати і писати ".
Sanghyun Lee

Чи не повинен він використовувати, Thread.join()а не Thread.sleep()тому, що він хоче чекати, коли подружжя поїсть?
Потіха

що ми повинні зробити, щоб подолати проблему лайвокера в цьому конкретному прикладі?
Тор

1
getOwnerМетод повинен бути синхронізовані , оскільки навіть якщо setOwnerсинхронізується, це не гарантує нитку з допомогою getOwner(або доступ поле ownerбезпосередньо) будуть бачити зміни , зроблені іншим потоком , що виконує setOwner. Це відео пояснює це дуже ретельно: youtube.com/watch?v=WTVooKLLVT8
Тимофей

2
Вам не потрібно використовувати synchronized ключове слово для setOwnerметоду, оскільки читання та запис - це атомна дія для еталонної змінної.
atiqkhaled

75

Flippant коментує в стороні, один приклад, який, як відомо, з'являється в коді, який намагається виявити та вирішити ситуацію з тупиком. Якщо дві нитки виявлять глухий кут і спробують "відійти" один від одного, без опіки вони в кінцевому підсумку застрягнуть у петлі, завжди "відступаючи" і ніколи не встигаючи рухатися вперед.

Під "кроком убік" я маю на увазі, що вони відпустять замок і спробують дозволити іншому придбати його. Ми можемо уявити ситуацію, коли два потоки роблять це (псевдокод):

// 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();
}

Умови перегонів осторонь, що ми маємо тут, це ситуація, коли обидві нитки, якщо вони входять одночасно, в кінцевому підсумку будуть працювати у внутрішньому циклі, не продовжуючи. Очевидно, що це спрощений приклад. Наївним виправленням було б ввести якусь випадковість у проміжок часу, який нитки будуть чекати.

Правильне виправлення - це завжди дотримуватися ієрархії замка . Виберіть замовлення, у якому ви придбаєте замки, і дотримуйтесь цього. Наприклад, якщо обидва потоки завжди набувають lock1 перед lock2, то немає можливості тупикового зв'язку.


Так, я це розумію. Я шукаю приклад фактичного коду такого. Питання полягає в тому, що означає "відхід убік" і як він створює такий сценарій.
Алекс Міллер

Я розумію, що це надуманий приклад, але чи ймовірно, що це може призвести до життєвого шляху? Чи не буде набагато ймовірніше, що врешті-решт відкриється вікно, де одна функція може захопити обидві, через невідповідності часу, коли потоки будуть вголос запускатися та коли вони заплановані.
DubiousPusher

Хоча це не стійкий лайфлэк, оскільки вони, очевидно, вирвуться з нього в підсумку, я думаю, що він досить добре відповідає опису
1800 ІНФОРМАЦІЯ

Прекрасний і змістовний приклад.
alecov

7

Оскільки немає відповіді, позначеної як прийнята відповідь, я спробував створити приклад прямого блокування;

Оригінальна програма була написана мною в квітні 2012 року для вивчення різних концепцій багатопотокової роботи. Цього разу я змінив його, щоб створити тупик, стан перегонів, мешкання тощо.

Тож давайте розберемося спочатку з проблемою;

Проблема створення файлів cookie

Є кілька контейнерів для інгредієнтів: ChocoPowederContainer , WheatPowderContainer . CookieMaker бере певну кількість порошку з контейнерів для інгредієнтів, щоб випікати печиво . Якщо виробник файлів cookie виявить контейнер порожнім, він перевіряє наявність іншого контейнера, щоб заощадити час. І чекає, поки Філлер заповнить необхідний контейнер. Є Filler, який перевіряє контейнер регулярно і заповнює певну кількість, якщо контейнер його потребує.

Перевірте повний код на github ;

Дозвольте коротко пояснити вам реалізацію.

  • Я починаю Філлер як демонова нитка. Таким чином, він буде тримати наповнення контейнерів регулярно. Щоб наповнити контейнер спочатку, він заблокує контейнер -> перевірте, чи потрібен йому якийсь порошок -> заповнить його -> подайте сигнал усім виробникам, які його чекають -> розблокувати контейнер.
  • Я створюю CookieMaker і встановлюю, що він може випікати до 8 файлів cookie паралельно. І я починаю 8 ниток, щоб спекти печиво.
  • Кожна нитка виробника створює 2 дзвінки, що дзвоняться, щоб взяти порошок з контейнерів.
  • sub-thread бере замок на контейнер і перевіряє, чи є в ньому достатня кількість порошку. Якщо ні, зачекайте деякий час. Як тільки наповнювач заповнить контейнер, він бере порошок і розблокує контейнер.
  • Зараз він завершує інші види діяльності: приготування суміші та випічка тощо.

Давайте подивимось у коді:

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();
        :
}

IngredientContainer.java

public boolean getIngredient(int n) throws Exception {
    :
    lock();
    if (quantityHeld >= n) {
        TimeUnit.SECONDS.sleep(2);
        quantityHeld -= n;
        unlock();
        return true;
    }
    unlock();
    return false;
}

Все працює добре, поки Філлер не заповнить контейнери. Але якщо я забуду запустити наповнювач, або наповнювач піде у несподіваний відпустку, підрядки продовжують змінювати стан, щоб дозволити іншим виробникам перейти та перевірити контейнер.

Я також створив демон 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]

Ви помітите, що підрядки та зміна їх стану та очікування.


4

Справжній (хоч і без точного коду) приклад - це два конкуруючих процеси блокування в реальному часі в спробі виправити тупиковий запуск сервера SQL, при цьому кожен процес використовує один і той же алгоритм очікування-повторної спроби. Незважаючи на те, що пощастило в часі, я бачив, що це відбувається на окремих машинах з подібними характеристиками продуктивності у відповідь на повідомлення, додане до теми EMS (наприклад, збереження оновлення одного графіку об'єкта більше одного разу), і не в змозі контролювати замовлення на замовлення.

Хорошим рішенням у цьому випадку було б мати конкуруючих споживачів (не допускати оброблення дублікатів якомога вище в ланцюжку шляхом розподілу робіт на незв'язаних об'єктах).

Менш бажаним (добре, брудним) рішенням є заздалегідь зламати невдачу в часі (вид різниці сили в обробці) або зламати її після глухого кута, використовуючи різні алгоритми або якийсь елемент випадковості. Це все ще може мати проблеми, оскільки можливе замовлення на замовлення є "липким" для кожного процесу, і це займає певний мінімум часу, який не враховується в повторі спроби очікування.

Ще одне рішення (принаймні для SQL Server) - спробувати інший рівень ізоляції (наприклад, знімок).


2

Я зашифрував приклад двох осіб, які проходили в коридорі. Дві нитки будуть уникати один одного, як тільки вони зрозуміють, що їх напрямки однакові.

public class LiveLock {
    public static void main(String[] args) throws InterruptedException {
        Object left = new Object();
        Object right = new Object();
        Pedestrian one = new Pedestrian(left, right, 0); //one's left is one's left
        Pedestrian two = new Pedestrian(right, left, 1); //one's left is two's right, so have to swap order
        one.setOther(two);
        two.setOther(one);
        one.start();
        two.start();
    }
}

class Pedestrian extends Thread {
    private Object l;
    private Object r;
    private Pedestrian other;
    private Object current;

    Pedestrian (Object left, Object right, int firstDirection) {
        l = left;
        r = right;
        if (firstDirection==0) {
            current = l;
        }
        else {
            current = r;
        }
    }

    void setOther(Pedestrian otherP) {
        other = otherP;
    }

    Object getDirection() {
        return current;
    }

    Object getOppositeDirection() {
        if (current.equals(l)) {
            return r;
        }
        else {
            return l;
        }
    }

    void switchDirection() throws InterruptedException {
        Thread.sleep(100);
        current = getOppositeDirection();
        System.out.println(Thread.currentThread().getName() + " is stepping aside.");
    }

    public void run() {
        while (getDirection().equals(other.getDirection())) {
            try {
                switchDirection();
                Thread.sleep(100);
            } catch (InterruptedException e) {}
        }
    }
} 

2

C # версія коду jelbourn:

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);
                }
            }
        }
    }
}

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();
      }
    }
  }
}

Я можу уявити, що такий код буде проблематичним, оскільки у вас стикається багато ниток і чекаєте отримання набору замків. Але я не впевнений, що це дуже переконливо для мене як простий приклад.


1
щоб це було мелодією, вам знадобиться ще одна нитка, щоб придбати ці замки в іншому порядку. Якщо всі потоки використовують tryLockAll()із замками locksу тому самому порядку, немає жодної послідовності.
JaviMerino

0

Версія Python з кодом jelbourn:

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()

Помилки: у режимі use () друк не використовується і - що ще важливіше - голодний прапор не встановлено на значення False.
НДР

0

Я змінюю відповідь @jelbourn. Коли один із них помічає, що другий голодний, він (вона) повинен відпустити ложку і чекати, коли інше сповістить, тому трапляється живіт.

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();
        }
    }
}

0
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;
        }
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.