Зачем использовать SyncLocks in.NET для простых операций, когда блокируемый класс доступен?


Я занимался простой многопоточностью в VB.NET на некоторое время, и только что получил в свой первый большой многопоточный проект. Я всегда все делал с помощью оператора Synclock, потому что не думал, что есть лучший способ.

Я только что узнал о классе Interlocked - это выглядит так, как будто все это:

Private SomeInt as Integer
Private SomeInt_LockObject as New Object

Public Sub IntrementSomeInt
    Synclock SomeInt_LockObject
        SomeInt += 1
    End Synclock
End Sub

Можно заменить одним утверждением:

Interlocked.Increment(SomeInt)

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

Есть ли причина, по которой я бы использовал свою собственную блокировку, используя выделенные объекты блокировки, когда я могу сделать то же самое, используя методы Interlocked?

5 8

5 ответов:

Вы правы; Interlocked следует использовать здесь, и будет быстрее, чем SyncLock.
Однако класс Interlocked не является хорошо известным.

Однако бывают ситуации, когда нужно использовать SyncLock и Interlocked не поможет.

Это потому, что никто об этом не знает. Распространить слово!

Короткий ответ заключается в том, что использование блокировки Monitor (SyncLock в VB и lock { } В C#) не только гарантирует, что только один поток за один раз может получить доступ к переменной (или, в строгом смысле, только один поток за один раз может получить блокировку объекта блокировки), но и создает барьер памяти, необходимый для обеспечения того, чтобы чтение переменной не оптимизировалось.

Если вы никогда просто не считываете значение переменной (другими словами, вся ваша работа выполняется через вызовы Interlocked), то ты будешь в порядке. Однако, если вам нужно иметь возможность выполнить обычное чтение переменной, то ситуация сложнее. Безблокировочные операции чтения / записи обычно выполняются в C# с помощью ключевого слова volatile. Это позволяет компилятору считывать значение переменной везде, где она используется, вместо того, чтобы оптимизировать любое из этих считываний в локальный кэш. К сожалению, нет никакого эквивалента в VB.NET так что вам придется использовать что-то другое.

Принятый ответ на это вопрос должен предоставить дополнительную информацию о том, что вы можете сделать. Короче говоря, большинство людей используют SyncLock в VB.NET потому что это проще и менее сложно, чем логика, необходимая для этого SyncLock.

Однажды я прочитал очень хорошее объяснение так называемых неатомных и атомарных (в VB: взаимосвязанных) операций и попытаюсь подвести итог.

Normal "non-atomic" operations consist of several steps 

- > другие потоки могут работать между этими стрепами

"Atomic" operations consist of one only one step 

-> другие потоки не могут выполнять работу, пока обрабатываются атомарные операции, атомарные операции всегда обрабатываются как целое

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

Тем не менее комбинация этих threadsafe команд может быть небезопасной, так как между атомарными операциями могут возникать условия гонки.

Поэтому, если вы хотите, например, сравнить 2 переменные, а затем увеличить меньшую, это не является потокобезопасным, даже если отдельные операции для них самих (блокируются.сравните, сцепились.прирост). Здесь вам еще придется попользоваться синклокс.

Кроме этого ограничения нет никакой" скрытой плохой стороны " блокировки.

Один пример для условия гонки с a = 5:

 Thread1: a+=1
 Thread2: a+=2    
 --> supposed to be 8, but can be only 6 or 7,
 but can also be 6 or 7 depending on which thread wins the race

Вариант 1:

T1 step 1: read 5
T1 step 2: add 1 = 6
T1 step 3: write 6
T2 step 1: read 6
T2 step 2: add 2 = 8
T2 step 3: write 8
--> is 8 as supposed

Или Вариант 2:

T1 step 1: read 5
T2 step 1: read 5
T1 step 2: add 1 = 6
T2 step 2: add 2 = 7
T2 step 3: write 7
T1 step 3: write 6
--> is only 6

Или Вариант 3:

T1 step 1: read 5
T2 step 1: read 5
T1 step 2: add 1 = 6
T2 step 2: add 2 = 7
T1 step 3: write 6
T2 step 3: write 7
--> is only 7

С блокировкой.инкремент:

Вариант 1:

T1 step 1: read 5, add 1, write 6
T2 step 1: read 6, add 2, write 8

Или Вариант 2:

T2 step 1: read 5, add 2, write 7
T1 step 1: read 7, add 1, write 8

-> во всех случаях a = 8, как предполагается, threadsafe решение

Все вопросы, которые были размещены здесь, можно решить, применив это простой пример к сомнительному коду.

Надеюсь, это поможет другим людям, которые гуглят эту тему. Янис

Interlocked ограничивается простыми операциями над целочисленными, длинными и булевыми и тому подобными.

Если вы хотите добавить элемент в общий список(из T), например, вам все равно понадобится SynClock.