Vraag Onder C # is Int64 gebruik op een 32 bit-processor gevaarlijk


Ik lees in de MS-documentatie dat het toewijzen van een 64-bits waarde aan een 32-bits Intel-computer geen atomaire bewerking is; dat wil zeggen, de bewerking is niet thread safe. Dit betekent dat als twee mensen tegelijkertijd een waarde aan een statisch toewijzen Int64 veld, kan de eindwaarde van het veld niet worden voorspeld.

Drie deelvraag:

  • Is dit echt waar?
  • Is dit iets waar ik me zorgen over maak in de echte wereld?
  • Als mijn toepassing multi-threaded is, moet ik echt al mijn gegevens omsluiten Int64 opdrachten met sluitcode?

17
2018-02-27 18:44


oorsprong


antwoorden:


Dit gaat niet over elke variabele die je tegenkomt. Als een variabele wordt gebruikt als een gedeelde status of iets (inclusief, maar niet beperkt tot sommige  static velden), moet u dit probleem oplossen. Het is volledig non-issue voor lokale variabelen die niet worden opgehesen als gevolg van gesloten te zijn in een afsluiting of een iteratortransformatie en worden gebruikt door een enkele functie (en dus een enkele thread) per keer.


18
2018-02-27 18:48



Zelfs als het schrijft waren atomisch, de kans groot dat je nog steeds een slot moet afsluiten wanneer je de variabele benadert. Als je dat niet deed, zou je in ieder geval de variabele moeten maken volatile om ervoor te zorgen dat alle threads de nieuwe waarde zagen de volgende keer dat ze de variabele lezen (wat bijna altijd is wat je wilt). Dat laat je atomaire, vluchtige sets doen - maar zodra je iets interessants wilt doen, zoals het toevoegen van 5, ben je weer terug aan het vergrendelen.

Lock-free programmeren is heel, heel moeilijk om goed te krijgen. Je moet weten precies wat je aan het doen bent, en houd de complexiteit bij tot een zo klein mogelijk codetje. Persoonlijk probeer ik het zelfs zelden om het te proberen, behalve voor zeer bekende patronen zoals het gebruik van een statische initializer om een ​​verzameling te initialiseren en vervolgens uit de verzameling te lezen zonder te vergrendelen.

De ... gebruiken Interlocked klas kan in sommige situaties helpen, maar het is bijna altijd een stuk eenvoudiger om gewoon een slot te sluiten. Niet-betwiste sloten zijn "vrij goedkoop" (toegegeven dat ze duur worden met meer kernen, maar dat doet alles) - rommel niet met slotvrije code totdat je goed bewijs hebt dat het echt een groot verschil gaat maken.


12
2018-02-27 19:40



MSDN:

Het toewijzen van een instantie van dit type is   niet thread veilig op alle hardware   platforms omdat het binaire bestand   weergave van die instantie zou kunnen   te groot zijn om in een single toe te wijzen   atomaire operatie.

Maar ook:

Zoals bij elk ander type, lezen en   schrijven naar een gedeelde variabele dat   bevat een instantie van dit type moet   worden beschermd door een slot om te garanderen   draadveiligheid.


7
2018-02-27 18:51



Als u een gedeelde variabele hebt (bijvoorbeeld als statisch veld van een klasse of als veld van een gedeeld object) en dat veld of object dwars over de draad wordt gebruikt, dan moet u ervoor zorgen die toegang tot die variabele wordt beschermd via een atomaire operatie. De x86-processor heeft intrinsieke functies om ervoor te zorgen dat dit gebeurt en deze voorziening wordt zichtbaar via de methoden System.Threading.Interlocked class.

Bijvoorbeeld:

class Program
{
    public static Int64 UnsafeSharedData;
    public static Int64 SafeSharedData;

