Что такое синхронизация условия?

Я читал здесь или в другом месте, нет, у вас нет доступа из многопоточности, но никто не говорит, что на самом деле происходит.

Итак, сегодня я увидел (поэтому я отвечаю на этот старый вопрос) приложение, работающее в производстве с марта: 2, поставленные на тот же HashSet (тогда HashMap), вызывают перегрузку процессора (около 100%) и увеличение памяти на 3 ГБ, затем уменьшение на GC. Мы должны перезапустить приложение.

5
задан nbro 8 January 2016 в 19:47
поделиться

2 ответа

Похоже, ваш профессор говорит о многопоточности. Многопоточность позволяет компьютерным программам выполнять сразу несколько задач. Акт запуска нового потока, когда он уже запущен, программисты называют «раскручиванием потока»

. Потоки могут совместно использовать одно и то же пространство памяти. Синхронизация условий (или просто синхронизация) - это любой механизм, который защищает области памяти от одновременного изменения двумя разными потоками.

Допустим, вы ходите по магазинам, а жена оплачивает счета дома. Это наивный пример, и в реальной жизни он так не работает, но он послужит простой иллюстрацией.

Ваша жена оплачивает счет онлайн. В то же время, вы проводите своей кредитной картой в продуктовом магазине. Оба действия включают перевод денег с вашего текущего счета. Чтобы смоделировать эту активность, мы пишем следующий код:

public class MyBanking
{
    static double myAccountBalance;
    //
    public void DebitAccount(double debitAmount)
    {
        Console.Writeline("Your Old Balance is: " + myAccountBalance.ToString());
        Console.Writeline("Your Debit is:       " + debitAmount.ToString());
        myAccountBalance = myAccountBalance - amount;
        Console.Writeline("Your New Balance is: " + myAccountBalance.ToString());
    }
}

Гипотетически ваша жена запускает один экземпляр («копию») этого класса в одном потоке, вы запускаете экземпляр в другом потоке. Переменная myAccountBalance объявлена ​​статической, чтобы ее можно было разделить между обоими запущенными экземплярами (у вас и вашей жены только один текущий счет).

Вы производите дебет, вызывая следующий код:

MyBanking bankingObject = new MyBanking();
bankingObject.DebitAccount(100);

Ваша жена дебетует одновременно:

MyBanking bankingObject = new MyBanking();
bankingObject.DebitAccount(50);

Что произойдет, если ваша цепочка будет прервана цепочкой вашей жены после того, как ваш старый баланс будет напечатан на экране, но до того, как будет напечатан новый баланс? Тема вашей жены списывает средства со счета и возвращает управление вашей цепочке. Ваша жена видит это на экране:

Your Old Balance is: 2000
Your Debit is:       50
Your New Balance Is: 1950

Когда компьютер печатает новый баланс на вашем экране, это будет неправильно, потому что дебет вашей жены также будет посчитан. Вы увидите что-то вроде этого:

Your Old Balance is: 2000
Your Debit is:       100
Your New Balance Is: 1850

Чтобы исправить это, мы окружаем код нашего метода оператором блокировки. Оператор блокировки заставляет все другие потоки ждать завершения нашего экземпляра. Новый код выглядит так:

public class MyBanking
{
    static double myAccountBalance;
    //
    public void DebitAccount(double debitAmount)
    {
        lock (this)
        {
            Console.Writeline("Your Old Balance is: " + myAccountBalance.ToString());
            Console.Writeline("Your Debit is:       " + debitAmount.ToString());
            myAccountBalance = myAccountBalance - amount;
            Console.Writeline("Your New Balance is: " + myAccountBalance.ToString());
        }
    }
}

Теперь поток вашей жены будет ждать завершения выполнения вашего кода в операторе блокировки, прежде чем начнется выполнение кода вашей жены. Ваш новый баланс теперь будет правильным, потому что больше нет возможности, что поток вашей жены изменит баланс, пока вы завершаете ВАШУ транзакцию. На экране вы увидите следующее:

Your Old Balance is: 2000
Your Debit is:       100
Your New Balance Is: 1900

Ваша жена увидит это:

Your Old Balance is: 1900
Your Debit is:       50
Your New Balance Is: 1850

Это синхронизация.

Your Old Balance is: 2000
Your Debit is:       50
Your New Balance Is: 1950

Когда компьютер печатает новый баланс на вашем экране, он будет неправильным, потому что будет также засчитан дебет вашей жены. Вы увидите что-то вроде этого:

Your Old Balance is: 2000
Your Debit is:       100
Your New Balance Is: 1850

Чтобы исправить это, мы окружаем код нашего метода оператором блокировки. Оператор блокировки заставляет все другие потоки ждать завершения нашего экземпляра. Новый код выглядит так:

