답변:
다른 사람들이 권장하는 Interlocked.Increment
것보다 성능이 더 좋습니다 lock()
. IL 및 어셈블리를 살펴보면 Increment
"bus lock"문으로 바뀌고 변수가 직접 증가 (x86)되거나 "x64"에 추가되는 것을 볼 수 있습니다.
이 "bus lock"문은 호출 CPU가 작동하는 동안 다른 CPU가 버스에 액세스하지 못하도록 버스를 잠급니다. 이제 C # lock()
문의 IL을 살펴보십시오 . 여기 Monitor
에서 섹션을 시작하거나 종료하기위한 호출이 표시됩니다 .
즉, .Net lock()
문은 .Net보다 많은 일을하고 있습니다 Interlocked.Increment
.
따라서 변수를 늘리는 것만 큼 Interlock.Increment
빨라집니다. 사용 가능한 다양한 원자 연산을 확인하고 필요에 맞는 연산을 찾으려면 모든 연동 방법을 검토하십시오. lock()
여러 관련 증가 / 감소와 같은보다 복잡한 작업을 수행하거나 정수보다 복잡한 리소스에 대한 액세스를 직렬화하려는 경우에 사용하십시오 .
System.Threading 라이브러리에서 .NET의 내장 인터록 증분을 사용하는 것이 좋습니다.
다음 코드는 참조로 긴 변수를 증가시키고 완전히 스레드 안전합니다.
Interlocked.Increment(ref myNum);
이미 언급했듯이 Interlocked.Increment
MS의 코드 예제 :
다음 예제는 중간 점 값으로 1,000 개의 난수를 생성하는 데 필요한 0에서 1,000 사이의 난수를 결정합니다. 중간 점 값의 수를 추적하기 위해 변수 midpointCount가 0으로 설정되고 난수 생성기가 10,000에 도달 할 때까지 난수 생성기가 중간 점 값을 리턴 할 때마다 증가합니다. 세 개의 스레드가 난수를 생성하므로 Increment (Int32) 메서드가 호출되어 여러 스레드가 midpointCount를 동시에 업데이트하지 않도록합니다. 잠금은 난수 생성기를 보호하는 데에도 사용되며 CountdownEvent 객체는 Main 메서드가 세 개의 스레드 전에 실행을 완료하지 못하도록하는 데 사용됩니다.
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
다음 예제는 스레드 프로 시저 대신 Task 클래스를 사용하여 50,000 개의 임의의 중간 점 정수를 생성한다는 점을 제외하면 이전 예제와 유사합니다. 이 예제에서 람다 식은 GenerateNumbers 스레드 프로 시저를 대체하고 Task.WaitAll 메서드를 호출하면 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