C# 从不同的线程读取和写入相同的内存

C# 从不同的线程读取和写入相同的内存,c#,.net,multithreading,async-await,thread-synchronization,C#,.net,Multithreading,Async Await,Thread Synchronization,我有一个简单的类,它异步发送请求 public class MyClass { private readonly ISender _sender; public MyClass(ISender sender) { _sender = sender; } public Task<string> SendAsync(string input, CancellationToken cancellationToken) {

我有一个简单的类,它异步发送请求

public class MyClass
{
    private readonly ISender _sender;

    public MyClass(ISender sender)
    {
        _sender = sender;
    }

    public Task<string> SendAsync(string input, CancellationToken cancellationToken)
    {
        return _sender.SendAsync(input, cancellationToken);
    }
}

public interface ISender
{
    Task<string> SendAsync(string input, CancellationToken cancellationToken);
}
公共类MyClass
{
专用只读ISender\u发送方;
公共MyClass(ISender发件人)
{
_发送方=发送方;
}
公共任务SendAsync(字符串输入,CancellationToken CancellationToken)
{
返回_sender.SendAsync(输入,取消令牌);
}
}
公共接口ISender
{
任务SendAsync(字符串输入,CancellationToken CancellationToken);
}
所有这些看起来都很简单,直到满足以下要求:\可以在运行时更改发送方

MyClass的新实现:

public class MyClass
{
    private readonly ISender _sender;

    public MyClass(ISender sender)
    {
        _sender = sender;
    }

    public Task<string> SendAsync(string input, CancellationToken cancellationToken)
    {
        return _sender.SendAsync(input, cancellationToken);
    }

    public void SenderChanged(object unused, SenderEventArgs e)
    {
        ISender previous = Interlocked.Exchange(ref _sender, SenderFactory.Create(e.NewSenderConfig));
        previous.Dispose();
    }
}
公共类MyClass
{
专用只读ISender\u发送方;
公共MyClass(ISender发件人)
{
_发送方=发送方;
}
公共任务SendAsync(字符串输入,CancellationToken CancellationToken)
{
返回_sender.SendAsync(输入,取消令牌);
}
已更改公用无效发件人(未使用的对象,发件人目标)
{
ISender previous=Interlocked.Exchange(参考发送方,发送方工厂创建(例如NewSenderConfig));
previous.Dispose();
}
}
显然,这段代码不是线程安全的。我需要在
sendsync
SenderChanged
中引入
lock
,以确保发送方始终是最新的对象。 但是我希望
SenderChanged
每天调用一次,并且
sendsync
(读取\u sender对象)每秒调用10000次。
锁定
和上下文切换会破坏此代码的性能


是否有可能通过低电平锁定来处理此问题?或者,在了解上述要求的情况下,您将如何解决此问题

通常的方法是使用读写器锁,特别是。这是一个类似监视器的锁,可以优化频繁读访问和不频繁写访问,并且它支持多个并发读卡器和一个写卡器,这似乎正是您的用例

然而,它的成本似乎并不高。我编写了两个测试—一个使用
readerwriterlocksim
正确地执行操作,另一个使用您的实现,唯一的更改是一个已处理的异常重试循环。就我而言,我更换了20次发件人,每10秒一次。这比您提议的用例要短得多,但可以作为性能差异的估计

最后:

  • 读写器锁每毫秒能通过2878个工作单位
  • “带重试功能的裸机”能够以每秒9940个工作单位的速度运行
其中“workunit”正在调用DoWork方法,该方法调用
Thread.SpinWait(100)
。如果您想自己测试,下面将发布代码

编辑:

我调整了
Thread.SpinWait()
调用,以更改在锁定和“工作”上花费的时间的平衡。在我的机器上,旋转等待大约为900-1000次,两种实现以相同的速度运行,大约1000个工作单位/毫秒。从上面的结果来看,这应该是显而易见的,但我确实想做一次理智检查

事实上,最初的结果表明,我们能够使用锁每秒处理大约280万个请求;至少在我的4核Intel CPU机器上,“Intel core 2 Quad CPU Q9650@3.00 GHz”。考虑到您正在争取每秒10k请求,在锁定开始成为CPU使用的重要部分之前,您似乎已经有了大约一个数量级的净空


