C# 多线程计数

C# 多线程计数,c#,multithreading,thread-safety,thread-local-storage,C#,Multithreading,Thread Safety,Thread Local Storage,在我的.NET程序中,我想计算一段代码被命中的次数。更具挑战性的是,我的代码通常在多个线程中执行,我无法控制线程的创建/销毁(也不知道它们是何时创建的)。。。他们甚至可以合并。说: class Program { static int counter = 0; static void Main(string[] args) { Stopwatch sw = Stopwatch.StartNew(); Parallel.For(0, 10

在我的.NET程序中,我想计算一段代码被命中的次数。更具挑战性的是,我的代码通常在多个线程中执行,我无法控制线程的创建/销毁(也不知道它们是何时创建的)。。。他们甚至可以合并。说:

class Program
{
    static int counter = 0;

    static void Main(string[] args)
    {
        Stopwatch sw = Stopwatch.StartNew();

        Parallel.For(0, 100000000, (a) =>
            {
                Interlocked.Increment(ref counter);
            });

        Console.WriteLine(sw.Elapsed.ToString());
    }
}
由于性能计数器和方法被多次命中,我想使用“normal”变量,而不是原子/互锁整数。因此,我的第二次尝试是将threadlocal存储与IDisposable结合使用,以加快速度。因为我无法控制创建/销毁,所以我必须跟踪存储变量:

class Program
{
    static int counter = 0;

    // I don't know when threads are created / joined, which is why I need this:
    static List<WeakReference<ThreadLocalValue>> allStorage = 
        new List<WeakReference<ThreadLocalValue>>();

    // The performance counter
    [ThreadStatic]
    static ThreadLocalValue local;

    class ThreadLocalValue : IDisposable
    {
        public ThreadLocalValue()
        {
            lock (allStorage)
            {
                allStorage.Add(new WeakReference<ThreadLocalValue>(this));
            }
        }

        public int ctr = 0;

        public void Dispose()
        {
            // Atomic add and exchange
            int tmp = Interlocked.Exchange(ref ctr, 0); // atomic set to 0-with-read
            Interlocked.Add(ref Program.counter, tmp); // atomic add
        }

        ~ThreadLocalValue()
        {
            // Make sure it's merged.
            Dispose();
        }
    }

    // Create-or-increment
    static void LocalInc()
    {
        if (local == null) { local = new ThreadLocalValue(); } 
        ++local.ctr;
    }

    static void Main(string[] args)
    {
        Stopwatch sw = Stopwatch.StartNew();

        Parallel.For(0, 100000000, (a) =>
            {
                LocalInc();
            });

        lock (allStorage)
        {
            foreach (var item in allStorage)
            {
                ThreadLocalValue target;
                if (item.TryGetTarget(out target))
                {
                    target.Dispose();
                }
            }
        }

        Console.WriteLine(sw.Elapsed.ToString());

        Console.WriteLine(counter);
        Console.ReadLine();
    }
}
类程序
{
静态整数计数器=0;
//我不知道何时创建/加入线程,这就是为什么我需要:
静态列表所有存储=
新列表();
//性能计数器
[线程静态]
静态线程本地值本地;
类ThreadLocalValue:IDisposable
{
公共线程localvalue()
{
锁(所有存储)
{
添加(新WeakReference(this));
}
}
公共int ctr=0;
公共空间处置()
{
//原子添加和交换
int tmp=Interlocked.Exchange(ref-ctr,0);//原子设置为0-with-read
Interlocked.Add(ref Program.counter,tmp);//原子添加
}
~ThreadLocalValue()
{
//确保它已合并。
处置();
}
}
//创造或增加
静态void LocalInc()
{
如果(local==null){local=new ThreadLocalValue();}
++local.ctr;
}
静态void Main(字符串[]参数)
{
秒表sw=Stopwatch.StartNew();
对于(0,100000000,(a)=>
{
LocalInc();
});
锁(所有存储)
{
foreach(所有存储中的var项)
{
价值目标;
if(项目TryGetTarget(目标外))
{
target.Dispose();
}
}
}
Console.WriteLine(sw.appeased.ToString());
控制台。写线(计数器);
Console.ReadLine();
}
}

我的问题是:我们能做得更快和/或更漂亮吗?

您需要一个线程安全、非阻塞的变量来为您执行计数。


谢天谢地,.NET framework提供了可管理的方法来执行您想要的操作

首先,您需要一个可变的静态变量作为计数器。像这样声明它(您的所有线程都可以访问它):

其中static表示这是一个类而不是实例成员,volatile可以防止发生缓存错误

接下来,您将需要一个以线程安全和非阻塞方式递增它的代码。如果您不希望计数器超过int变量的限制(这很可能),则可以使用:

Interlocked.Increment(ref yourInstance.volatileCounter);

将保证您的增量操作是原子的,因此没有争用条件会导致错误的结果,并且它也是非阻塞的,以重加权的方式,这里涉及线程阻塞。

您知道访问
ThreadStatic
字段要比共享的原子增量慢得多吗。你让事情变得更糟了。您是否面临任何性能问题?@SriramSakthivel请运行测试。带有
联锁的原始版本。增量
在此在1,6秒内执行,而第二个版本在0.2秒内运行。@Atlast现在有两个
Dispose()
同时运行。两者都可以在执行
ctr=0
之前执行
Interlocked.Increment()
,在@atlaste添加两次
ctr
,并且
列表未锁定。。。对
List
GC.WaitForPendingFinalizers()
@atlaste进行了一些更改以支持交换和锁定
GC.Suppress
是无用的,但是
GC.Wait..
是必需的:您可以运行一个执行
Interl.Exchange
的终结器,然后调度程序暂停其线程,
foreach
执行
Dispose()
,但是执行
Interl.Exchange
它得到0(因为终结器
Interl.Exchange
已经删除了该值),所以它不会添加任何内容。。。主线程像火车一样运行,无需等待终结器线程。。。现在您在
控制台上。WriteLine
,在完成终结器
交换.Increment
之前执行它。Ehm。。。这就是我开始提问的原因?我的
ThreadStatic
解决方案实际上要快得多。@Atlast你从哪里开始提问的?我重读了一遍,没能找到它。此外,我对底层系统的详细了解使我强烈怀疑是否有比我更快的线程安全解决方案。我可以想象一个解决方案在某些条件下同样快速,但仅此而已(因为变量必须是可变的以避免缓存错误,执行非阻塞线程安全增量的最快方法是InterlockedIncrement(),它由interlocked类在后台调用)。好吧,我们似乎误解了彼此。在我的问题中,我从
Interlocked.Increment
的一个实现开始,然后我注意到“我想使用一个‘normal’变量,而不是一个原子的/联锁的整数”。要求性能计数器在程序执行后必须具有正确的值。执行期间线程之间的一致性不是一项要求。由于这种宽松的约束,您可以认为,
Interlocked
对于我想要做的事情来说太重了,因此在没有原子访问的情况下应该可以实现更好的实现
Interlocked.Increment(ref yourInstance.volatileCounter);