Odpowiedzi:
Zgodnie z zaleceniami innych, Interlocked.Increment
będą miały lepszą wydajność niż lock()
. Wystarczy spojrzeć na IL i Assembly, gdzie zobaczysz, że Increment
zmienia się to w instrukcję „bus lock”, a jej zmienna jest bezpośrednio zwiększana (x86) lub „dodawana” do (x64).
To polecenie „bus lock” blokuje magistralę, aby uniemożliwić innemu procesorowi dostęp do magistrali, podczas gdy wywołujący procesor wykonuje swoją operację. Teraz spójrz na lock()
IL instrukcji C # . Tutaj zobaczysz wezwania Monitor
do rozpoczęcia lub zakończenia sekcji.
Innymi słowy, lock()
instrukcja .Net robi o wiele więcej niż .Net Interlocked.Increment
.
Więc jeśli wszystko, co chcesz zrobić, to inkrementować zmienną, Interlock.Increment
będzie szybsza. Przejrzyj wszystkie metody Interlocked, aby zobaczyć różne dostępne operacje atomowe i znaleźć te, które odpowiadają Twoim potrzebom. Użyj, lock()
gdy chcesz wykonywać bardziej złożone rzeczy, takie jak wiele powiązanych ze sobą przyrostów / dekrementów lub serializować dostęp do zasobów, które są bardziej złożone niż liczby całkowite.
Sugeruję użycie wbudowanego inkrementacji blokady .NET w bibliotece System.Threading.
Poniższy kod zwiększy długość zmiennej przez odniesienie i jest całkowicie bezpieczny dla wątków:
Interlocked.Increment(ref myNum);
Źródło: http://msdn.microsoft.com/en-us/library/dd78zt0c.aspx
Spróbuj z Interlocked.Increment
Jak już wspomniano, użyj Interlocked.Increment
Przykład kodu z MS:
Poniższy przykład określa, ile liczb losowych z zakresu od 0 do 1000 jest wymaganych do wygenerowania 1000 liczb losowych z wartością punktu środkowego. Aby śledzić liczbę wartości punktu środkowego, zmienna midpointCount jest ustawiana na 0 i zwiększana za każdym razem, gdy generator liczb losowych zwraca wartość punktu środkowego, aż osiągnie 10000. Ponieważ trzy wątki generują liczby losowe, metoda Increment (Int32) jest wywoływana w celu zapewnienia, że wiele wątków nie aktualizuje współbieżnie midpointCount. Należy zauważyć, że blokada jest również używana do ochrony generatora liczb losowych i że obiekt CountdownEvent jest używany w celu zapewnienia, że metoda Main nie zakończy wykonywania przed trzema wątkami.
using System;
using System.Threading;
public class Example
{
const int LOWERBOUND = 0;
const int UPPERBOUND = 1001;
static Object lockObj = new Object();
static Random rnd = new Random();
static CountdownEvent cte;
static int totalCount = 0;
static int totalMidpoint = 0;
static int midpointCount = 0;
public static void Main()
{
cte = new CountdownEvent(1);
// Start three threads.
for (int ctr = 0; ctr <= 2; ctr++) {
cte.AddCount();
Thread th = new Thread(GenerateNumbers);
th.Name = "Thread" + ctr.ToString();
th.Start();
}
cte.Signal();
cte.Wait();
Console.WriteLine();
Console.WriteLine("Total midpoint values: {0,10:N0} ({1:P3})",
totalMidpoint, totalMidpoint/((double)totalCount));
Console.WriteLine("Total number of values: {0,10:N0}",
totalCount);
}
private static void GenerateNumbers()
{
int midpoint = (UPPERBOUND - LOWERBOUND) / 2;
int value = 0;
int total = 0;
int midpt = 0;
do {
lock (lockObj) {
value = rnd.Next(LOWERBOUND, UPPERBOUND);
}
if (value == midpoint) {
Interlocked.Increment(ref midpointCount);
midpt++;
}
total++;
} while (midpointCount < 10000);
Interlocked.Add(ref totalCount, total);
Interlocked.Add(ref totalMidpoint, midpt);
string s = String.Format("Thread {0}:\n", Thread.CurrentThread.Name) +
String.Format(" Random Numbers: {0:N0}\n", total) +
String.Format(" Midpoint values: {0:N0} ({1:P3})", midpt,
((double) midpt)/total);
Console.WriteLine(s);
cte.Signal();
}
}
// The example displays output like the following:
// Thread Thread2:
// Random Numbers: 2,776,674
// Midpoint values: 2,773 (0.100 %)
// Thread Thread1:
// Random Numbers: 4,876,100
// Midpoint values: 4,873 (0.100 %)
// Thread Thread0:
// Random Numbers: 2,312,310
// Midpoint values: 2,354 (0.102 %)
//
// Total midpoint values: 10,000 (0.100 %)
// Total number of values: 9,965,084
Poniższy przykład jest podobny do poprzedniego, z tą różnicą, że używa klasy Task zamiast procedury wątku do generowania 50 000 losowych liczb całkowitych punktu środkowego. W tym przykładzie wyrażenie lambda zastępuje procedurę wątku GenerateNumbers, a wywołanie metody Task.WaitAll eliminuje potrzebę stosowania obiektu CountdownEvent.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
public class Example
{
const int LOWERBOUND = 0;
const int UPPERBOUND = 1001;
static Object lockObj = new Object();
static Random rnd = new Random();
static int totalCount = 0;
static int totalMidpoint = 0;
static int midpointCount = 0;
public static void Main()
{
List<Task> tasks = new List<Task>();
// Start three tasks.
for (int ctr = 0; ctr <= 2; ctr++)
tasks.Add(Task.Run( () => { int midpoint = (UPPERBOUND - LOWERBOUND) / 2;
int value = 0;
int total = 0;
int midpt = 0;
do {
lock (lockObj) {
value = rnd.Next(LOWERBOUND, UPPERBOUND);
}
if (value == midpoint) {
Interlocked.Increment(ref midpointCount);
midpt++;
}
total++;
} while (midpointCount < 50000);
Interlocked.Add(ref totalCount, total);
Interlocked.Add(ref totalMidpoint, midpt);
string s = String.Format("Task {0}:\n", Task.CurrentId) +
String.Format(" Random Numbers: {0:N0}\n", total) +
String.Format(" Midpoint values: {0:N0} ({1:P3})", midpt,
((double) midpt)/total);
Console.WriteLine(s); } ));
Task.WaitAll(tasks.ToArray());
Console.WriteLine();
Console.WriteLine("Total midpoint values: {0,10:N0} ({1:P3})",
totalMidpoint, totalMidpoint/((double)totalCount));
Console.WriteLine("Total number of values: {0,10:N0}",
totalCount);
}
}
// The example displays output like the following:
// Task 3:
// Random Numbers: 10,855,250
// Midpoint values: 10,823 (0.100 %)
// Task 1:
// Random Numbers: 15,243,703
// Midpoint values: 15,110 (0.099 %)
// Task 2:
// Random Numbers: 24,107,425
// Midpoint values: 24,067 (0.100 %)
//
// Total midpoint values: 50,000 (0.100 %)
// Total number of values: 50,206,378
https://docs.microsoft.com/en-us/dotnet/api/system.threading.interlocked.increment?view=netcore-3.0