C#线程安全快速(est)计数器
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/13181740/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
C# Thread safe fast(est) counter
提问by JohnDoDo
What is the way to obtain a thread safe counter in C# with best possible performance?
在 C# 中获得最佳性能的线程安全计数器的方法是什么?
This is as simple as it gets:
这很简单:
public static long GetNextValue()
{
long result;
lock (LOCK)
{
result = COUNTER++;
}
return result;
}
But are there faster alternatives?
但是有更快的替代方案吗?
采纳答案by Austin Salonen
This would be simpler:
这会更简单:
return Interlocked.Increment(ref COUNTER);
回答by fsimonazzi
Try with Interlocked.Increment
回答by Andrew White
I suggest you use .NET's built in interlock increment in the System.Threading library.
我建议您在 System.Threading 库中使用 .NET 的内置互锁增量。
The following code will increment a long variable by reference and is completely thread safe:
以下代码将通过引用递增一个 long 变量,并且是完全线程安全的:
Interlocked.Increment(ref myNum);
Source: http://msdn.microsoft.com/en-us/library/dd78zt0c.aspx
回答by Les
As recommended by others, the Interlocked.Incrementwill have better performance than lock(). Just take a look at the IL and Assembly where you will see that Incrementturns into a "bus lock" statement and its variable is directly incremented (x86) or "added" to (x64).
正如其他人所推荐的那样,Interlocked.Increment将比 具有更好的性能lock()。只需看看 IL 和程序集,您就会看到它Increment变成了“总线锁定”语句,并且它的变量直接递增 (x86) 或“添加”到 (x64)。
This "bus lock" statement locks the bus to prevent another CPU from accessing the bus while the calling CPU does its operation. Now, take a look at the C# lock()statement's IL. Here you will see calls to Monitorin order to begin or end a section.
这个“总线锁定”语句锁定总线以防止另一个 CPU 在调用 CPU 执行其操作时访问总线。现在,看看 C#lock()语句的 IL。在这里,您将看到调用以Monitor开始或结束一个部分。
In other words, .Net lock()statement is doing a lot more than the .Net Interlocked.Increment.
换句话说,.Netlock()语句比 .Net 做得更多Interlocked.Increment。
SO, if all you want to do is increment a variable, Interlock.Incrementwill be faster. Review all of the Interlocked methods to see the various atomic operations available and to find those that suit your needs. Use lock()when you want to do more complex things like multiple inter-related increments/decrements, or to serialize access to resources that are more complex than integers.
所以,如果你只想增加一个变量,Interlock.Increment会更快。查看所有 Interlocked 方法以查看各种可用的原子操作并找到适合您需求的操作。lock()当您想要做更复杂的事情时使用,例如多个相互关联的增量/减量,或者序列化对比整数更复杂的资源的访问。
回答by Ogglas
As already mentioned use Interlocked.Increment
正如已经提到的使用 Interlocked.Increment
Code example from MS:
来自 MS 的代码示例:
The following example determines how many random numbers that range from 0 to 1,000 are required to generate 1,000 random numbers with a midpoint value. To keep track of the number of midpoint values, a variable, midpointCount, is set equal to 0 and incremented each time the random number generator returns a midpoint value until it reaches 10,000. Because three threads generate the random numbers, the Increment(Int32) method is called to ensure that multiple threads don't update midpointCount concurrently. Note that a lock is also used to protect the random number generator, and that a CountdownEvent object is used to ensure that the Main method doesn't finish execution before the three threads.
下面的示例确定需要多少个范围从 0 到 1,000 的随机数才能生成 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
The following example is similar to the previous one, except that it uses the Task class instead of a thread procedure to generate 50,000 random midpoint integers. In this example, a lambda expression replaces the GenerateNumbers thread procedure, and the call to the Task.WaitAll method eliminates the need for the CountdownEvent object.
下面的示例与前一个示例类似,不同之处在于它使用 Task 类而不是线程过程来生成 50,000 个随机中点整数。在此示例中,一个 lambda 表达式替换了 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
https://docs.microsoft.com/en-us/dotnet/api/system.threading.interlocked.increment?view=netcore-3.0