public class MyBanking
{
    static double myAccountBalance;
    //
    public void DebitAccount(double debitAmount)
    {
        lock (this)
        {
            Console.Writeline("Your Old Balance is: " + myAccountBalance.ToString());
            Console.Writeline("Your Debit is:       " + debitAmount.ToString());
            myAccountBalance = myAccountBalance - amount;
            Console.Writeline("Your New Balance is: " + myAccountBalance.ToString());
        }
    }
}

Теперь поток вашей жены будет ждать завершения выполнения вашего кода в операторе блокировки, прежде чем начнется выполнение кода вашей жены. Ваш новый баланс теперь будет правильным, потому что больше нет возможности, что поток вашей жены изменит баланс, пока вы завершаете ВАШУ транзакцию. На экране вы увидите следующее:

Your Old Balance is: 2000
Your Debit is:       100
Your New Balance Is: 1900

Ваша жена увидит это:

Your Old Balance is: 1900
Your Debit is:       50
Your New Balance Is: 1850

Это синхронизация.

Your Old Balance is: 2000
Your Debit is:       50
Your New Balance Is: 1950

Когда компьютер печатает новый баланс на вашем экране, он будет неправильным, потому что будет также засчитан дебет вашей жены. Вы увидите что-то вроде этого:

Your Old Balance is: 2000
Your Debit is:       100
Your New Balance Is: 1850

Чтобы исправить это, мы окружаем код нашего метода оператором блокировки. Оператор блокировки заставляет все другие потоки ждать завершения нашего экземпляра. Новый код выглядит так:

public class MyBanking
{
    static double myAccountBalance;
    //
    public void DebitAccount(double debitAmount)
    {
        lock (this)
        {
            Console.Writeline("Your Old Balance is: " + myAccountBalance.ToString());
            Console.Writeline("Your Debit is:       " + debitAmount.ToString());
            myAccountBalance = myAccountBalance - amount;
            Console.Writeline("Your New Balance is: " + myAccountBalance.ToString());
        }
    }
}

Теперь поток вашей жены будет ждать завершения выполнения вашего кода в операторе блокировки, прежде чем начнется выполнение кода вашей жены. Ваш новый баланс теперь будет правильным, потому что больше нет возможности, что поток вашей жены изменит баланс, пока вы завершаете ВАШУ транзакцию. На экране вы увидите следующее:

Your Old Balance is: 2000
Your Debit is:       100
Your New Balance Is: 1900

Ваша жена увидит это:

Your Old Balance is: 1900
Your Debit is:       50
Your New Balance Is: 1850

Это синхронизация.

потому что будет засчитан и дебет вашей жены. Вы увидите что-то вроде этого:

Your Old Balance is: 2000
Your Debit is:       100
Your New Balance Is: 1850

Чтобы исправить это, мы окружаем код нашего метода оператором блокировки. Оператор блокировки заставляет все другие потоки ждать завершения нашего экземпляра. Новый код выглядит так:

public class MyBanking
{
    static double myAccountBalance;
    //
    public void DebitAccount(double debitAmount)
    {
        lock (this)
        {
            Console.Writeline("Your Old Balance is: " + myAccountBalance.ToString());
            Console.Writeline("Your Debit is:       " + debitAmount.ToString());
            myAccountBalance = myAccountBalance - amount;
            Console.Writeline("Your New Balance is: " + myAccountBalance.ToString());
        }
    }
}

Теперь поток вашей жены будет ждать завершения выполнения вашего кода в операторе блокировки, прежде чем начнется выполнение кода вашей жены. Ваш новый баланс теперь будет правильным, потому что больше нет возможности, что поток вашей жены изменит баланс, пока вы завершаете ВАШУ транзакцию. На экране вы увидите следующее:

Your Old Balance is: 2000
Your Debit is:       100
Your New Balance Is: 1900

Ваша жена увидит это:

Your Old Balance is: 1900
Your Debit is:       50
Your New Balance Is: 1850

Это синхронизация.

потому что будет засчитан и дебет вашей жены. Вы увидите что-то вроде этого:

Your Old Balance is: 2000
Your Debit is:       100
Your New Balance Is: 1850

Чтобы исправить это, мы окружаем код нашего метода оператором блокировки. Оператор блокировки заставляет все другие потоки ждать завершения нашего экземпляра. Новый код выглядит так:

public class MyBanking
{
    static double myAccountBalance;
    //
    public void DebitAccount(double debitAmount)
    {
        lock (this)
        {
            Console.Writeline("Your Old Balance is: " + myAccountBalance.ToString());
            Console.Writeline("Your Debit is:       " + debitAmount.ToString());
            myAccountBalance = myAccountBalance - amount;
            Console.Writeline("Your New Balance is: " + myAccountBalance.ToString());
        }
    }
}

Теперь поток вашей жены будет ждать завершения выполнения вашего кода в операторе блокировки, прежде чем начнется выполнение кода вашей жены. Ваш новый баланс теперь будет правильным, потому что больше нет возможности, что поток вашей жены изменит баланс, пока вы завершаете ВАШУ транзакцию. На экране вы увидите следующее:

