Czy samo get jest thread-safe?

0

Trochę się pogubiłem w tym net.

public class Temp
    {
        public void SetProperty(bool value)
        {
            lock (locker) Property = value;
        }

        internal bool Property { get; private set; }

        private readonly object locker = new object();
    }

Czy metoda SetProperty i właściwość Property są bezpieczne między wątkami? Podobno value typy nie są, a referencyjne są - ale nie wiem. Czy ktoś ma to poukładane obecnie w głowie aby wyjaśnić? Czy podany kod jest bezpieczny.

2

Jest o ile NIGDY nie będziesz robił: Property = value bez lockera. Czyli można powiedzieć, że nie jest, bo prędzej czy później ktoś to zrobi :)

To powinno być tak:

public bool Property
{
    get { return property; }
    set
    {
        lock(locker)
        {
            property = value;
        }
    }
}

Generalnie wartość zmiennej możesz pobierać bezpiecznie między wątkami bez żadnej synchronizacji. To jest tylko odczyt, który niczego nie zmienia. A nigdy nie trafisz w sytuację, że np. odczytasz coś w będące w trakcie zmiany. W sensie, że np. zmieniły się już 4 z 8 bitów odczytywanej zmiennej. Masz to zagwarantowane.

Problem oczywiście jest przy zapisie, gdzie synchronizacja już musi być.

1

Nie jest. Nie dostaniesz tutaj word tearing, ale nie ma żadnej gwarancji, że odczytasz najnowszą wartość. W getterze też powinien być lock, a dokładniej read barrier.

Podobno value typy nie są, a referencyjne są - ale nie wiem.

To zależy od rozmiaru zmiennej, typy referencyjne są, bo przy ich modyfikacji zmieniasz jedynie referencję, jej atomowy zapis masz gwarantowany przez CLR. W typach wartościowych będzie podobnie, o ile da się go zapisać atomowo, to będzie okej. Niemniej dopóki nie rozumiesz takich rzeczy do samego spodu, dopóty nie baw się w usuwanie blokad, bo w końcu napytasz sobie biedy, to nie jest łatwy temat, więc nie ma co ryzykować w kodzie produkcyjnym.

2

Przykład pokazujący niebezpieczeństwo braku bariery jeśli mamy tylko lock na zapisie, brak locka na odczycie -> brak bariery. W trybie release wątek się nigdy nie zakończy, ale wystarczy że użyjemy czegoś co tą barierę wygeneruje np (Thread.MemoryBarrier();) i program będzie działał tak jak oczekujemy.

using System;
using System.Threading;

namespace ConsoleApp2
{
    class Program
    {
        static readonly object lockObj = new object();

        static void Main()
        {
            bool complete = false;
            var t = new Thread(() =>
            {
                bool toggle = false;
                
                while (!complete)
                {
                    //Thread.MemoryBarrier();
                    toggle = !toggle;
                }            
            });
            t.Start();
            Thread.Sleep(1000);
            lock (lockObj)
            {
                 complete = true;
            }
            t.Join();       
        }
    }
}

1 użytkowników online, w tym zalogowanych: 0, gości: 1