C # потоки и синхронизация - PullRequest
       3

C # потоки и синхронизация

4 голосов
/ 26 января 2011

У меня есть личное статическое поле, которое я использую для синхронизации (блокировка). Теперь у меня есть две функции, которые я не хочу выполнять одновременно. Итак, я сделал это:

public class Synchronization
{
    private static object _lock = new object();

    public void MethodA()
    {
        lock (_lock)
        {
            Console.WriteLine("I shouldn't execute with MethodB");
        }
    }

    public void MethodB()
    {
        lock (_lock)
        {
            Console.WriteLine("I shouldn't execute with MethodA");
        }
    }
}

Я знаю, что блокировка на объекте предотвратит параллельное выполнение одной функции, но будет ли работать то же самое, если я использую один и тот же объект блокировки в разных методах, которые выполняются одновременно? Проще говоря, может ли другой поток получить блокировку для уже заблокированного объекта в другой функции?

Ответы [ 5 ]

5 голосов
/ 26 января 2011

Только один поток одновременно может получить блокировку, поэтому это состояние является исключительным для всех потоков в одном экземпляре блокировки.Таким образом, в вашем примере, только один метод тела может выполняться в любой момент времени для всех экземпляров класса Synchronization, поскольку ваша блокировка статическая.Если вам нужна блокировка для каждого экземпляра вашего класса, не помечайте объект блокировки как статический.

Ваши предположения относительно синхронизации верны.

Обратите внимание, что вы должны пометить объект блокировки readonly для полностью водонепроницаемого раствора.Поскольку код не изменен, возможно, что объект блокировки будет переназначен и нарушит семантику блокировки, например:

public class Synchronization
{
    private static object _lock = new object();

    public void MethodA()
    {
        lock (_lock)
        {
            Console.WriteLine("I shouldn't execute with MethodB");
        }
    }

    public void MethodB()
    {
        //This shouldn't be allowed!
        _lock = new object();

        lock (_lock)
        {
            Console.WriteLine("I shouldn't execute with MethodA");
        }
    }
}

Объект блокировки должен быть помечен как readonly, то есть:

private static readonly object _lock = new object();
2 голосов
/ 26 января 2011

Я полагаю, вы правильно прочитали Заявление о блокировке в MSDN.

1 голос
/ 26 января 2011

Во-первых, _lock не должен быть статическим.Или вы хотите, чтобы несколько экземпляров объекта блокировали друг друга?Во-вторых, у вас должен быть только один синхронизированный метод в классе.Более того, вам следует избегать зависимостей между синхронизированными методами в вашем классе.В противном случае вы рискуете, что вызывающая сторона ваших методов сделает это неправильно и получит неожиданное поведение.

Рассмотрим, например, этот код:

class Synchronized
{
    object lockObj = new object();
    int counter = 100;

    public void Decrement()
    {
        lock (this.lockObj)
        {
            this.counter--;
        }
    }

    public int IsZero()
    {
        lock (this.lockObj)
        {
            return this.counter == 0;
        }
    }
}

Что теперь делатьс совместно используемым экземпляром Synchronized?

Использовать это так

while (!synchronized.IsZero())
{
    synchronized.Decrement();
}

Теперь поток 1 вызывает Decrement, счетчик получает значение 0, а поток 2 немедленно вызывает Decrement, потому что он ожидал блокировки в методе Decrement, а не метод IsZero.Счетчик теперь равен -1, а цикл бесконечен.

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

Это должно быть примерно так:

class Synchronized
{
    object lockObj = new object();
    int counter = 100;

    public bool IfNotZeroDecrement()
    {
        lock (this.lockObj)
        {
            if (this.counter > 0)
                this.counter--;

            return this.counter > 0;
        }
    }    
}

/// Usage:
while (synchronized.IfZeroDecrement())
{
}
1 голос
/ 26 января 2011

Блокировки предоставляются на основе объекта, который является целью блокировки, а не метода, в котором выполняется оператор lock. Таким образом, в вашем случае несколько потоков могут вводить различные методы, но только один поток может выполнять любой код, который находится внутри оператора lock.

1 голос
/ 26 января 2011

Вы делаете это правильно. Вы создали два критических раздела, которые не будут введены одновременно.

Таким образом, MethodA и MethodB не будут «активными» одновременно. Кроме того, одновременно будет активен только один метод A (и метод B).

Это действительно для всех создаваемых вами объектов. Я имею в виду: только один поток будет в любом MethodA или MethodB из любого объекта. Если вы хотите, чтобы блокировка происходила только внутри одного объекта, вы можете сделать объект _lock не статичным.

...