    static void Main(string[] args)
    {
        Action<Int32> unsafeAdd = i => { UnsafeSharedData += i; };
        Action<Int32> unsafeSubtract = i => { UnsafeSharedData -= i; };
        Action<Int32> safeAdd = i => Interlocked.Add(ref SafeSharedData, i);
        Action<Int32> safeSubtract = i => Interlocked.Add(ref SafeSharedData, -i);

        WaitHandle[] waitHandles = new[] { new ManualResetEvent(false), 
                                           new ManualResetEvent(false),
                                           new ManualResetEvent(false),
                                           new ManualResetEvent(false)};

        Action<Action<Int32>, Object> compute = (a, e) =>
                                            {
                                                for (Int32 i = 1; i <= 1000000; i++)
                                                {
                                                    a(i);
                                                    Thread.Sleep(0);
                                                }

                                                ((ManualResetEvent) e).Set();
                                            };

        ThreadPool.QueueUserWorkItem(o => compute(unsafeAdd, o), waitHandles[0]);
        ThreadPool.QueueUserWorkItem(o => compute(unsafeSubtract, o), waitHandles[1]);
        ThreadPool.QueueUserWorkItem(o => compute(safeAdd, o), waitHandles[2]);
        ThreadPool.QueueUserWorkItem(o => compute(safeSubtract, o), waitHandles[3]);

        WaitHandle.WaitAll(waitHandles);
        Debug.WriteLine("Unsafe: " + UnsafeSharedData);
        Debug.WriteLine("Safe: " + SafeSharedData);
    }
}

De resultaten:

Onveilig: -24050275641    Veilig: 0

Aan een interessante kanttekening, ik heb dit in de x64-modus op Vista 64 uitgevoerd. Dit geeft aan dat 64-bits velden worden behandeld als 32-bits velden volgens de runtime, dat wil zeggen dat 64-bits bewerkingen niet-atomisch zijn. Iedereen weet of dit een CLR-probleem of een x64-probleem is?


2
2018-02-27 19:35



Op een 32-bits x86-platform is het grootste atomische geheugengeheugen 32 bits.

Dit betekent dat als iets schrijft naar of leest van een 64-bit-groottevariabele, het mogelijk is dat dat lezen / schrijven tijdens de uitvoering wordt uitgesloten.

  • U begint bijvoorbeeld een waarde toe te wijzen aan een 64-bits variabele.
  • Nadat de eerste 32 bits zijn geschreven, besluit het besturingssysteem dat een ander proces CPU-tijd krijgt.
  • In het volgende proces wordt geprobeerd de variabele te lezen waar u aan toewees.

Dat is slechts één mogelijke raceconditie met 64-bits toewijzing op een 32-bits platform.

Zelfs met een 32-bits variabele kunnen er raceomstandigheden zijn met lezen en schrijven, daarom moet elke gedeelde variabele op de een of andere manier worden gesynchroniseerd om deze raceomstandigheden op te lossen.


1
2018-02-27 18:53



Is dit echt waar? Ja, zo blijkt. Als uw registers slechts 32 bits bevatten en u moet een 64-bits waarde opslaan op een bepaalde geheugenlocatie, dan zijn er twee laadbewerkingen en twee winkelbewerkingen nodig. Als uw proces wordt onderbroken door een ander proces tussen deze twee load / stores, kan het andere proces de helft van uw gegevens corrumperen! Raar maar waar. Dit is een probleem geweest bij elke ooit gebouwde processor - als uw datatype langer is dan uw registers, zult u concurrency-problemen hebben.

Is dit iets waar ik me zorgen over maak in de echte wereld? Ja en nee. Aangezien bijna alle moderne programmering een eigen adresruimte krijgt, hoeft u zich hier alleen zorgen over te maken als u met meerdere threads programmeert.

Als mijn toepassing multi-threaded is, moet ik dan echt al mijn Int64-toewijzingen met een vergrendelingscode omringen? Helaas, ja als je technisch wilt worden. In de praktijk is het meestal gemakkelijker om een ​​Mutex of Semaphore te gebruiken rond grotere codeblokken dan om elke individuele setinstructie over globaal toegankelijke variabelen te vergrendelen.


0
2018-02-27 18:53