#定义使用读写器
使用制度;
使用System.Collections.Generic;
使用系统诊断;
使用System.Linq;
使用系统线程;
使用System.Threading.Tasks;
使用System.Windows.Forms;
命名空间测试项目
{
静态类程序
{
/// 
///应用程序的主要入口点。
/// 
[状态线程]
静态void Main()
{
SenderDispatch dispatch=新SenderDispatch();
列表工作者=新列表();
工人。添加(新工人(派遣,“A”);
工人。添加(新工人(派遣,“B”);
增加(新工人(派遣,“C”);
工人。增加(新工人(派遣,“D”);
Thread.CurrentThread.Name=“主线程”;
Process.GetCurrentProcess().PriorityClass=ProcessPriorityClass.High;
秒表=新秒表();
watch.Start();
ForEach(x=>x.Start());
对于(int i=0;i<20;i++)
{
睡眠(10000);
dispatch.NewSender();
}
控制台。写入线(“停止…”);
ForEach(x=>x.Stop());
看,停;
控制台。写入线(“停止”);
long sum=workers.sum(x=>x.FinalCount);
字符串消息=
工作循环次数之和:“+Sum.ToString(“n0”)+”\r\n+
“总时间:”+(watch.elapsedmillyses/1000.0)。ToString(“0.000”)+“\r\n”+
“迭代次数/ms:+sum/watch.elapsedmillyses;
MessageBox.Show(message);
}
}
公社工人
{
专用发送器调度调度器;
私有线程;
私人布尔工作;
私有字符串工作名;
公共工作人员(发送方Dispatch dispatcher、字符串工作名)
{
this.dispatcher=dispatcher;
this.workerName=workerName;
这个工作=假;
}
公共长FinalCount{get;private set;}
公开作废开始()
{
this.thread=新线程(运行);
this.thread.Name=“Worker”+this.workerName;
这是真的;
this.thread.Start();
}
脉波重复间隔
#define USE_READERWRITER

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace TestProject
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            SenderDispatch dispatch = new SenderDispatch();

            List<Worker> workers = new List<Worker>();

            workers.Add( new Worker( dispatch, "A" ) );
            workers.Add( new Worker( dispatch, "B" ) );
            workers.Add( new Worker( dispatch, "C" ) );
            workers.Add( new Worker( dispatch, "D" ) );

            Thread.CurrentThread.Name = "Main thread";
            Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;

            Stopwatch watch = new Stopwatch();

            watch.Start();
            workers.ForEach( x => x.Start() );

            for( int i = 0; i < 20; i++ )
            {
                Thread.Sleep( 10000 );
                dispatch.NewSender();
            }

            Console.WriteLine( "Stopping..." );

            workers.ForEach( x => x.Stop() );
            watch.Stop();

            Console.WriteLine( "Stopped" );

            long sum = workers.Sum( x => x.FinalCount );

            string message = 
                "Sum of worker iterations: " + sum.ToString( "n0" ) + "\r\n" +
                "Total time:               " + ( watch.ElapsedMilliseconds / 1000.0 ).ToString( "0.000" ) + "\r\n" +
                "Iterations/ms:            " + sum / watch.ElapsedMilliseconds;

            MessageBox.Show( message );
        }
    }

    public class Worker
    {
        private SenderDispatch dispatcher;
        private Thread thread;
        private bool working;

        private string workerName;

        public Worker( SenderDispatch dispatcher, string workerName )
        {
            this.dispatcher = dispatcher;
            this.workerName = workerName;

            this.working = false;
        }

        public long FinalCount { get; private set; }

        public void Start()
        {
            this.thread = new Thread( Run );
            this.thread.Name = "Worker " + this.workerName;

            this.working = true;
            this.thread.Start();
        }

        private void Run()
        {
            long state = 0;

            while( this.working )
            {
                this.dispatcher.DoOperation( workerName, state );
                state++;
            }

            this.FinalCount = state;
        }

        public void Stop()
        {
            this.working = false;

            this.thread.Join();
        }
    }

    public class SenderDispatch
    {
        private Sender sender;

        private ReaderWriterLockSlim senderLock;

        public SenderDispatch()
        {
            this.sender = new Sender();
            this.senderLock = new ReaderWriterLockSlim( LockRecursionPolicy.NoRecursion );
        }

        public void DoOperation( string workerName, long value )
        {

#if USE_READERWRITER
            this.senderLock.EnterReadLock();
            try
            {
                this.sender.DoOperation( workerName, value );
            }
            finally
            {
                this.senderLock.ExitReadLock();
            }
#else 
            bool done = false;

            do
            {
                try
                {
                    this.sender.DoOperation( workerName, value );
                    done = true;
                }
                catch (ObjectDisposedException) { }
            }
            while( !done );
#endif

        }

        public void NewSender()
        {
            Sender prevSender;
            Sender newSender;

            newSender = new Sender();

#if USE_READERWRITER
            this.senderLock.EnterWriteLock();
            try
            {
                prevSender = Interlocked.Exchange( ref this.sender, newSender );
            }
            finally
            {
                this.senderLock.ExitWriteLock();
            }
#else
            prevSender = Interlocked.Exchange( ref this.sender, newSender );
            prevSender.Dispose();

#endif
            prevSender.Dispose();

        }
    }

    public class Sender : IDisposable
    {
        private bool disposed;

        public Sender()
        {
            this.disposed = false;
        }

        public void DoOperation( string workerName, long value )
        {
            if( this.disposed )
            {
                throw new ObjectDisposedException( 
                    "Sender",
                    string.Format( "Worker {0} tried to queue work item {1}", workerName, value ) 
                );
            }

            Thread.SpinWait( 100 );
        }

        public void Dispose()
        {
            this.disposed = true;
        }
    }
}