Your Old Balance is: 2000
Your Debit is:       100
Your New Balance Is: 1900

Ваша жена увидит это:

Your Old Balance is: 1900
Your Debit is:       50
Your New Balance Is: 1850

Это синхронизация.

мы окружаем код нашего метода оператором блокировки. Оператор блокировки заставляет все другие потоки ждать завершения нашего экземпляра. Новый код выглядит так:

public class MyBanking
{
    static double myAccountBalance;
    //
    public void DebitAccount(double debitAmount)
    {
        lock (this)
        {
            Console.Writeline("Your Old Balance is: " + myAccountBalance.ToString());
            Console.Writeline("Your Debit is:       " + debitAmount.ToString());
            myAccountBalance = myAccountBalance - amount;
            Console.Writeline("Your New Balance is: " + myAccountBalance.ToString());
        }
    }
}

Теперь поток вашей жены будет ждать завершения выполнения вашего кода в операторе блокировки, прежде чем начнется выполнение кода вашей жены. Ваш новый баланс теперь будет правильным, потому что больше нет возможности, что поток вашей жены изменит баланс, пока вы завершаете ВАШУ транзакцию. На экране вы увидите следующее:

Your Old Balance is: 2000
Your Debit is:       100
Your New Balance Is: 1900

Ваша жена увидит это:

Your Old Balance is: 1900
Your Debit is:       50
Your New Balance Is: 1850

Это синхронизация.

мы окружаем код нашего метода оператором блокировки. Оператор блокировки заставляет все другие потоки ждать завершения нашего экземпляра. Новый код выглядит так:

public class MyBanking
{
    static double myAccountBalance;
    //
    public void DebitAccount(double debitAmount)
    {
        lock (this)
        {
            Console.Writeline("Your Old Balance is: " + myAccountBalance.ToString());
            Console.Writeline("Your Debit is:       " + debitAmount.ToString());
            myAccountBalance = myAccountBalance - amount;
            Console.Writeline("Your New Balance is: " + myAccountBalance.ToString());
        }
    }
}

Теперь поток вашей жены будет ждать завершения выполнения вашего кода в операторе блокировки, прежде чем начнется выполнение кода вашей жены. Ваш новый баланс теперь будет правильным, потому что больше нет возможности, что поток вашей жены изменит баланс, пока вы завершаете ВАШУ транзакцию. На экране вы увидите следующее:

Your Old Balance is: 2000
Your Debit is:       100
Your New Balance Is: 1900

Ваша жена увидит это:

Your Old Balance is: 1900
Your Debit is:       50
Your New Balance Is: 1850

Это синхронизация.

до того, как код вашей жены начнет выполняться. Ваш новый баланс теперь будет правильным, потому что больше нет возможности, что поток вашей жены изменит баланс, пока вы завершаете ВАШУ транзакцию. На экране вы увидите следующее:

Your Old Balance is: 2000
Your Debit is:       100
Your New Balance Is: 1900

Ваша жена увидит это:

Your Old Balance is: 1900
Your Debit is:       50
Your New Balance Is: 1850

Это синхронизация.

до того, как код вашей жены начнет выполняться. Ваш новый баланс теперь будет правильным, потому что больше нет возможности, что поток вашей жены изменит баланс, пока вы завершаете ВАШУ транзакцию. На экране вы увидите следующее:

Your Old Balance is: 2000
Your Debit is:       100
Your New Balance Is: 1900

Ваша жена увидит это:

Your Old Balance is: 1900
Your Debit is:       50
Your New Balance Is: 1850

Это синхронизация.

9
ответ дан 18 December 2019 в 14:51
поделиться

В основном это шаблон проектирования для потоков, которым требуется

a) синхронизировать доступ к ресурсу

b) иногда ждать других потоков, пока не будут выполнены определенные условия

Вы спрашиваете это в контексте C #, .NET поддерживает это с помощью Monitor.Wait и Monitor.Pulse (и с оболочками вокруг различных объектов Win32, таких как WaitEventhandle).

Вот хороший пример очереди на SO.

Основные технические детали выглядят так:

lock(buffer)  // is Monitor.Enter(buffer) ... finally Monitor.Leave(buffer)
{
   while (buffer.Count < 1)
   {
      Monitor.Wait(buffer); 
   }
   ...
}

Обратите внимание на то, что там есть блокировка ожидания. Это похоже на тупик, но Wait снимет блокировку, пока ожидает. Код внутри lock () {} по-прежнему имеет монопольный доступ к буферу при запуске.

И затем другой поток должен сигнализировать, когда он помещает что-то в буфер:

Monitor.Pulse(buffer);
2
ответ дан 18 December 2019 в 14:51
поделиться
Другие вопросы по тегам:

Похожие вопросы: